| """Probe live Sentinel-1 RTC fetch via Microsoft Planetary Computer. |
| |
| Sentinel-1 RTC (radiometric terrain corrected) is the SAR product our |
| TerraMind-NYC model was trained on. Earth Search only hosts Sentinel-1 |
| GRD (raw slant-range, no CRS), which would require us to process the |
| RTC step ourselves — non-trivial. |
| |
| Microsoft PC hosts `sentinel-1-rtc` as a STAC collection. Has been |
| flaky in our prior tests (May 3 evening showed >50% timeout rate). |
| Re-probing here. |
| |
| Sovereignty disclosure: PC requires a one-time URL signing per asset, |
| sponsored by Microsoft. Same Copernicus license on the data. Less |
| sovereign than Earth Search; less authoritative than ESA CDSE. |
| |
| Usage: |
| python3 probe_pc_s1rtc.py --lat 40.7484 --lon -73.9857 |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import os |
| import sys |
| import time |
| from datetime import datetime, timedelta |
|
|
| os.environ.setdefault("AWS_NO_SIGN_REQUEST", "YES") |
| os.environ.setdefault("GDAL_DISABLE_READDIR_ON_OPEN", "EMPTY_DIR") |
| os.environ.setdefault("CPL_VSIL_CURL_USE_HEAD", "NO") |
|
|
| PC = "https://planetarycomputer.microsoft.com/api/stac/v1/" |
| S1_RTC = "sentinel-1-rtc" |
|
|
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument("--lat", type=float, default=40.7484) |
| ap.add_argument("--lon", type=float, default=-73.9857) |
| ap.add_argument("--max-age-days", type=int, default=30) |
| args = ap.parse_args() |
|
|
| from pystac_client import Client |
| import planetary_computer as pc |
| import rasterio |
| from rasterio.warp import transform as warp_transform |
| from rasterio.windows import from_bounds |
|
|
| print(f"[probe] PC STAC: {PC}", flush=True) |
| print(f"[probe] lat,lon = ({args.lat}, {args.lon})", flush=True) |
|
|
| today = datetime.utcnow().date() |
| earliest = (today - timedelta(days=args.max_age_days)).isoformat() |
| d = 0.01 |
| bbox = (args.lon - d, args.lat - d, args.lon + d, args.lat + d) |
|
|
| t0 = time.time() |
| err = None |
| item = None |
| for attempt in range(4): |
| try: |
| client = Client.open(PC) |
| items = list(client.search( |
| collections=[S1_RTC], |
| bbox=bbox, |
| datetime=f"{earliest}/{today.isoformat()}", |
| max_items=10, |
| ).items()) |
| if items: |
| items.sort(key=lambda i: i.properties["datetime"], reverse=True) |
| item = items[0] |
| break |
| err = f"no S1-RTC items in last {args.max_age_days} days" |
| except Exception as e: |
| err = f"PC search attempt {attempt+1}: {e}" |
| print(f"[probe] {err}", flush=True) |
| time.sleep(2 + 3 * attempt) |
|
|
| if not item: |
| print(f"[probe] FAIL: {err}", flush=True) |
| return 1 |
|
|
| s1_dt = datetime.fromisoformat(item.properties["datetime"].replace("Z", "+00:00")) |
| age = (today - s1_dt.date()).days |
| print(f"[probe] S1-RTC product: {item.id}", flush=True) |
| print(f"[probe] acquired: {item.properties['datetime']} ({age} days ago)", |
| flush=True) |
| print(f"[probe] PC search wall-clock: {time.time()-t0:.2f}s", flush=True) |
|
|
| |
| t = time.time() |
| try: |
| signed = pc.sign(item.assets["vv"].href) |
| with rasterio.open(signed) as src: |
| print(f"[probe] CRS: {src.crs}, shape: {src.shape}", flush=True) |
| HALF = 1280 |
| xs, ys = warp_transform( |
| "EPSG:4326", src.crs, |
| [args.lon - HALF/85_000.0, args.lon + HALF/85_000.0], |
| [args.lat - HALF/111_000.0, args.lat + HALF/111_000.0], |
| ) |
| window = from_bounds(xs[0], ys[0], xs[1], ys[1], src.transform) |
| chip = src.read(1, window=window, boundless=True, fill_value=0, |
| out_shape=(256, 256)) |
| print(f"[probe] vv chip read: {time.time()-t:.2f}s, " |
| f"shape={chip.shape}, dtype={chip.dtype}, " |
| f"nz_pct={(chip > 0).mean()*100:.1f}%, " |
| f"mean={float(chip.mean()):.4f}", flush=True) |
| except Exception as e: |
| print(f"[probe] vv chip read FAIL: {e}", flush=True) |
| return 1 |
|
|
| print(f"\n[probe] === Result ===", flush=True) |
| print(json.dumps({ |
| "ok": True, |
| "source": "microsoft_planetary_computer", |
| "collection": S1_RTC, |
| "product_id": item.id, |
| "s1_acquired_iso": item.properties["datetime"], |
| "s1_age_days": age, |
| "elapsed_s": round(time.time() - t0, 2), |
| "license": "ESA Copernicus License (free for any use, attribution required)", |
| "host": "Microsoft Planetary Computer (URL-signed, free)", |
| }, indent=2, default=str)) |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|