isaaccorley/geospatial-skills/planet-bulk-download
Claude Code and agent skill by isaaccorley/geospatial-skills
- community
Checking that the SKILL.md file is still in the repository.
Download PlanetScope imagery at scale (hundreds to 100k+ AOIs) via the Planet Python SDK v2. Covers search/activate/extract phases, COG /vsicurl/ vs Orders API, GDAL config for signed URLs, retry logic, concurrency budgets, and Planet-side gotchas (broken activations, cold-storage thaw tails, dead assets). Use when downloading at scale, hitting 429s or stuck activations, choosing Orders API vs COG reads, or designing a resumable batch Planet pipeline.
Install
npx vskill@latest install isaaccorley/geospatial-skills/planet-bulk-downloadSKILL.md
Planet Bulk Download
Hard-won reference for pulling PlanetScope imagery at scale with the Planet SDK v2. The SDK is thin: no retries, no batching, no awareness of cold-storage thaw, no scene grouping. You build all of that. This document is the part you will otherwise rediscover by burning a 17-hour batch job.
Architecture: three-phase pipeline
Split the job into search → activate → extract, each with its own JSONL cache, its own concurrency knob, and its own resume logic. The phases have completely different bottlenecks:
- search: cheap metadata calls, rate-limited client-side.
- activate: Planet-side cold-storage thaw queue, capped at ~10 cold scenes/min/account regardless of how many you request.
- extract: HTTP range reads via GDAL, network-bound at ~0.5s/patch on a 1.5km AOI.
Suggested layout:
Phase a re-activation pass after the first extract pass (see Reliability Gotchas below).
Scene grouping is the activation budget killer
Many patches share scenes (mean ~23 patches/scene in practice). Group all patches needing scene `S`, activate `S` once, then read every patch's window from the warm URL. Cuts activations 10-20x.
Sharded array jobs
For search and extract, shard by patch index. Aim 1-5k entries per shard so a single shard finishes in 10-30 min (good for walltime budgeting and partial-progress visibility).
Hash by item_id for extract so the same scene is always processed by the same task — GDAL's /vsicurl/ cache stays warm across patches.
COG range-reads beat the Orders API by ~100x
PSScene ortho_analytic_4b_sr has been a Cloud-Optimized GeoTIFF since ~2022. After :activate, the signed location URL supports HTTP range reads via GDAL /vsicurl/. For windowed access (anything less than a full strip), the Orders API queue + clip-tool path is dramatically slower and adds another queue to fail through.
Use the Orders API only when you need full-strip downloads or server-side reprojection you cannot do locally.
GDAL config for /vsicurl/ COG reads
Apply via rasterio.Env(**opts). Pass numeric values as Python ints, not strings — GDAL_CACHEMAX and a few others go through C funcs that require int. Strings silently break with cryptic errors.
Critical: do NOT set `CPL_VSIL_CURL_ALLOWED_EXTENSIONS`. Planet's signed URLs end with ?token=..., not .tif. The extension allowlist will reject them. Half a day was lost to this.
Retry logic (the SDK has none)
Wrap every Data API call — search, list_item_assets, activate_asset, wait_asset — in a retry that handles all of:
planet.exceptions.TooManyRequests(429). Exponential backoff starting 0.5s, cap 30s, ~8 attempts. Planet does not sendRetry-After; pace yourself.planet.exceptions.APIErrorwhose message contains"503","502","504", or"Server Error". Same backoff. The SDK does not expose a clean status code on all error paths — heuristic match on the message string is required.httpx.ConnectTimeout,httpx.ReadTimeout,httpx.ConnectError,httpx.RemoteProtocolError,httpx.ReadError. All happen on multi-hour runs.
sess.client("data") returns an Any-typed object that breaks static checkers. Cast to Any to silence:
Concurrency tuning (measured, paid account)
| Phase | Per-node concurrency | Notes | |
|---|---|---|---|
| Search | 16/shard × 8 shards (~128 inflight) | 512 inflight gets 429-throttled. Aim 64-128. | |
| Activate | 32-96 | Planet's queue caps total throughput ~10 cold scenes/min/account. Past ~64 is diminishing returns. | |
| Extract | 32-64 | Network-bound ~0.5s read + 0.08s write per 1.5km patch. Disk is not the bottleneck. |
The per-account activation cap is undocumented. Ask Planet support directly if you need it lifted.
Activate SR and UDM2 in parallel via `asyncio.gather`. UDM2 is ~3x slower than SR for unknown reasons (worth reporting to Planet). Doing them serially halves throughput.
Planet reliability gotchas (plan around these)
- ~24% of `:activate` responses return `status=active` with a broken URL. First range-read fails HTTP 400:
"Failure writing output to destination, passed 107 returned 0". Solution: a second:activatereturns a different, working URL — asset is still active, the URL itself was corrupted. Build a re-activation pass: scan extract logs foropen_failed, call:activateagain, append a fresh row toactivations.jsonl. Use last-write-wins onitem_idwhen loading. - Some activations return success with `location=null`. Same pattern; re-activate.
- Pre-2018 archive and certain regions (India, Finland observed) have permanently dead assets. Multiple reactivations do not help. Budget 3-5% unrecoverable.
- Cold-storage thaw p99 ≥ 45 minutes for never-touched scenes. Mean ~3 min once warm. If Planet support enables it, pre-warming a list of scene IDs ahead of time collapses the long tail.
Efficient patterns
Resumable JSONL caches everywhere
Append-only, flushed every line. On rerun, build the set of done keys and skip them. Cheap and crash-safe.
Always wrap `json.loads` in try/except. NFS append-races produce torn lines that crash a naive loader.
Don't wipe output files before re-extract
If you wipe <patch>.tif for resample then the resample fails, you have lost the original too. Either:
- Don't wipe — let
rasterio.open(path, "w")overwrite naturally on success. - Write to
<patch>.tif.tmpandos.replaceon success.
UDM2 (or any small companion asset) gets its own 3-phase pass
Run a separate plan → activate → extract pass for UDM2 after the SR pipeline lands. Ships SR-first results while UDM2 is still thawing on Planet's side.
Patch-level cloud cover ≠ scene-level cloud cover
A scene with 10% scene-level cloud can have a specific 1.5km patch at 100% cover. Always probe UDM2 at the actual patch AOI post-hoc. Re-sample cloudy patches against alternative scenes from the search results.
Anti-patterns
- Do not use the Orders API for windowed access at scale.
- Do not trust the SDK to auto-retry — there are no built-in retries.
- Do not activate UDM2 inside per-patch loops — batch by unique
item_idfirst. - Do not set
CPL_VSIL_CURL_ALLOWED_EXTENSIONSwith Planet signed URLs. - Do not pass GDAL config numeric values as strings.
- Do not run
wait_assetpolling without a max-attempts cap — stuck scenes will pin a worker forever. - Do not wipe existing outputs before a re-extract attempt.
- Do not assume
status=activemeans the URL works — verify with the first read, retry-by-reactivate on failure.
Quick pre-flight checklist
- AOIs deduped and grouped by
item_idbefore any activation call. - JSONL caches for search/activations/extract wired up and resumable.
- Retry wrapper installed around every Data API call (
429+5xxstrings +httpxtransients). - GDAL config dict in place, numeric values as ints,
CPL_VSIL_CURL_ALLOWED_EXTENSIONSunset. - SR and UDM2 activations launched in parallel via
asyncio.gather. - Concurrency capped (search ≤128, activate ≤96, extract ≤64).
- Re-activation pass planned for
open_failedextract rows. - Budget 3-5% permanently unrecoverable assets.
- Shard size 1-5k entries; extract sharded by
hash(item_id). - Post-hoc UDM2 cloud probe at patch AOI, not scene level.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/planet-bulk-download/SKILL.md
Popularity
- Repository stars
- 79
- Repository forks
- 12
- 7-day trend score
- 0
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/isaaccorley/geospatial-skills/planet-bulk-download)