Spaces:
Sleeping
Sleeping
M5: add curated Sentinel-2 (Track-B) tab — baked OSCD AOIs, cache-served
Browse files- .gitattributes +10 -0
- Dockerfile +1 -0
- backend/app.py +38 -1
- backend/build_sentinel2.py +485 -0
- backend/data/sentinel2/_predictions.json +0 -0
- backend/data/sentinel2/beijing_daxing/after.png +3 -0
- backend/data/sentinel2/beijing_daxing/before.png +3 -0
- backend/data/sentinel2/beijing_daxing/overlay.png +0 -0
- backend/data/sentinel2/bhadla_solar/after.png +3 -0
- backend/data/sentinel2/bhadla_solar/before.png +3 -0
- backend/data/sentinel2/bhadla_solar/overlay.png +0 -0
- backend/data/sentinel2/dubai_deira/after.png +3 -0
- backend/data/sentinel2/dubai_deira/before.png +3 -0
- backend/data/sentinel2/dubai_deira/overlay.png +0 -0
- backend/data/sentinel2/egypt_newcapital/after.png +3 -0
- backend/data/sentinel2/egypt_newcapital/before.png +3 -0
- backend/data/sentinel2/egypt_newcapital/overlay.png +0 -0
- backend/data/sentinel2/gerd_reservoir/after.png +3 -0
- backend/data/sentinel2/gerd_reservoir/before.png +3 -0
- backend/data/sentinel2/gerd_reservoir/overlay.png +0 -0
- backend/data/sentinel2/manifest.json +99 -0
- backend/sentinel2.py +93 -0
- frontend/src/App.tsx +27 -4
- frontend/src/api.ts +6 -1
- frontend/src/assets/land-110m.json +1 -0
- frontend/src/components/CompareView.tsx +3 -1
- frontend/src/components/Sentinel2View.tsx +299 -0
- frontend/src/styles.css +114 -1
- frontend/src/types.ts +24 -0
.gitattributes
CHANGED
|
@@ -47,3 +47,13 @@ backend/data/curated/levir_05_test08/after.png filter=lfs diff=lfs merge=lfs -te
|
|
| 47 |
backend/data/curated/levir_05_test08/before.png filter=lfs diff=lfs merge=lfs -text
|
| 48 |
backend/data/curated/levir_06_test32/after.png filter=lfs diff=lfs merge=lfs -text
|
| 49 |
backend/data/curated/levir_06_test32/before.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
backend/data/curated/levir_05_test08/before.png filter=lfs diff=lfs merge=lfs -text
|
| 48 |
backend/data/curated/levir_06_test32/after.png filter=lfs diff=lfs merge=lfs -text
|
| 49 |
backend/data/curated/levir_06_test32/before.png filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
backend/data/sentinel2/beijing_daxing/after.png filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
backend/data/sentinel2/beijing_daxing/before.png filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
backend/data/sentinel2/bhadla_solar/after.png filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
backend/data/sentinel2/bhadla_solar/before.png filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
backend/data/sentinel2/dubai_deira/after.png filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
backend/data/sentinel2/dubai_deira/before.png filter=lfs diff=lfs merge=lfs -text
|
| 56 |
+
backend/data/sentinel2/egypt_newcapital/after.png filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
backend/data/sentinel2/egypt_newcapital/before.png filter=lfs diff=lfs merge=lfs -text
|
| 58 |
+
backend/data/sentinel2/gerd_reservoir/after.png filter=lfs diff=lfs merge=lfs -text
|
| 59 |
+
backend/data/sentinel2/gerd_reservoir/before.png filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
CHANGED
|
@@ -18,6 +18,7 @@ ENV PYTHONUNBUFFERED=1 \
|
|
| 18 |
HF_HOME=/tmp/hf \
|
| 19 |
BUNDLES_DIR=/app/models \
|
| 20 |
CURATED_DIR=/app/data/curated \
|
|
|
|
| 21 |
FRONTEND_DIST=/app/frontend_dist
|
| 22 |
|
| 23 |
COPY backend/requirements.txt ./requirements.txt
|
|
|
|
| 18 |
HF_HOME=/tmp/hf \
|
| 19 |
BUNDLES_DIR=/app/models \
|
| 20 |
CURATED_DIR=/app/data/curated \
|
| 21 |
+
SENTINEL2_DIR=/app/data/sentinel2 \
|
| 22 |
FRONTEND_DIST=/app/frontend_dist
|
| 23 |
|
| 24 |
COPY backend/requirements.txt ./requirements.txt
|
backend/app.py
CHANGED
|
@@ -10,10 +10,13 @@ Endpoints
|
|
| 10 |
GET /api/curated curated before/after pairs (id, title, size, ...)
|
| 11 |
GET /api/curated/{id}/{which}.png the before/after image
|
| 12 |
POST /api/predict {pair_id, model_id} -> change overlay (PNG data URL) + stats
|
|
|
|
|
|
|
| 13 |
|
| 14 |
Config via env:
|
| 15 |
BUNDLES_DIR dir of exported model bundles (default: app/backend/models)
|
| 16 |
CURATED_DIR dir of curated pairs + manifest (default: app/backend/data/curated)
|
|
|
|
| 17 |
FRONTEND_DIST built React app to serve at / (default: app/frontend/dist)
|
| 18 |
HF_BUNDLE_REPO optional HF model repo to pull bundles from at startup (id or url)
|
| 19 |
"""
|
|
@@ -33,10 +36,12 @@ from fastapi.responses import FileResponse, HTMLResponse, PlainTextResponse
|
|
| 33 |
from fastapi.staticfiles import StaticFiles
|
| 34 |
from inference import BundleRegistry
|
| 35 |
from pydantic import BaseModel
|
|
|
|
| 36 |
|
| 37 |
_HERE = Path(__file__).resolve().parent
|
| 38 |
BUNDLES_DIR = Path(os.environ.get("BUNDLES_DIR", _HERE / "models"))
|
| 39 |
CURATED_DIR = Path(os.environ.get("CURATED_DIR", _HERE / "data" / "curated"))
|
|
|
|
| 40 |
FRONTEND_DIST = Path(os.environ.get("FRONTEND_DIST", _HERE.parent / "frontend" / "dist"))
|
| 41 |
|
| 42 |
|
|
@@ -45,6 +50,11 @@ def _maybe_pull_bundles() -> None:
|
|
| 45 |
|
| 46 |
This is the HF-Space networking path (PRD §3/§9): the Space pulls weights from a companion
|
| 47 |
Model repo at startup. It is a no-op locally (repo unset) and never runs on Leonardo.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
"""
|
| 49 |
repo = os.environ.get("HF_BUNDLE_REPO", "").strip()
|
| 50 |
if not repo or (BUNDLES_DIR.exists() and any(BUNDLES_DIR.iterdir())):
|
|
@@ -53,7 +63,12 @@ def _maybe_pull_bundles() -> None:
|
|
| 53 |
from huggingface_hub import snapshot_download
|
| 54 |
|
| 55 |
BUNDLES_DIR.mkdir(parents=True, exist_ok=True)
|
| 56 |
-
snapshot_download(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
except Exception as exc: # noqa: BLE001 — startup pull is best-effort; app still serves UI
|
| 58 |
print(f"[startup] bundle pull from {repo!r} failed: {exc}")
|
| 59 |
|
|
@@ -61,6 +76,7 @@ def _maybe_pull_bundles() -> None:
|
|
| 61 |
_maybe_pull_bundles()
|
| 62 |
models = BundleRegistry(BUNDLES_DIR)
|
| 63 |
curated = CuratedRegistry(CURATED_DIR)
|
|
|
|
| 64 |
_predict_cache: dict[tuple[str, str], dict[str, Any]] = {}
|
| 65 |
# Curated pairs + models are fixed, so predictions are deterministic. tile+stitch inference is a few
|
| 66 |
# seconds of CPU per scene (DINOv2), so we bake predictions to disk (the deploy plan): the file is
|
|
@@ -144,6 +160,7 @@ def health() -> dict[str, Any]:
|
|
| 144 |
"status": "ok",
|
| 145 |
"models": models.ids(),
|
| 146 |
"n_curated": len(curated.pairs),
|
|
|
|
| 147 |
"bundles_dir": str(BUNDLES_DIR),
|
| 148 |
}
|
| 149 |
|
|
@@ -184,6 +201,26 @@ def predict(req: PredictRequest) -> dict[str, Any]:
|
|
| 184 |
return _compute_prediction(req.pair_id, req.model_id)
|
| 185 |
|
| 186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
# --- static frontend (mounted LAST so the /api/* routes above take precedence) ---------------
|
| 188 |
# html=True serves index.html for "/" and correctly serves hashed JS/CSS + binary assets.
|
| 189 |
if FRONTEND_DIST.exists():
|
|
|
|
| 10 |
GET /api/curated curated before/after pairs (id, title, size, ...)
|
| 11 |
GET /api/curated/{id}/{which}.png the before/after image
|
| 12 |
POST /api/predict {pair_id, model_id} -> change overlay (PNG data URL) + stats
|
| 13 |
+
GET /api/sentinel2 curated Sentinel-2 AOIs (baked results only; no runtime inference)
|
| 14 |
+
GET /api/sentinel2/{id}/{which}.png the before / after / overlay image
|
| 15 |
|
| 16 |
Config via env:
|
| 17 |
BUNDLES_DIR dir of exported model bundles (default: app/backend/models)
|
| 18 |
CURATED_DIR dir of curated pairs + manifest (default: app/backend/data/curated)
|
| 19 |
+
SENTINEL2_DIR dir of baked Sentinel-2 AOIs (default: app/backend/data/sentinel2)
|
| 20 |
FRONTEND_DIST built React app to serve at / (default: app/frontend/dist)
|
| 21 |
HF_BUNDLE_REPO optional HF model repo to pull bundles from at startup (id or url)
|
| 22 |
"""
|
|
|
|
| 36 |
from fastapi.staticfiles import StaticFiles
|
| 37 |
from inference import BundleRegistry
|
| 38 |
from pydantic import BaseModel
|
| 39 |
+
from sentinel2 import Sentinel2Registry
|
| 40 |
|
| 41 |
_HERE = Path(__file__).resolve().parent
|
| 42 |
BUNDLES_DIR = Path(os.environ.get("BUNDLES_DIR", _HERE / "models"))
|
| 43 |
CURATED_DIR = Path(os.environ.get("CURATED_DIR", _HERE / "data" / "curated"))
|
| 44 |
+
SENTINEL2_DIR = Path(os.environ.get("SENTINEL2_DIR", _HERE / "data" / "sentinel2"))
|
| 45 |
FRONTEND_DIST = Path(os.environ.get("FRONTEND_DIST", _HERE.parent / "frontend" / "dist"))
|
| 46 |
|
| 47 |
|
|
|
|
| 50 |
|
| 51 |
This is the HF-Space networking path (PRD §3/§9): the Space pulls weights from a companion
|
| 52 |
Model repo at startup. It is a no-op locally (repo unset) and never runs on Leonardo.
|
| 53 |
+
|
| 54 |
+
The OSCD Track-B (``oscd_*``) bundle is deliberately NOT pulled: the Sentinel-2 tab is served
|
| 55 |
+
entirely from the baked cache (no runtime inference), and the 4-band OSCD model cannot run on
|
| 56 |
+
the 3-band RGB curated pairs — pulling it would surface it in the aerial ``/api/models``
|
| 57 |
+
dropdown and 500 on select. It still lives in the Model repo as a published portfolio artifact.
|
| 58 |
"""
|
| 59 |
repo = os.environ.get("HF_BUNDLE_REPO", "").strip()
|
| 60 |
if not repo or (BUNDLES_DIR.exists() and any(BUNDLES_DIR.iterdir())):
|
|
|
|
| 63 |
from huggingface_hub import snapshot_download
|
| 64 |
|
| 65 |
BUNDLES_DIR.mkdir(parents=True, exist_ok=True)
|
| 66 |
+
snapshot_download(
|
| 67 |
+
repo_id=repo,
|
| 68 |
+
repo_type="model",
|
| 69 |
+
local_dir=str(BUNDLES_DIR),
|
| 70 |
+
ignore_patterns=["oscd_*", "oscd_*/*"], # Track-B artifact only; not served at runtime
|
| 71 |
+
)
|
| 72 |
except Exception as exc: # noqa: BLE001 — startup pull is best-effort; app still serves UI
|
| 73 |
print(f"[startup] bundle pull from {repo!r} failed: {exc}")
|
| 74 |
|
|
|
|
| 76 |
_maybe_pull_bundles()
|
| 77 |
models = BundleRegistry(BUNDLES_DIR)
|
| 78 |
curated = CuratedRegistry(CURATED_DIR)
|
| 79 |
+
sentinel2 = Sentinel2Registry(SENTINEL2_DIR)
|
| 80 |
_predict_cache: dict[tuple[str, str], dict[str, Any]] = {}
|
| 81 |
# Curated pairs + models are fixed, so predictions are deterministic. tile+stitch inference is a few
|
| 82 |
# seconds of CPU per scene (DINOv2), so we bake predictions to disk (the deploy plan): the file is
|
|
|
|
| 160 |
"status": "ok",
|
| 161 |
"models": models.ids(),
|
| 162 |
"n_curated": len(curated.pairs),
|
| 163 |
+
"n_sentinel2": len(sentinel2.aois),
|
| 164 |
"bundles_dir": str(BUNDLES_DIR),
|
| 165 |
}
|
| 166 |
|
|
|
|
| 201 |
return _compute_prediction(req.pair_id, req.model_id)
|
| 202 |
|
| 203 |
|
| 204 |
+
# --- Sentinel-2 (Track B): curated AOIs served ENTIRELY from the baked cache --------------------
|
| 205 |
+
# No runtime inference, no STAC, no GPU — the OSCD 4-band predictions were computed offline by
|
| 206 |
+
# build_sentinel2.py (aerial models do NOT transfer to 10 m, so this tab uses the Sentinel-2-native
|
| 207 |
+
# OSCD model only). The runtime never imports pystac/rasterio; it just serves PNGs + baked stats.
|
| 208 |
+
@app.get("/api/sentinel2")
|
| 209 |
+
def list_sentinel2() -> list[dict[str, Any]]:
|
| 210 |
+
return sentinel2.list()
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
@app.get("/api/sentinel2/{aoi_id}/{which}.png")
|
| 214 |
+
def sentinel2_image(aoi_id: str, which: str) -> FileResponse:
|
| 215 |
+
try:
|
| 216 |
+
path = sentinel2.image_path(aoi_id, which)
|
| 217 |
+
except (KeyError, ValueError) as exc:
|
| 218 |
+
raise HTTPException(404, f"no {which} image for Sentinel-2 AOI {aoi_id!r}") from exc
|
| 219 |
+
if not path.exists():
|
| 220 |
+
raise HTTPException(404, f"no {which} image for Sentinel-2 AOI {aoi_id!r}")
|
| 221 |
+
return FileResponse(path, media_type="image/png")
|
| 222 |
+
|
| 223 |
+
|
| 224 |
# --- static frontend (mounted LAST so the /api/* routes above take precedence) ---------------
|
| 225 |
# html=True serves index.html for "/" and correctly serves hashed JS/CSS + binary assets.
|
| 226 |
if FRONTEND_DIST.exists():
|
backend/build_sentinel2.py
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Build-time Sentinel-2 AOI bake for the curated Sentinel-2 (Track-B) tab — M5 Phase 4.
|
| 2 |
+
|
| 3 |
+
**BUILD-TIME ONLY.** This is the *only* place Planetary Computer STAC / rasterio run. Its deps
|
| 4 |
+
(``pystac-client``, ``planetary-computer``, ``rasterio``) are **build-time only** and are never
|
| 5 |
+
added to ``app/backend/requirements.txt`` — the runtime Space image stays STAC-free and serves the
|
| 6 |
+
baked cache exactly like the aerial curated mode (no runtime inference, no runtime STAC, no GPU).
|
| 7 |
+
|
| 8 |
+
For each confirmed AOI (a real-world location with large, obvious change visible even at 10 m) it:
|
| 9 |
+
|
| 10 |
+
1. Resolves the two confirmed low-cloud S2 **L2A** acquisitions from PC STAC (exact day + tile).
|
| 11 |
+
2. Reads a fixed window of bands **B04,B03,B02,B08** (R,G,B,NIR) around the AOI centre.
|
| 12 |
+
Co-registration is trivial: each AOI's before/after share one MGRS tile → identical UTM grid, so
|
| 13 |
+
the same pixel window is aligned by construction (asserted).
|
| 14 |
+
3. Harmonises reflectance across processing baselines (baseline ≥ 04.00 carries ``BOA_ADD_OFFSET``
|
| 15 |
+
``-1000``; OSCD predates it) so both dates and the model input match training's radiometry.
|
| 16 |
+
4. Renders 8-bit RGB display PNGs (joint percentile stretch so before/after are radiometrically
|
| 17 |
+
comparable).
|
| 18 |
+
5. Runs the OSCD 4-band ONNX bundle **offline** with its own tile-stitch — deliberately *not* the
|
| 19 |
+
``inference.py`` path, which is hard-coded RGB (3-band). ``inference.py._overlay_png`` IS
|
| 20 |
+
band-agnostic and is reused for the amber-fill + outline overlay.
|
| 21 |
+
6. Bakes ``before.png`` / ``after.png`` / ``overlay.png`` + ``manifest.json`` + a predictions cache
|
| 22 |
+
under the gitignored ``app/backend/data/sentinel2/`` — the cache schema the app already serves.
|
| 23 |
+
|
| 24 |
+
Usage (from the repo root, with the CPU ``.venv`` that has the STAC/rasterio deps)::
|
| 25 |
+
|
| 26 |
+
python app/backend/build_sentinel2.py # bake all AOIs
|
| 27 |
+
python app/backend/build_sentinel2.py --only gerd_reservoir --window 1536
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
from __future__ import annotations
|
| 31 |
+
|
| 32 |
+
import argparse
|
| 33 |
+
import base64
|
| 34 |
+
import json
|
| 35 |
+
import sys
|
| 36 |
+
import time
|
| 37 |
+
from dataclasses import dataclass, field
|
| 38 |
+
from pathlib import Path
|
| 39 |
+
from typing import Any
|
| 40 |
+
|
| 41 |
+
import numpy as np
|
| 42 |
+
import onnxruntime as ort
|
| 43 |
+
import planetary_computer as pc
|
| 44 |
+
import pystac_client
|
| 45 |
+
import rasterio
|
| 46 |
+
from PIL import Image
|
| 47 |
+
from rasterio.warp import transform as warp_transform
|
| 48 |
+
from rasterio.windows import Window
|
| 49 |
+
|
| 50 |
+
# reuse the band-agnostic overlay renderer from inference.py (flat import, as in app.py)
|
| 51 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
| 52 |
+
from inference import _overlay_png # noqa: E402
|
| 53 |
+
|
| 54 |
+
_HERE = Path(__file__).resolve().parent
|
| 55 |
+
_REPO = _HERE.parent.parent
|
| 56 |
+
BUNDLE_DIR = _REPO / "bundles" / "oscd_s2_baseline"
|
| 57 |
+
OUT_DIR = _HERE / "data" / "sentinel2"
|
| 58 |
+
MODEL_ID = "oscd_s2_baseline"
|
| 59 |
+
STAC_URL = "https://planetarycomputer.microsoft.com/api/stac/v1"
|
| 60 |
+
|
| 61 |
+
# Sentinel-2 bands in the OSCD band order [R, G, B, NIR].
|
| 62 |
+
BANDS = ("B04", "B03", "B02", "B08")
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
@dataclass
|
| 66 |
+
class AOI:
|
| 67 |
+
"""A curated Sentinel-2 change site: two confirmed low-cloud acquisitions on one MGRS tile."""
|
| 68 |
+
|
| 69 |
+
id: str
|
| 70 |
+
title: str
|
| 71 |
+
description: str
|
| 72 |
+
lat: float
|
| 73 |
+
lon: float
|
| 74 |
+
tile: str # MGRS tile, e.g. "T40RCP" — before/after share it → aligned by construction
|
| 75 |
+
date_before: str # "YYYY-MM-DD"
|
| 76 |
+
date_after: str
|
| 77 |
+
window: int = 1024 # crop side in 10 m pixels (10.24 km); tuned per feature scale
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
# The 5 confirmed AOIs (arid/engineered, crispest imagery — reclamation / reservoir / airport /
|
| 81 |
+
# solar / desert city). Dates + tiles verified low-cloud S2 L2A with all four bands present.
|
| 82 |
+
AOIS: list[AOI] = [
|
| 83 |
+
AOI(
|
| 84 |
+
"dubai_deira",
|
| 85 |
+
"Dubai — Deira Islands reclamation",
|
| 86 |
+
"Land reclaimed from the Persian Gulf: new coastline and island fill off Deira.",
|
| 87 |
+
25.30,
|
| 88 |
+
55.34,
|
| 89 |
+
"T40RCP",
|
| 90 |
+
"2016-03-03",
|
| 91 |
+
"2023-04-28",
|
| 92 |
+
window=1024,
|
| 93 |
+
),
|
| 94 |
+
AOI(
|
| 95 |
+
"gerd_reservoir",
|
| 96 |
+
"Grand Ethiopian Renaissance Dam — reservoir filling",
|
| 97 |
+
"The Blue Nile reservoir behind the GERD fills — open water where there was valley.",
|
| 98 |
+
11.21,
|
| 99 |
+
35.09,
|
| 100 |
+
"T36PYT",
|
| 101 |
+
"2020-02-14",
|
| 102 |
+
"2023-12-25",
|
| 103 |
+
window=1536,
|
| 104 |
+
),
|
| 105 |
+
AOI(
|
| 106 |
+
"beijing_daxing",
|
| 107 |
+
"Beijing Daxing International Airport",
|
| 108 |
+
"A greenfield mega-airport built from farmland — terminal, aprons and runways appear.",
|
| 109 |
+
39.51,
|
| 110 |
+
116.41,
|
| 111 |
+
"T50SMJ",
|
| 112 |
+
"2016-10-10",
|
| 113 |
+
"2020-10-04", # same-season (early-Oct), same-orbit (R075) after — minimal crop delta
|
| 114 |
+
window=1024,
|
| 115 |
+
),
|
| 116 |
+
AOI(
|
| 117 |
+
"bhadla_solar",
|
| 118 |
+
"Bhadla Solar Park, Rajasthan",
|
| 119 |
+
"One of the world's largest solar parks spreads across the Thar desert.",
|
| 120 |
+
27.53,
|
| 121 |
+
71.91,
|
| 122 |
+
"T42RYR",
|
| 123 |
+
"2017-12-17",
|
| 124 |
+
"2021-12-16",
|
| 125 |
+
window=1280,
|
| 126 |
+
),
|
| 127 |
+
AOI(
|
| 128 |
+
"egypt_newcapital",
|
| 129 |
+
"New Administrative Capital, Egypt",
|
| 130 |
+
"A new capital city rises from open desert east of Cairo.",
|
| 131 |
+
30.01,
|
| 132 |
+
31.75,
|
| 133 |
+
"T36RUU",
|
| 134 |
+
"2016-08-27",
|
| 135 |
+
"2023-08-26",
|
| 136 |
+
window=1280,
|
| 137 |
+
),
|
| 138 |
+
]
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
# -------------------------------------------------------------------------------------------------
|
| 142 |
+
# OSCD 4-band ONNX inference (standalone tile-stitch; the eval-harness / bundle preprocessing)
|
| 143 |
+
# -------------------------------------------------------------------------------------------------
|
| 144 |
+
@dataclass
|
| 145 |
+
class OscdModel:
|
| 146 |
+
"""The OSCD 4-band ONNX bundle + its documented preprocessing (band order, scaling, norm)."""
|
| 147 |
+
|
| 148 |
+
pre: dict[str, Any]
|
| 149 |
+
_session: ort.InferenceSession = field(repr=False)
|
| 150 |
+
|
| 151 |
+
@classmethod
|
| 152 |
+
def load(cls, bundle_dir: Path) -> OscdModel:
|
| 153 |
+
pre = json.loads((bundle_dir / "preprocessing.json").read_text())
|
| 154 |
+
so = ort.SessionOptions()
|
| 155 |
+
so.intra_op_num_threads = 2
|
| 156 |
+
sess = ort.InferenceSession(
|
| 157 |
+
str(bundle_dir / "model.onnx"), sess_options=so, providers=["CPUExecutionProvider"]
|
| 158 |
+
)
|
| 159 |
+
return cls(pre=pre, _session=sess)
|
| 160 |
+
|
| 161 |
+
@property
|
| 162 |
+
def mean(self) -> np.ndarray:
|
| 163 |
+
return np.asarray(self.pre["normalization"]["mean"], dtype=np.float32).reshape(-1, 1, 1)
|
| 164 |
+
|
| 165 |
+
@property
|
| 166 |
+
def std(self) -> np.ndarray:
|
| 167 |
+
return np.asarray(self.pre["normalization"]["std"], dtype=np.float32).reshape(-1, 1, 1)
|
| 168 |
+
|
| 169 |
+
@property
|
| 170 |
+
def threshold(self) -> float:
|
| 171 |
+
return float(self.pre["output"]["recommended_threshold"])
|
| 172 |
+
|
| 173 |
+
@property
|
| 174 |
+
def tile_size(self) -> int:
|
| 175 |
+
return int(self.pre.get("tiling", {}).get("tile_size", 256))
|
| 176 |
+
|
| 177 |
+
@property
|
| 178 |
+
def input_size(self) -> int:
|
| 179 |
+
return int(self.pre.get("input_size", 256))
|
| 180 |
+
|
| 181 |
+
def _standardize(self, refl: np.ndarray) -> np.ndarray:
|
| 182 |
+
"""(4, H, W) float32 reflectance -> standardized model input (÷scale already applied)."""
|
| 183 |
+
return (refl - self.mean) / self.std
|
| 184 |
+
|
| 185 |
+
def predict(
|
| 186 |
+
self, before: np.ndarray, after: np.ndarray
|
| 187 |
+
) -> tuple[np.ndarray, np.ndarray, int, float]:
|
| 188 |
+
"""Tile the scene into ``tile_size`` crops, run each 4-band pair through the ONNX model, and
|
| 189 |
+
stitch the probability map — the same per-tile procedure as ``src/evaluate.py`` and the
|
| 190 |
+
bundle's documented preprocessing. Returns ``(mask, prob, n_tiles, elapsed_ms)``.
|
| 191 |
+
|
| 192 |
+
``before``/``after`` are ``(4, H, W)`` standardized inputs. The model is fully convolutional
|
| 193 |
+
with dynamic H/W and ``input_size == tile_size``, so each tile is fed at its native size."""
|
| 194 |
+
_, h, w = before.shape
|
| 195 |
+
tile = self.tile_size
|
| 196 |
+
prob = np.zeros((h, w), dtype=np.float32)
|
| 197 |
+
n_tiles = 0
|
| 198 |
+
t0 = time.perf_counter()
|
| 199 |
+
for y0 in range(0, h, tile):
|
| 200 |
+
for x0 in range(0, w, tile):
|
| 201 |
+
y1, x1 = min(y0 + tile, h), min(x0 + tile, w)
|
| 202 |
+
b = before[:, y0:y1, x0:x1]
|
| 203 |
+
a = after[:, y0:y1, x0:x1]
|
| 204 |
+
x = np.stack([b, a], axis=0)[None].astype(np.float32) # (1, 2, 4, t, t)
|
| 205 |
+
logits = self._session.run(["logits"], {"input": x})[0]
|
| 206 |
+
prob[y0:y1, x0:x1] = 1.0 / (1.0 + np.exp(-logits[0, 0]))
|
| 207 |
+
n_tiles += 1
|
| 208 |
+
elapsed_ms = (time.perf_counter() - t0) * 1000.0
|
| 209 |
+
mask = prob >= self.threshold
|
| 210 |
+
return mask, prob, n_tiles, elapsed_ms
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
# -------------------------------------------------------------------------------------------------
|
| 214 |
+
# STAC fetch + windowed read + radiometric harmonisation
|
| 215 |
+
# -------------------------------------------------------------------------------------------------
|
| 216 |
+
COVER_MIN = 0.995 # a scene must fill the AOI window; same MGRS tile ≠ same orbit swath coverage
|
| 217 |
+
CLOUD_MAX = 25.0 # generous ceiling for a fallback; confirmed scenes are all < 1%
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def open_catalog() -> pystac_client.Client:
|
| 221 |
+
return pystac_client.Client.open(STAC_URL, modifier=pc.sign_inplace)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def _day(item: Any) -> str:
|
| 225 |
+
return item.datetime.strftime("%Y-%m-%d")
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def _coverage(item: Any, aoi: AOI) -> float:
|
| 229 |
+
"""Fraction of the AOI window with valid (non-nodata) data. A given acquisition only fills the
|
| 230 |
+
part of its MGRS tile its orbit swath covers, so ``eo:cloud_cover`` alone is not enough."""
|
| 231 |
+
with rasterio.open(item.assets["B04"].href) as ds:
|
| 232 |
+
dn = ds.read(1, window=_window_from_center(ds, aoi.lon, aoi.lat, aoi.window))
|
| 233 |
+
return float((dn > 0).mean())
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def find_item(cat: pystac_client.Client, aoi: AOI, date: str) -> tuple[Any, str, bool]:
|
| 237 |
+
"""Resolve an S2 L2A item that actually *covers* the AOI window. Prefers the confirmed ``date``
|
| 238 |
+
(lowest cloud if reprocessed twice); if that acquisition's swath misses the window, falls back
|
| 239 |
+
to the lowest-cloud covering scene that month on the same tile. Returns ``(item, day, fb)``."""
|
| 240 |
+
y, m, _ = date.split("-")
|
| 241 |
+
search = cat.search(
|
| 242 |
+
collections=["sentinel-2-l2a"],
|
| 243 |
+
intersects={"type": "Point", "coordinates": [aoi.lon, aoi.lat]},
|
| 244 |
+
datetime=f"{y}-{m}-01/{y}-{m}-28",
|
| 245 |
+
)
|
| 246 |
+
want_tile = aoi.tile.lstrip("T")
|
| 247 |
+
same_tile = [
|
| 248 |
+
it
|
| 249 |
+
for it in search.items()
|
| 250 |
+
if it.properties.get("s2:mgrs_tile") == want_tile and it.datetime is not None
|
| 251 |
+
]
|
| 252 |
+
if not same_tile:
|
| 253 |
+
raise RuntimeError(f"{aoi.id}: no S2 L2A item on tile {aoi.tile} in {y}-{m}")
|
| 254 |
+
|
| 255 |
+
cloud = lambda it: it.properties.get("eo:cloud_cover", 100.0) # noqa: E731
|
| 256 |
+
|
| 257 |
+
# 1) the confirmed day, if its swath covers the window
|
| 258 |
+
for it in sorted((x for x in same_tile if _day(x) == date), key=cloud):
|
| 259 |
+
if _coverage(it, aoi) >= COVER_MIN:
|
| 260 |
+
return it, date, False
|
| 261 |
+
|
| 262 |
+
# 2) fallback — any covering low-cloud scene that month; lowest cloud, then nearest target day
|
| 263 |
+
target = int(date[8:10])
|
| 264 |
+
covering = [
|
| 265 |
+
it for it in same_tile if cloud(it) <= CLOUD_MAX and _coverage(it, aoi) >= COVER_MIN
|
| 266 |
+
]
|
| 267 |
+
if not covering:
|
| 268 |
+
raise RuntimeError(
|
| 269 |
+
f"{aoi.id}: no covering low-cloud S2 scene near {date} on tile {aoi.tile}"
|
| 270 |
+
)
|
| 271 |
+
covering.sort(key=lambda it: (cloud(it), abs(int(_day(it)[8:10]) - target)))
|
| 272 |
+
chosen = covering[0]
|
| 273 |
+
return chosen, _day(chosen), True
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def _boa_offset(item: Any) -> float:
|
| 277 |
+
"""DN offset to subtract before ÷10000. Baseline ≥ 04.00 (post 2022-01-25) carries
|
| 278 |
+
``BOA_ADD_OFFSET = -1000``; OSCD predates it, so we harmonise every scene to that convention."""
|
| 279 |
+
baseline = item.properties.get("s2:processing_baseline")
|
| 280 |
+
try:
|
| 281 |
+
if baseline is not None and float(baseline) >= 4.0:
|
| 282 |
+
return 1000.0
|
| 283 |
+
except (TypeError, ValueError):
|
| 284 |
+
pass
|
| 285 |
+
# fallback on acquisition date if the baseline field is absent
|
| 286 |
+
dt = item.datetime
|
| 287 |
+
if dt is not None and dt.strftime("%Y-%m-%d") >= "2022-01-25":
|
| 288 |
+
return 1000.0
|
| 289 |
+
return 0.0
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def _window_from_center(ds: rasterio.io.DatasetReader, lon: float, lat: float, win: int) -> Window:
|
| 293 |
+
"""A ``win×win`` pixel window centred on (lon,lat), clamped inside the raster."""
|
| 294 |
+
xs, ys = warp_transform("EPSG:4326", ds.crs, [lon], [lat])
|
| 295 |
+
row, col = ds.index(xs[0], ys[0])
|
| 296 |
+
col_off = int(round(col - win / 2))
|
| 297 |
+
row_off = int(round(row - win / 2))
|
| 298 |
+
col_off = max(0, min(col_off, ds.width - win))
|
| 299 |
+
row_off = max(0, min(row_off, ds.height - win))
|
| 300 |
+
return Window(col_off, row_off, win, win)
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
def read_reflectance(item: Any, aoi: AOI) -> tuple[np.ndarray, tuple[float, float]]:
|
| 304 |
+
"""Read the AOI window for all four bands and return harmonised reflectance ``(4, win, win)``
|
| 305 |
+
plus the geographic ``(transform_a, origin)`` fingerprint used to assert co-registration."""
|
| 306 |
+
offset = _boa_offset(item)
|
| 307 |
+
bands: list[np.ndarray] = []
|
| 308 |
+
grid_fp: tuple[float, float] | None = None
|
| 309 |
+
win: Window | None = None
|
| 310 |
+
for band in BANDS:
|
| 311 |
+
href = item.assets[band].href # already pc-signed via sign_inplace modifier
|
| 312 |
+
with rasterio.open(href) as ds:
|
| 313 |
+
if win is None:
|
| 314 |
+
win = _window_from_center(ds, aoi.lon, aoi.lat, aoi.window)
|
| 315 |
+
grid_fp = (ds.transform.a, ds.transform.c + ds.transform.f)
|
| 316 |
+
dn = ds.read(1, window=win).astype(np.float32)
|
| 317 |
+
refl = np.clip((dn - offset) / 10000.0, 0.0, None)
|
| 318 |
+
bands.append(refl)
|
| 319 |
+
assert grid_fp is not None
|
| 320 |
+
return np.stack(bands, axis=0), grid_fp
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def stretch_rgb(
|
| 324 |
+
before_rgb: np.ndarray, after_rgb: np.ndarray, lo_pct: float = 2.0, hi_pct: float = 98.0
|
| 325 |
+
) -> tuple[np.ndarray, np.ndarray]:
|
| 326 |
+
"""Joint per-channel percentile stretch of two ``(3, H, W)`` reflectance stacks → uint8 HxWx3.
|
| 327 |
+
|
| 328 |
+
Shared bounds (computed over both dates) keep before/after radiometrically comparable, so the
|
| 329 |
+
swipe slider shows real change, not a stretch artefact."""
|
| 330 |
+
|
| 331 |
+
def to_uint8(rgb: np.ndarray, lo: np.ndarray, hi: np.ndarray) -> np.ndarray:
|
| 332 |
+
out = np.empty(rgb.shape, dtype=np.float32)
|
| 333 |
+
for c in range(3):
|
| 334 |
+
span = max(float(hi[c] - lo[c]), 1e-6)
|
| 335 |
+
out[c] = np.clip((rgb[c] - lo[c]) / span, 0.0, 1.0)
|
| 336 |
+
return (out * 255.0).round().astype(np.uint8).transpose(1, 2, 0)
|
| 337 |
+
|
| 338 |
+
both = np.concatenate([before_rgb, after_rgb], axis=1) # (3, 2H, W)
|
| 339 |
+
lo = np.array([np.percentile(both[c], lo_pct) for c in range(3)], dtype=np.float32)
|
| 340 |
+
hi = np.array([np.percentile(both[c], hi_pct) for c in range(3)], dtype=np.float32)
|
| 341 |
+
return to_uint8(before_rgb, lo, hi), to_uint8(after_rgb, lo, hi)
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
# -------------------------------------------------------------------------------------------------
|
| 345 |
+
# Bake
|
| 346 |
+
# -------------------------------------------------------------------------------------------------
|
| 347 |
+
def _overlay_data_url_to_png(data_url: str, path: Path) -> None:
|
| 348 |
+
"""Write the base64 PNG produced by ``_overlay_png`` to ``path``."""
|
| 349 |
+
_, _, b64 = data_url.partition(",")
|
| 350 |
+
path.write_bytes(base64.b64decode(b64))
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
def bake_aoi(cat: pystac_client.Client, model: OscdModel, aoi: AOI) -> tuple[dict, dict]:
|
| 354 |
+
"""Fetch, run the model offline, write the cache artefacts. Returns ``(manifest, pred)``."""
|
| 355 |
+
item_b, day_b, fb_b = find_item(cat, aoi, aoi.date_before)
|
| 356 |
+
item_a, day_a, fb_a = find_item(cat, aoi, aoi.date_after)
|
| 357 |
+
cloud_b = float(item_b.properties.get("eo:cloud_cover", 0.0))
|
| 358 |
+
cloud_a = float(item_a.properties.get("eo:cloud_cover", 0.0))
|
| 359 |
+
fb = lambda flag, want, got: ( # noqa: E731
|
| 360 |
+
f" (fallback from {want}: confirmed swath misses AOI)" if flag else ""
|
| 361 |
+
)
|
| 362 |
+
print(
|
| 363 |
+
f"[{aoi.id}] before={day_b} {item_b.id} ({cloud_b:.2f}%)"
|
| 364 |
+
f"{fb(fb_b, aoi.date_before, day_b)}\n"
|
| 365 |
+
f" after={day_a} {item_a.id} ({cloud_a:.2f}%){fb(fb_a, aoi.date_after, day_a)}"
|
| 366 |
+
)
|
| 367 |
+
|
| 368 |
+
refl_b, grid_b = read_reflectance(item_b, aoi)
|
| 369 |
+
refl_a, grid_a = read_reflectance(item_a, aoi)
|
| 370 |
+
# co-registration check: same MGRS tile => identical grid (pixel size + origin) within tolerance
|
| 371 |
+
if not (abs(grid_b[0] - grid_a[0]) < 1e-6 and abs(grid_b[1] - grid_a[1]) < 1.0):
|
| 372 |
+
raise RuntimeError(f"{aoi.id}: before/after grids differ ({grid_b} vs {grid_a})")
|
| 373 |
+
|
| 374 |
+
before_rgb_u8, after_rgb_u8 = stretch_rgb(refl_b[:3], refl_a[:3])
|
| 375 |
+
|
| 376 |
+
inp_b = model._standardize(refl_b)
|
| 377 |
+
inp_a = model._standardize(refl_a)
|
| 378 |
+
mask, prob, n_tiles, elapsed_ms = model.predict(inp_b, inp_a)
|
| 379 |
+
|
| 380 |
+
h, w = mask.shape
|
| 381 |
+
overlay_url = _overlay_png(mask, (w, h))
|
| 382 |
+
|
| 383 |
+
out = OUT_DIR / aoi.id
|
| 384 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 385 |
+
Image.fromarray(before_rgb_u8, mode="RGB").save(out / "before.png")
|
| 386 |
+
Image.fromarray(after_rgb_u8, mode="RGB").save(out / "after.png")
|
| 387 |
+
_overlay_data_url_to_png(overlay_url, out / "overlay.png")
|
| 388 |
+
|
| 389 |
+
changed_frac = float(mask.mean())
|
| 390 |
+
mean_conf_changed = float(prob[mask].mean()) if mask.any() else 0.0
|
| 391 |
+
stats = {
|
| 392 |
+
"changed_fraction": changed_frac,
|
| 393 |
+
"changed_percent": round(100.0 * changed_frac, 2),
|
| 394 |
+
"mean_confidence_changed": round(mean_conf_changed, 4),
|
| 395 |
+
"mean_confidence_overall": round(float(prob.mean()), 4),
|
| 396 |
+
"changed_pixels": int(mask.sum()),
|
| 397 |
+
"total_pixels": int(mask.size),
|
| 398 |
+
}
|
| 399 |
+
manifest = {
|
| 400 |
+
"id": aoi.id,
|
| 401 |
+
"title": aoi.title,
|
| 402 |
+
"description": aoi.description,
|
| 403 |
+
"source": "Sentinel-2 L2A · 10 m",
|
| 404 |
+
"tile": aoi.tile,
|
| 405 |
+
"center": [aoi.lon, aoi.lat],
|
| 406 |
+
"width": w,
|
| 407 |
+
"height": h,
|
| 408 |
+
"date_before": day_b,
|
| 409 |
+
"date_after": day_a,
|
| 410 |
+
"cloud_before": round(cloud_b, 2),
|
| 411 |
+
"cloud_after": round(cloud_a, 2),
|
| 412 |
+
"scene_before": item_b.id,
|
| 413 |
+
"scene_after": item_a.id,
|
| 414 |
+
}
|
| 415 |
+
prediction = {
|
| 416 |
+
"overlay_png": overlay_url,
|
| 417 |
+
"threshold": model.threshold,
|
| 418 |
+
"is_placeholder": False,
|
| 419 |
+
"stats": stats,
|
| 420 |
+
"elapsed_ms": round(elapsed_ms, 1),
|
| 421 |
+
"input_size": model.input_size,
|
| 422 |
+
"n_tiles": n_tiles,
|
| 423 |
+
"pair_id": aoi.id,
|
| 424 |
+
"model_id": MODEL_ID,
|
| 425 |
+
}
|
| 426 |
+
print(
|
| 427 |
+
f" baked {w}×{h}px · {n_tiles} tiles · changed {stats['changed_percent']}% · "
|
| 428 |
+
f"{elapsed_ms:.0f} ms"
|
| 429 |
+
)
|
| 430 |
+
return manifest, prediction
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
def main() -> None:
|
| 434 |
+
ap = argparse.ArgumentParser(description="Bake curated Sentinel-2 AOIs (build-time).")
|
| 435 |
+
ap.add_argument("--only", default=None, help="bake a single AOI id (default: all)")
|
| 436 |
+
ap.add_argument("--window", type=int, default=None, help="override crop side in px for --only")
|
| 437 |
+
ap.add_argument("--bundle", default=str(BUNDLE_DIR), help="OSCD ONNX bundle dir")
|
| 438 |
+
args = ap.parse_args()
|
| 439 |
+
|
| 440 |
+
aois = AOIS if args.only is None else [a for a in AOIS if a.id == args.only]
|
| 441 |
+
if not aois:
|
| 442 |
+
raise SystemExit(f"no AOI matches --only {args.only!r}")
|
| 443 |
+
if args.window is not None:
|
| 444 |
+
for a in aois:
|
| 445 |
+
a.window = args.window
|
| 446 |
+
|
| 447 |
+
model = OscdModel.load(Path(args.bundle))
|
| 448 |
+
cat = open_catalog()
|
| 449 |
+
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
| 450 |
+
|
| 451 |
+
# merge with any existing manifest/cache so `--only` bakes are incremental
|
| 452 |
+
manifest_path = OUT_DIR / "manifest.json"
|
| 453 |
+
cache_path = OUT_DIR / "_predictions.json"
|
| 454 |
+
manifest_by_id: dict[str, dict] = {}
|
| 455 |
+
if manifest_path.exists():
|
| 456 |
+
for e in json.loads(manifest_path.read_text()).get("pairs", []):
|
| 457 |
+
manifest_by_id[e["id"]] = e
|
| 458 |
+
cache: dict[str, dict] = json.loads(cache_path.read_text()) if cache_path.exists() else {}
|
| 459 |
+
|
| 460 |
+
for aoi in aois:
|
| 461 |
+
# STAC/COG network reads are occasionally flaky (RemoteDisconnected) — retry the whole AOI.
|
| 462 |
+
last: Exception | None = None
|
| 463 |
+
for attempt in range(1, 5):
|
| 464 |
+
try:
|
| 465 |
+
manifest, prediction = bake_aoi(cat, model, aoi)
|
| 466 |
+
manifest_by_id[aoi.id] = manifest
|
| 467 |
+
cache[aoi.id] = prediction
|
| 468 |
+
break
|
| 469 |
+
except Exception as exc: # noqa: BLE001 — build-time resilience over flaky network reads
|
| 470 |
+
if isinstance(exc, RuntimeError) and "no covering" in str(exc):
|
| 471 |
+
raise # a real coverage failure, not a transient network blip — surface it
|
| 472 |
+
last = exc
|
| 473 |
+
print(f" [{aoi.id}] attempt {attempt}/4 failed ({exc}); retrying…")
|
| 474 |
+
time.sleep(3 * attempt)
|
| 475 |
+
else:
|
| 476 |
+
raise RuntimeError(f"{aoi.id}: baking failed after retries") from last
|
| 477 |
+
|
| 478 |
+
ordered = [manifest_by_id[a.id] for a in AOIS if a.id in manifest_by_id]
|
| 479 |
+
manifest_path.write_text(json.dumps({"pairs": ordered}, indent=2))
|
| 480 |
+
cache_path.write_text(json.dumps(cache))
|
| 481 |
+
print(f"\nwrote {manifest_path.relative_to(_REPO)} ({len(ordered)} AOIs) + {cache_path.name}")
|
| 482 |
+
|
| 483 |
+
|
| 484 |
+
if __name__ == "__main__":
|
| 485 |
+
main()
|
backend/data/sentinel2/_predictions.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
backend/data/sentinel2/beijing_daxing/after.png
ADDED
|
Git LFS Details
|
backend/data/sentinel2/beijing_daxing/before.png
ADDED
|
Git LFS Details
|
backend/data/sentinel2/beijing_daxing/overlay.png
ADDED
|
backend/data/sentinel2/bhadla_solar/after.png
ADDED
|
Git LFS Details
|
backend/data/sentinel2/bhadla_solar/before.png
ADDED
|
Git LFS Details
|
backend/data/sentinel2/bhadla_solar/overlay.png
ADDED
|
backend/data/sentinel2/dubai_deira/after.png
ADDED
|
Git LFS Details
|
backend/data/sentinel2/dubai_deira/before.png
ADDED
|
Git LFS Details
|
backend/data/sentinel2/dubai_deira/overlay.png
ADDED
|
backend/data/sentinel2/egypt_newcapital/after.png
ADDED
|
Git LFS Details
|
backend/data/sentinel2/egypt_newcapital/before.png
ADDED
|
Git LFS Details
|
backend/data/sentinel2/egypt_newcapital/overlay.png
ADDED
|
backend/data/sentinel2/gerd_reservoir/after.png
ADDED
|
Git LFS Details
|
backend/data/sentinel2/gerd_reservoir/before.png
ADDED
|
Git LFS Details
|
backend/data/sentinel2/gerd_reservoir/overlay.png
ADDED
|
backend/data/sentinel2/manifest.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"pairs": [
|
| 3 |
+
{
|
| 4 |
+
"id": "dubai_deira",
|
| 5 |
+
"title": "Dubai \u2014 Deira Islands reclamation",
|
| 6 |
+
"description": "Land reclaimed from the Persian Gulf: new coastline and island fill off Deira.",
|
| 7 |
+
"source": "Sentinel-2 L2A \u00b7 10 m",
|
| 8 |
+
"tile": "T40RCP",
|
| 9 |
+
"center": [
|
| 10 |
+
55.34,
|
| 11 |
+
25.3
|
| 12 |
+
],
|
| 13 |
+
"width": 1024,
|
| 14 |
+
"height": 1024,
|
| 15 |
+
"date_before": "2016-03-20",
|
| 16 |
+
"date_after": "2023-04-28",
|
| 17 |
+
"cloud_before": 2.66,
|
| 18 |
+
"cloud_after": 0.03,
|
| 19 |
+
"scene_before": "S2A_MSIL2A_20160320T064602_R020_T40RCP_20210528T170644",
|
| 20 |
+
"scene_after": "S2B_MSIL2A_20230428T064629_R020_T40RCP_20230428T123139"
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"id": "gerd_reservoir",
|
| 24 |
+
"title": "Grand Ethiopian Renaissance Dam \u2014 reservoir filling",
|
| 25 |
+
"description": "The Blue Nile reservoir behind the GERD fills \u2014 open water where there was valley.",
|
| 26 |
+
"source": "Sentinel-2 L2A \u00b7 10 m",
|
| 27 |
+
"tile": "T36PYT",
|
| 28 |
+
"center": [
|
| 29 |
+
35.09,
|
| 30 |
+
11.21
|
| 31 |
+
],
|
| 32 |
+
"width": 1536,
|
| 33 |
+
"height": 1536,
|
| 34 |
+
"date_before": "2020-02-14",
|
| 35 |
+
"date_after": "2023-12-25",
|
| 36 |
+
"cloud_before": 0.01,
|
| 37 |
+
"cloud_after": 0.02,
|
| 38 |
+
"scene_before": "S2B_MSIL2A_20200214T075959_R035_T36PYT_20201002T000846",
|
| 39 |
+
"scene_after": "S2B_MSIL2A_20231225T080249_R035_T36PYT_20231225T204542"
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"id": "beijing_daxing",
|
| 43 |
+
"title": "Beijing Daxing International Airport",
|
| 44 |
+
"description": "A greenfield mega-airport built from farmland \u2014 terminal, aprons and runways appear.",
|
| 45 |
+
"source": "Sentinel-2 L2A \u00b7 10 m",
|
| 46 |
+
"tile": "T50SMJ",
|
| 47 |
+
"center": [
|
| 48 |
+
116.41,
|
| 49 |
+
39.51
|
| 50 |
+
],
|
| 51 |
+
"width": 1024,
|
| 52 |
+
"height": 1024,
|
| 53 |
+
"date_before": "2016-10-10",
|
| 54 |
+
"date_after": "2020-10-04",
|
| 55 |
+
"cloud_before": 0.83,
|
| 56 |
+
"cloud_after": 0.95,
|
| 57 |
+
"scene_before": "S2A_MSIL2A_20161010T031622_R075_T50SMJ_20210213T091241",
|
| 58 |
+
"scene_after": "S2B_MSIL2A_20201004T030559_R075_T50SMJ_20201006T063013"
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"id": "bhadla_solar",
|
| 62 |
+
"title": "Bhadla Solar Park, Rajasthan",
|
| 63 |
+
"description": "One of the world's largest solar parks spreads across the Thar desert.",
|
| 64 |
+
"source": "Sentinel-2 L2A \u00b7 10 m",
|
| 65 |
+
"tile": "T42RYR",
|
| 66 |
+
"center": [
|
| 67 |
+
71.91,
|
| 68 |
+
27.53
|
| 69 |
+
],
|
| 70 |
+
"width": 1280,
|
| 71 |
+
"height": 1280,
|
| 72 |
+
"date_before": "2017-12-17",
|
| 73 |
+
"date_after": "2021-12-16",
|
| 74 |
+
"cloud_before": 0.01,
|
| 75 |
+
"cloud_after": 0.39,
|
| 76 |
+
"scene_before": "S2B_MSIL2A_20171217T055229_R048_T42RYR_20201014T144207",
|
| 77 |
+
"scene_after": "S2B_MSIL2A_20211216T055239_R048_T42RYR_20211216T200146"
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"id": "egypt_newcapital",
|
| 81 |
+
"title": "New Administrative Capital, Egypt",
|
| 82 |
+
"description": "A new capital city rises from open desert east of Cairo.",
|
| 83 |
+
"source": "Sentinel-2 L2A \u00b7 10 m",
|
| 84 |
+
"tile": "T36RUU",
|
| 85 |
+
"center": [
|
| 86 |
+
31.75,
|
| 87 |
+
30.01
|
| 88 |
+
],
|
| 89 |
+
"width": 1280,
|
| 90 |
+
"height": 1280,
|
| 91 |
+
"date_before": "2016-08-27",
|
| 92 |
+
"date_after": "2023-08-26",
|
| 93 |
+
"cloud_before": 0.67,
|
| 94 |
+
"cloud_after": 0.0,
|
| 95 |
+
"scene_before": "S2A_MSIL2A_20160827T083012_R021_T36RUU_20210212T183504",
|
| 96 |
+
"scene_after": "S2B_MSIL2A_20230826T082609_R021_T36RUU_20230826T152914"
|
| 97 |
+
}
|
| 98 |
+
]
|
| 99 |
+
}
|
backend/sentinel2.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Curated Sentinel-2 (Track-B) AOI registry for the ``/sentinel2`` demo tab (M5, PRD §6.3/§10).
|
| 2 |
+
|
| 3 |
+
A curated Sentinel-2 *AOI* is a real-world location with large, obvious change visible even at 10 m
|
| 4 |
+
(reclamation, a filling reservoir, an airport built from farmland, a solar park, a desert city). Its
|
| 5 |
+
before/after imagery and change prediction are **baked offline** by ``build_sentinel2.py`` and
|
| 6 |
+
served here straight from the cache — **no runtime inference, no runtime STAC, no GPU**, exactly
|
| 7 |
+
like the aerial curated mode. The runtime image stays STAC-free (no ``pystac``/``rasterio`` deps).
|
| 8 |
+
|
| 9 |
+
The registry reads ``<data_dir>/manifest.json`` (per-AOI metadata: title, MGRS tile, centre, the
|
| 10 |
+
acquisition dates + cloud cover) and the baked ``<data_dir>/_predictions.json`` (the same cache
|
| 11 |
+
schema ``inference.py.predict`` returns), and exposes both to the API.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import json
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
from typing import Any
|
| 19 |
+
|
| 20 |
+
from PIL import Image
|
| 21 |
+
|
| 22 |
+
_IMAGE_KINDS = ("before", "after", "overlay")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class Sentinel2Registry:
|
| 26 |
+
"""Reads the baked Sentinel-2 manifest + prediction cache and serves them (cache only)."""
|
| 27 |
+
|
| 28 |
+
def __init__(self, data_dir: str | Path) -> None:
|
| 29 |
+
self.data_dir = Path(data_dir)
|
| 30 |
+
self.aois: dict[str, dict[str, Any]] = {}
|
| 31 |
+
self.predictions: dict[str, dict[str, Any]] = {}
|
| 32 |
+
self.reload()
|
| 33 |
+
|
| 34 |
+
def reload(self) -> None:
|
| 35 |
+
self.aois.clear()
|
| 36 |
+
self.predictions.clear()
|
| 37 |
+
manifest = self.data_dir / "manifest.json"
|
| 38 |
+
if manifest.exists():
|
| 39 |
+
for entry in json.loads(manifest.read_text()).get("pairs", []):
|
| 40 |
+
aid = str(entry["id"])
|
| 41 |
+
if (self.data_dir / aid / "before.png").exists():
|
| 42 |
+
self.aois[aid] = entry
|
| 43 |
+
cache = self.data_dir / "_predictions.json"
|
| 44 |
+
if cache.exists():
|
| 45 |
+
try:
|
| 46 |
+
data = json.loads(cache.read_text())
|
| 47 |
+
except json.JSONDecodeError:
|
| 48 |
+
data = {}
|
| 49 |
+
for aid, pred in data.items():
|
| 50 |
+
if aid in self.aois:
|
| 51 |
+
self.predictions[aid] = pred
|
| 52 |
+
|
| 53 |
+
def list(self) -> list[dict[str, Any]]:
|
| 54 |
+
"""Per-AOI metadata + the baked prediction summary (stats/threshold/tiles), minus the heavy
|
| 55 |
+
``overlay_png`` data URL — the overlay is served as a PNG file via :meth:`image_path`."""
|
| 56 |
+
out = []
|
| 57 |
+
for aid, entry in self.aois.items():
|
| 58 |
+
pred = self.predictions.get(aid, {})
|
| 59 |
+
out.append(
|
| 60 |
+
{
|
| 61 |
+
"id": aid,
|
| 62 |
+
"title": entry.get("title", aid),
|
| 63 |
+
"description": entry.get("description", ""),
|
| 64 |
+
"source": entry.get("source", "Sentinel-2 L2A · 10 m"),
|
| 65 |
+
"tile": entry.get("tile", ""),
|
| 66 |
+
"center": entry.get("center"),
|
| 67 |
+
"width": entry.get("width"),
|
| 68 |
+
"height": entry.get("height"),
|
| 69 |
+
"date_before": entry.get("date_before"),
|
| 70 |
+
"date_after": entry.get("date_after"),
|
| 71 |
+
"cloud_before": entry.get("cloud_before"),
|
| 72 |
+
"cloud_after": entry.get("cloud_after"),
|
| 73 |
+
"model_id": pred.get("model_id", ""),
|
| 74 |
+
"threshold": pred.get("threshold"),
|
| 75 |
+
"is_placeholder": pred.get("is_placeholder", False),
|
| 76 |
+
"n_tiles": pred.get("n_tiles"),
|
| 77 |
+
"input_size": pred.get("input_size"),
|
| 78 |
+
"elapsed_ms": pred.get("elapsed_ms"),
|
| 79 |
+
"stats": pred.get("stats", {}),
|
| 80 |
+
}
|
| 81 |
+
)
|
| 82 |
+
return out
|
| 83 |
+
|
| 84 |
+
def image_path(self, aoi_id: str, which: str) -> Path:
|
| 85 |
+
if aoi_id not in self.aois:
|
| 86 |
+
raise KeyError(aoi_id)
|
| 87 |
+
if which not in _IMAGE_KINDS:
|
| 88 |
+
raise ValueError(which)
|
| 89 |
+
return self.data_dir / aoi_id / f"{which}.png"
|
| 90 |
+
|
| 91 |
+
def dimensions(self, aoi_id: str) -> tuple[int, int]:
|
| 92 |
+
with Image.open(self.image_path(aoi_id, "before")) as im:
|
| 93 |
+
return im.size
|
frontend/src/App.tsx
CHANGED
|
@@ -2,9 +2,10 @@ import { useEffect, useState } from "react";
|
|
| 2 |
import { api } from "./api";
|
| 3 |
import CompareView from "./components/CompareView";
|
| 4 |
import ModelCard from "./components/ModelCard";
|
| 5 |
-
import
|
|
|
|
| 6 |
|
| 7 |
-
type Tab = "curated" | "card";
|
| 8 |
|
| 9 |
// friendly labels for the served bundle ids (falls back to the raw id)
|
| 10 |
const MODEL_LABELS: Record<string, string> = {
|
|
@@ -38,6 +39,7 @@ export default function App() {
|
|
| 38 |
const [tab, setTab] = useState<Tab>("curated");
|
| 39 |
const [models, setModels] = useState<ModelSummary[]>([]);
|
| 40 |
const [pairs, setPairs] = useState<CuratedPair[]>([]);
|
|
|
|
| 41 |
const [modelId, setModelId] = useState<string>("");
|
| 42 |
const [pairId, setPairId] = useState<string>("");
|
| 43 |
const [result, setResult] = useState<PredictResult | null>(null);
|
|
@@ -56,6 +58,11 @@ export default function App() {
|
|
| 56 |
if (p.length) setPairId(p[0].id);
|
| 57 |
})
|
| 58 |
.catch((e) => setError(String(e)));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
}, []);
|
| 60 |
|
| 61 |
useEffect(() => {
|
|
@@ -95,13 +102,27 @@ export default function App() {
|
|
| 95 |
<div>
|
| 96 |
<h1>Satellite Change Detection</h1>
|
| 97 |
<div className="sub">
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
</div>
|
| 100 |
</div>
|
| 101 |
</div>
|
| 102 |
<nav className="nav">
|
| 103 |
<button className={`tab ${tab === "curated" ? "on" : ""}`} onClick={() => setTab("curated")}>
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
</button>
|
| 106 |
<button className={`tab ${tab === "card" ? "on" : ""}`} onClick={() => setTab("card")}>
|
| 107 |
Model card
|
|
@@ -127,6 +148,8 @@ export default function App() {
|
|
| 127 |
|
| 128 |
{tab === "card" ? (
|
| 129 |
<ModelCard />
|
|
|
|
|
|
|
| 130 |
) : (
|
| 131 |
<div className="body">
|
| 132 |
<aside className="rail">
|
|
|
|
| 2 |
import { api } from "./api";
|
| 3 |
import CompareView from "./components/CompareView";
|
| 4 |
import ModelCard from "./components/ModelCard";
|
| 5 |
+
import Sentinel2View from "./components/Sentinel2View";
|
| 6 |
+
import type { CuratedPair, ModelSummary, PredictResult, Sentinel2AOI } from "./types";
|
| 7 |
|
| 8 |
+
type Tab = "curated" | "sentinel2" | "card";
|
| 9 |
|
| 10 |
// friendly labels for the served bundle ids (falls back to the raw id)
|
| 11 |
const MODEL_LABELS: Record<string, string> = {
|
|
|
|
| 39 |
const [tab, setTab] = useState<Tab>("curated");
|
| 40 |
const [models, setModels] = useState<ModelSummary[]>([]);
|
| 41 |
const [pairs, setPairs] = useState<CuratedPair[]>([]);
|
| 42 |
+
const [s2, setS2] = useState<Sentinel2AOI[]>([]);
|
| 43 |
const [modelId, setModelId] = useState<string>("");
|
| 44 |
const [pairId, setPairId] = useState<string>("");
|
| 45 |
const [result, setResult] = useState<PredictResult | null>(null);
|
|
|
|
| 58 |
if (p.length) setPairId(p[0].id);
|
| 59 |
})
|
| 60 |
.catch((e) => setError(String(e)));
|
| 61 |
+
// Sentinel-2 AOIs load independently; a missing S2 cache must not break the aerial tab.
|
| 62 |
+
api
|
| 63 |
+
.sentinel2()
|
| 64 |
+
.then(setS2)
|
| 65 |
+
.catch((e) => console.warn("sentinel2 list failed:", e));
|
| 66 |
}, []);
|
| 67 |
|
| 68 |
useEffect(() => {
|
|
|
|
| 102 |
<div>
|
| 103 |
<h1>Satellite Change Detection</h1>
|
| 104 |
<div className="sub">
|
| 105 |
+
{tab === "sentinel2" ? (
|
| 106 |
+
<>
|
| 107 |
+
TRACK B · SENTINEL-2 10 M/PX · <b>ONNX · CPU</b>
|
| 108 |
+
</>
|
| 109 |
+
) : (
|
| 110 |
+
<>
|
| 111 |
+
TRACK A · AERIAL 0.5 M/PX · <b>ONNX · CPU</b>
|
| 112 |
+
</>
|
| 113 |
+
)}
|
| 114 |
</div>
|
| 115 |
</div>
|
| 116 |
</div>
|
| 117 |
<nav className="nav">
|
| 118 |
<button className={`tab ${tab === "curated" ? "on" : ""}`} onClick={() => setTab("curated")}>
|
| 119 |
+
Aerial
|
| 120 |
+
</button>
|
| 121 |
+
<button
|
| 122 |
+
className={`tab ${tab === "sentinel2" ? "on" : ""}`}
|
| 123 |
+
onClick={() => setTab("sentinel2")}
|
| 124 |
+
>
|
| 125 |
+
Sentinel-2
|
| 126 |
</button>
|
| 127 |
<button className={`tab ${tab === "card" ? "on" : ""}`} onClick={() => setTab("card")}>
|
| 128 |
Model card
|
|
|
|
| 148 |
|
| 149 |
{tab === "card" ? (
|
| 150 |
<ModelCard />
|
| 151 |
+
) : tab === "sentinel2" ? (
|
| 152 |
+
<Sentinel2View aois={s2} />
|
| 153 |
) : (
|
| 154 |
<div className="body">
|
| 155 |
<aside className="rail">
|
frontend/src/api.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import type { CuratedPair, ModelSummary, PredictResult } from "./types";
|
| 2 |
|
| 3 |
// Same-origin in production (FastAPI serves this build); vite dev proxies /api to :7860.
|
| 4 |
const BASE = "";
|
|
@@ -28,4 +28,9 @@ export const api = {
|
|
| 28 |
},
|
| 29 |
imageUrl: (pairId: string, which: "before" | "after") =>
|
| 30 |
`${BASE}/api/curated/${pairId}/${which}.png`,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
};
|
|
|
|
| 1 |
+
import type { CuratedPair, ModelSummary, PredictResult, Sentinel2AOI } from "./types";
|
| 2 |
|
| 3 |
// Same-origin in production (FastAPI serves this build); vite dev proxies /api to :7860.
|
| 4 |
const BASE = "";
|
|
|
|
| 28 |
},
|
| 29 |
imageUrl: (pairId: string, which: "before" | "after") =>
|
| 30 |
`${BASE}/api/curated/${pairId}/${which}.png`,
|
| 31 |
+
|
| 32 |
+
// Sentinel-2 (Track B): baked results only — a plain GET list, no predict call.
|
| 33 |
+
sentinel2: () => getJSON<Sentinel2AOI[]>("/api/sentinel2"),
|
| 34 |
+
sentinel2ImageUrl: (aoiId: string, which: "before" | "after" | "overlay") =>
|
| 35 |
+
`${BASE}/api/sentinel2/${aoiId}/${which}.png`,
|
| 36 |
};
|
frontend/src/assets/land-110m.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-59.57,-80.04],[-59.87,-80.55],[-60.16,-81.0],[-62.26,-80.86],[-64.49,-80.92],[-65.74,-80.59],[-65.74,-80.55],[-66.29,-80.26],[-64.04,-80.29],[-61.88,-80.39],[-61.14,-79.98],[-60.61,-79.63],[-59.57,-80.04]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-159.21,-79.5],[-161.13,-79.63],[-162.44,-79.28],[-163.03,-78.93],[-163.07,-78.87],[-163.71,-78.6],[-163.71,-78.6],[-163.11,-78.22],[-161.25,-78.38],[-160.25,-78.69],[-159.48,-79.05],[-159.21,-79.5]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-45.15,-78.05],[-43.92,-78.48],[-43.49,-79.09],[-43.37,-79.52],[-43.33,-80.03],[-44.88,-80.34],[-46.51,-80.59],[-48.39,-80.83],[-50.48,-81.03],[-52.85,-80.97],[-54.16,-80.63],[-53.99,-80.22],[-51.85,-79.95],[-50.99,-79.61],[-50.36,-79.18],[-49.91,-78.81],[-49.31,-78.46],[-48.66,-78.05],[-48.66,-78.05],[-48.15,-78.05],[-46.66,-77.83],[-45.15,-78.05]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-121.21,-73.5],[-119.92,-73.66],[-118.72,-73.48],[-119.29,-73.83],[-120.23,-74.09],[-121.62,-74.01],[-122.62,-73.66],[-122.62,-73.66],[-122.41,-73.32],[-121.21,-73.5]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-125.56,-73.48],[-124.03,-73.87],[-124.62,-73.83],[-125.91,-73.74],[-127.28,-73.46],[-127.28,-73.46],[-126.56,-73.25],[-125.56,-73.48]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-98.98,-71.93],[-97.88,-72.07],[-96.79,-71.95],[-96.2,-72.52],[-96.98,-72.44],[-98.2,-72.48],[-99.43,-72.44],[-100.78,-72.5],[-101.8,-72.31],[-102.33,-71.89],[-102.33,-71.89],[-101.7,-71.72],[-100.43,-71.85],[-98.98,-71.93]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-68.45,-70.96],[-68.33,-71.41],[-68.51,-71.8],[-68.78,-72.17],[-69.96,-72.31],[-71.08,-72.5],[-72.39,-72.48],[-71.9,-72.09],[-73.07,-72.23],[-74.19,-72.37],[-74.95,-72.07],[-75.01,-71.66],[-73.92,-71.27],[-73.92,-71.27],[-73.23,-71.15],[-72.07,-71.19],[-71.78,-70.68],[-71.72,-70.31],[-71.74,-69.51],[-71.17,-69.04],[-70.25,-68.88],[-69.72,-69.25],[-69.49,-69.62],[-69.06,-70.07],[-68.73,-70.51],[-68.45,-70.96]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.61,-64.15],[-59.05,-64.37],[-59.79,-64.21],[-60.61,-64.31],[-61.3,-64.54],[-62.02,-64.8],[-62.51,-65.09],[-62.65,-65.48],[-62.59,-65.86],[-62.12,-66.19],[-62.81,-66.43],[-63.75,-66.5],[-64.29,-66.84],[-64.88,-67.15],[-65.51,-67.58],[-65.67,-67.95],[-65.31,-68.37],[-64.78,-68.68],[-63.96,-68.91],[-63.2,-69.23],[-62.79,-69.62],[-62.57,-69.99],[-62.28,-70.38],[-61.81,-70.72],[-61.51,-71.09],[-61.38,-72.01],[-61.08,-72.38],[-61.0,-72.77],[-60.69,-73.17],[-60.83,-73.7],[-61.38,-74.11],[-61.96,-74.44],[-63.3,-74.58],[-63.75,-74.93],[-64.35,-75.26],[-65.86,-75.64],[-67.19,-75.79],[-68.45,-76.01],[-69.8,-76.22],[-70.6,-76.63],[-72.21,-76.67],[-73.97,-76.63],[-75.56,-76.71],[-77.24,-76.71],[-76.93,-77.1],[-75.4,-77.28],[-74.28,-77.56],[-73.66,-77.91],[-74.77,-78.22],[-76.5,-78.12],[-77.93,-78.38],[-77.98,-78.79],[-78.02,-79.18],[-76.85,-79.51],[-76.63,-79.89],[-75.36,-80.26],[-73.24,-80.42],[-71.44,-80.69],[-70.01,-81.0],[-68.19,-81.32],[-65.7,-81.47],[-63.26,-81.75],[-61.55,-82.04],[-59.69,-82.38],[-58.71,-82.85],[-58.22,-83.22],[-57.01,-82.87],[-55.36,-82.57],[-53.62,-82.26],[-51.54,-82.0],[-49.76,-81.73],[-47.27,-81.71],[-44.83,-81.85],[-42.81,-82.08],[-42.16,-81.65],[-40.77,-81.36],[-38.24,-81.34],[-36.27,-81.12],[-34.39,-80.91],[-32.31,-80.77],[-30.1,-80.59],[-28.55,-80.34],[-29.25,-79.99],[-29.69,-79.63],[-29.69,-79.26],[-31.62,-79.3],[-33.68,-79.46],[-35.64,-79.46],[-35.91,-79.08],[-35.78,-78.34],[-35.33,-78.12],[-33.9,-77.89],[-32.21,-77.65],[-31.0,-77.36],[-29.78,-77.07],[-28.88,-76.67],[-27.51,-76.5],[-26.16,-76.36],[-25.47,-76.28],[-23.93,-76.24],[-22.46,-76.11],[-21.22,-75.91],[-20.01,-75.67],[-18.91,-75.44],[-17.52,-75.13],[-16.64,-74.79],[-15.7,-74.5],[-15.41,-74.11],[-16.47,-73.87],[-16.11,-73.46],[-15.45,-73.15],[-14.41,-72.95],[-13.31,-72.72],[-12.29,-72.4],[-11.51,-72.01],[-11.02,-71.54],[-10.3,-71.27],[-9.1,-71.32],[-8.61,-71.66],[-7.42,-71.7],[-7.38,-71.32],[-6.87,-70.93],[-5.79,-71.03],[-5.54,-71.4],[-4.34,-71.46],[-3.05,-71.29],[-1.8,-71.17],[-0.66,-71.23],[-0.23,-71.64],[0.87,-71.3],[1.89,-71.13],[3.02,-70.99],[4.14,-70.85],[5.16,-70.62],[6.27,-70.46],[7.14,-70.25],[7.74,-69.89],[8.49,-70.15],[9.53,-70.01],[10.25,-70.48],[10.82,-70.83],[11.95,-70.64],[12.4,-70.25],[13.42,-69.97],[14.73,-70.03],[15.13,-70.4],[15.95,-70.03],[17.03,-69.91],[18.2,-69.87],[19.26,-69.89],[20.38,-70.01],[21.45,-70.07],[21.92,-70.4],[22.57,-70.7],[23.67,-70.52],[24.84,-70.48],[25.98,-70.48],[27.09,-70.46],[28.09,-70.32],[29.15,-70.21],[30.03,-69.93],[30.97,-69.76],[31.99,-69.66],[32.75,-69.38],[33.3,-68.84],[33.87,-68.5],[34.91,-68.66],[35.3,-69.01],[36.16,-69.25],[37.2,-69.17],[37.91,-69.52],[38.65,-69.78],[39.67,-69.54],[40.02,-69.11],[40.92,-68.93],[41.96,-68.6],[42.94,-68.46],[44.11,-68.27],[44.9,-68.05],[45.72,-67.82],[46.5,-67.6],[47.44,-67.72],[48.34,-67.37],[48.99,-67.09],[49.93,-67.11],[50.75,-66.88],[50.95,-66.52],[51.79,-66.25],[52.61,-66.05],[53.61,-65.9],[54.53,-65.82],[55.41,-65.88],[56.36,-65.97],[57.16,-66.25],[57.26,-66.68],[58.14,-67.01],[58.74,-67.29],[59.94,-67.41],[60.61,-67.68],[61.43,-67.95],[62.39,-68.01],[63.19,-67.82],[64.05,-67.41],[64.99,-67.62],[65.97,-67.74],[66.91,-67.86],[67.89,-67.93],[68.89,-67.93],[69.71,-68.97],[69.67,-69.23],[69.56,-69.68],[68.6,-69.93],[67.81,-70.31],[67.95,-70.7],[69.07,-70.68],[68.93,-71.07],[68.42,-71.44],[67.95,-71.85],[68.71,-72.17],[69.87,-72.26],[71.02,-72.09],[71.57,-71.7],[71.91,-71.32],[72.45,-71.01],[73.08,-70.72],[73.34,-70.36],[73.86,-69.87],[74.49,-69.78],[75.63,-69.74],[76.63,-69.62],[77.64,-69.46],[78.13,-69.07],[78.43,-68.7],[79.11,-68.33],[80.09,-68.07],[80.94,-67.88],[81.48,-67.54],[82.05,-67.37],[82.78,-67.21],[83.78,-67.31],[84.68,-67.21],[85.66,-67.09],[86.75,-67.15],[87.48,-66.88],[87.99,-66.21],[88.36,-66.48],[88.83,-66.95],[89.67,-67.15],[90.63,-67.23],[91.59,-67.11],[92.61,-67.19],[93.55,-67.21],[94.18,-67.11],[95.02,-67.17],[95.78,-67.39],[96.68,-67.25],[97.76,-67.25],[98.68,-67.11],[99.72,-67.25],[100.38,-66.92],[100.89,-66.58],[101.58,-66.31],[102.83,-65.56],[103.48,-65.7],[104.24,-65.97],[104.91,-66.33],[106.18,-66.93],[107.16,-66.95],[108.08,-66.95],[109.16,-66.84],[110.24,-66.7],[111.06,-66.43],[111.74,-66.13],[112.86,-66.09],[113.6,-65.88],[114.39,-66.07],[114.9,-66.39],[115.6,-66.7],[116.7,-66.66],[117.38,-66.92],[118.58,-67.17],[119.83,-67.27],[120.87,-67.19],[121.65,-66.88],[122.32,-66.56],[123.22,-66.48],[124.12,-66.62],[125.16,-66.72],[126.1,-66.56],[127.0,-66.56],[127.88,-66.66],[128.8,-66.76],[129.7,-66.58],[130.78,-66.43],[131.8,-66.39],[132.94,-66.39],[133.86,-66.29],[134.76,-66.21],[135.03,-65.72],[135.07,-65.31],[135.7,-65.58],[135.87,-66.03],[136.21,-66.45],[136.62,-66.78],[137.46,-66.95],[138.6,-66.9],[139.91,-66.88],[140.81,-66.82],[142.12,-66.82],[143.06,-66.8],[144.37,-66.84],[145.49,-66.92],[146.2,-67.23],[146.0,-67.6],[146.65,-67.9],[147.72,-68.13],[148.84,-68.39],[150.13,-68.56],[151.48,-68.72],[152.5,-68.87],[153.64,-68.89],[154.28,-68.56],[155.17,-68.84],[155.93,-69.15],[156.81,-69.38],[158.03,-69.48],[159.18,-69.6],[159.67,-69.99],[160.81,-70.23],[161.57,-70.58],[162.69,-70.74],[163.84,-70.72],[164.92,-70.78],[166.11,-70.76],[167.31,-70.83],[168.43,-70.97],[169.46,-71.21],[170.5,-71.4],[171.21,-71.7],[171.09,-72.09],[170.56,-72.44],[170.11,-72.89],[169.76,-73.24],[169.29,-73.66],[167.98,-73.81],[167.39,-74.17],[166.09,-74.38],[165.64,-74.77],[164.96,-75.15],[164.23,-75.46],[163.82,-75.87],[163.57,-76.24],[163.47,-76.69],[163.49,-77.07],[164.06,-77.46],[164.27,-77.83],[164.74,-78.18],[166.6,-78.32],[167.0,-78.75],[165.19,-78.91],[163.67,-79.12],[161.77,-79.16],[160.92,-79.73],[160.75,-80.2],[160.32,-80.57],[159.79,-80.95],[161.12,-81.28],[161.63,-81.69],[162.49,-82.06],[163.71,-82.4],[165.1,-82.71],[166.6,-83.02],[168.9,-83.34],[169.4,-83.83],[172.28,-84.04],[172.48,-84.12],[173.22,-84.41],[175.99,-84.16],[178.28,-84.47],[180,-84.71],[180,-90],[-180,-90],[-180,-84.71],[-179.94,-84.72],[-179.06,-84.14],[-177.26,-84.45],[-177.14,-84.42],[-176.86,-84.33],[-176.52,-84.23],[-176.23,-84.14],[-176.08,-84.1],[-175.93,-84.1],[-175.83,-84.12],[-174.38,-84.53],[-173.12,-84.12],[-172.89,-84.06],[-169.95,-83.88],[-169.0,-84.12],[-168.53,-84.24],[-167.02,-84.57],[-164.18,-84.83],[-161.93,-85.14],[-158.07,-85.37],[-155.19,-85.1],[-150.94,-85.3],[-148.53,-85.61],[-145.89,-85.32],[-143.11,-85.04],[-142.89,-84.57],[-146.83,-84.53],[-150.06,-84.3],[-150.9,-83.9],[-153.59,-83.69],[-153.41,-83.24],[-153.04,-82.83],[-152.67,-82.45],[-152.86,-82.04],[-154.53,-81.77],[-155.29,-81.42],[-156.84,-81.1],[-154.41,-81.16],[-152.1,-81.0],[-150.65,-81.34],[-148.87,-81.04],[-147.22,-80.67],[-146.42,-80.34],[-146.77,-79.93],[-148.06,-79.65],[-149.53,-79.36],[-151.59,-79.3],[-153.39,-79.16],[-155.33,-79.06],[-155.98,-78.69],[-157.27,-78.38],[-158.05,-78.03],[-158.37,-76.89],[-157.88,-76.99],[-156.97,-77.3],[-155.33,-77.2],[-153.74,-77.07],[-152.92,-77.5],[-151.33,-77.4],[-150.0,-77.18],[-148.75,-76.91],[-147.61,-76.58],[-146.1,-76.48],[-146.14,-76.11],[-146.5,-75.73],[-146.2,-75.38],[-144.91,-75.2],[-144.32,-75.54],[-142.79,-75.34],[-141.64,-75.09],[-140.21,-75.07],[-138.86,-74.97],[-137.51,-74.73],[-136.43,-74.52],[-135.21,-74.3],[-134.43,-74.36],[-133.75,-74.44],[-132.26,-74.3],[-130.93,-74.48],[-129.55,-74.46],[-128.24,-74.32],[-126.89,-74.42],[-125.4,-74.52],[-124.01,-74.48],[-122.56,-74.5],[-121.07,-74.52],[-119.7,-74.48],[-118.68,-74.19],[-117.47,-74.03],[-116.22,-74.24],[-115.02,-74.07],[-113.94,-73.71],[-113.3,-74.03],[-112.95,-74.38],[-112.3,-74.71],[-111.26,-74.42],[-110.07,-74.79],[-108.71,-74.91],[-107.56,-75.18],[-106.15,-75.13],[-104.88,-74.95],[-103.37,-74.99],[-102.02,-75.13],[-100.65,-75.3],[-100.12,-74.87],[-100.76,-74.54],[-101.25,-74.19],[-102.55,-74.11],[-103.11,-73.73],[-103.33,-73.36],[-103.68,-72.62],[-102.92,-72.75],[-101.61,-72.81],[-100.31,-72.75],[-99.14,-72.91],[-98.12,-73.21],[-97.69,-73.56],[-96.34,-73.62],[-95.04,-73.48],[-93.67,-73.28],[-92.44,-73.17],[-91.42,-73.4],[-90.09,-73.32],[-89.23,-72.56],[-88.42,-73.01],[-87.27,-73.19],[-86.01,-73.09],[-85.19,-73.48],[-83.88,-73.52],[-82.67,-73.64],[-81.47,-73.85],[-80.69,-73.48],[-80.3,-73.13],[-79.3,-73.52],[-77.93,-73.42],[-76.91,-73.64],[-76.22,-73.97],[-74.89,-73.87],[-73.85,-73.66],[-72.83,-73.4],[-71.62,-73.26],[-70.21,-73.15],[-68.94,-73.01],[-67.96,-72.79],[-67.37,-72.48],[-67.13,-72.05],[-67.25,-71.64],[-67.56,-71.25],[-67.92,-70.85],[-68.23,-70.46],[-68.49,-70.11],[-68.54,-69.72],[-68.45,-69.33],[-67.98,-68.95],[-67.58,-68.54],[-67.43,-68.15],[-67.62,-67.72],[-67.74,-67.33],[-67.25,-66.88],[-66.7,-66.58],[-66.06,-66.21],[-65.37,-65.9],[-64.57,-65.6],[-64.18,-65.17],[-63.63,-64.9],[-63.0,-64.64],[-62.04,-64.58],[-61.41,-64.27],[-60.71,-64.07],[-59.89,-63.96],[-59.16,-63.7],[-58.59,-63.39],[-57.81,-63.27],[-57.22,-63.53],[-57.6,-63.86],[-58.61,-64.15]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-67.75,-53.85],[-66.45,-54.45],[-65.05,-54.7],[-65.5,-55.2],[-66.45,-55.25],[-66.96,-54.9],[-67.29,-55.3],[-68.15,-55.61],[-69.23,-55.5],[-69.96,-55.2],[-71.01,-55.05],[-72.26,-54.5],[-73.29,-53.96],[-74.66,-52.84],[-73.84,-53.05],[-72.43,-53.72],[-71.11,-54.07],[-70.59,-53.62],[-70.27,-52.93],[-69.35,-52.52],[-68.63,-52.64],[-68.63,-52.64],[-68.25,-53.1],[-67.75,-53.85]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.55,-51.1],[-57.75,-51.55],[-58.05,-51.9],[-59.4,-52.2],[-59.85,-51.85],[-60.7,-52.3],[-61.2,-51.85],[-60,-51.25],[-59.15,-51.5],[-58.55,-51.1]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[70.28,-49.71],[68.75,-49.77],[68.72,-49.24],[68.87,-48.83],[68.94,-48.62],[69.58,-48.94],[70.53,-49.06],[70.56,-49.26],[70.28,-49.71]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[145.4,-40.79],[146.36,-41.14],[146.91,-41.0],[147.69,-40.81],[148.29,-40.88],[148.36,-42.06],[148.02,-42.41],[147.91,-43.21],[147.56,-42.94],[146.87,-43.63],[146.66,-43.58],[146.05,-43.55],[145.43,-42.69],[145.3,-42.03],[144.72,-41.16],[144.74,-40.7],[145.4,-40.79]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[173.02,-40.92],[173.25,-41.33],[173.96,-40.93],[174.25,-41.35],[174.25,-41.77],[173.88,-42.23],[173.22,-42.97],[172.71,-43.37],[173.08,-43.85],[172.31,-43.87],[171.45,-44.24],[171.19,-44.9],[170.62,-45.91],[169.83,-46.36],[169.33,-46.64],[168.41,-46.62],[167.76,-46.29],[166.68,-46.22],[166.51,-45.85],[167.05,-45.11],[168.3,-44.12],[168.95,-43.94],[169.67,-43.56],[170.52,-43.03],[171.13,-42.51],[171.57,-41.77],[171.95,-41.51],[172.1,-40.96],[172.8,-40.49],[173.02,-40.92]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[174.61,-36.16],[175.34,-37.21],[175.36,-36.53],[175.81,-36.8],[175.96,-37.56],[176.76,-37.88],[177.44,-37.96],[178.01,-37.58],[178.52,-37.7],[178.27,-38.58],[177.97,-39.17],[177.21,-39.15],[176.94,-39.45],[177.03,-39.88],[176.89,-40.07],[176.51,-40.6],[176.01,-41.29],[175.24,-41.69],[175.07,-41.43],[174.65,-41.28],[175.23,-40.46],[174.9,-39.91],[173.82,-39.51],[173.85,-39.15],[174.57,-38.8],[174.74,-38.03],[174.7,-37.38],[174.29,-36.71],[174.32,-36.53],[173.84,-36.12],[173.05,-35.24],[172.64,-34.53],[173.01,-34.45],[173.55,-35.01],[174.33,-35.27],[174.61,-36.16]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[167.12,-22.16],[166.74,-22.4],[166.19,-22.13],[165.47,-21.68],[164.83,-21.15],[164.17,-20.44],[164.03,-20.11],[164.46,-20.12],[165.02,-20.46],[165.46,-20.8],[165.78,-21.08],[166.6,-21.7],[167.12,-22.16]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[178.37,-17.34],[178.72,-17.63],[178.55,-18.15],[177.93,-18.29],[177.38,-18.16],[177.29,-17.72],[177.67,-17.38],[178.13,-17.5],[178.37,-17.34]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[179.36,-16.8],[178.73,-17.01],[178.6,-16.64],[179.1,-16.43],[179.41,-16.38],[180,-16.07],[180,-16.56],[179.36,-16.8]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-179.92,-16.5],[-180,-16.56],[-180,-16.07],[-179.79,-16.02],[-179.92,-16.5]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[167.84,-16.47],[167.52,-16.6],[167.18,-16.16],[167.22,-15.89],[167.84,-16.47]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[167.11,-14.93],[167.27,-15.74],[167.0,-15.61],[166.79,-15.67],[166.65,-15.39],[166.63,-14.63],[167.11,-14.93]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[50.06,-13.56],[50.22,-14.76],[50.48,-15.23],[50.38,-15.71],[50.2,-16.0],[49.86,-15.41],[49.67,-15.71],[49.86,-16.45],[49.77,-16.88],[49.5,-17.11],[49.44,-17.95],[49.04,-19.12],[48.55,-20.5],[47.93,-22.39],[47.55,-23.78],[47.1,-24.94],[46.28,-25.18],[45.41,-25.6],[44.83,-25.35],[44.04,-24.99],[43.76,-24.46],[43.7,-23.57],[43.35,-22.78],[43.25,-22.06],[43.43,-21.34],[43.89,-21.16],[43.9,-20.83],[44.37,-20.07],[44.46,-19.44],[44.23,-18.96],[44.04,-18.33],[43.96,-17.41],[44.31,-16.85],[44.45,-16.22],[44.94,-16.18],[45.5,-15.97],[45.87,-15.79],[46.31,-15.78],[46.88,-15.21],[47.71,-14.59],[48.01,-14.09],[47.87,-13.66],[48.29,-13.78],[48.85,-13.09],[48.86,-12.49],[49.19,-12.04],[49.54,-12.47],[49.81,-12.9],[50.06,-13.56]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[143.56,-13.76],[143.92,-14.55],[144.56,-14.17],[144.89,-14.59],[145.37,-14.98],[145.27,-15.43],[145.49,-16.29],[145.64,-16.78],[145.89,-16.91],[146.16,-17.76],[146.06,-18.28],[146.39,-18.96],[147.47,-19.48],[148.18,-19.96],[148.85,-20.39],[148.72,-20.63],[149.29,-21.26],[149.68,-22.34],[150.08,-22.12],[150.48,-22.56],[150.73,-22.4],[150.9,-23.46],[151.61,-24.08],[152.07,-24.46],[152.86,-25.27],[153.14,-26.07],[153.16,-26.64],[153.09,-27.26],[153.57,-28.11],[153.51,-29.0],[153.34,-29.46],[153.07,-30.35],[153.09,-30.92],[152.89,-31.64],[152.45,-32.55],[151.71,-33.04],[151.34,-33.82],[151.01,-34.31],[150.71,-35.17],[150.33,-35.67],[150.08,-36.42],[149.95,-37.11],[150.0,-37.43],[149.42,-37.77],[148.3,-37.81],[147.38,-38.22],[146.92,-38.61],[146.32,-39.04],[145.49,-38.59],[144.88,-38.42],[145.03,-37.9],[144.49,-38.09],[143.61,-38.81],[142.75,-38.54],[142.18,-38.38],[141.61,-38.31],[140.64,-38.02],[139.99,-37.4],[139.81,-36.64],[139.57,-36.14],[139.08,-35.73],[138.12,-35.61],[138.45,-35.13],[138.21,-34.38],[137.72,-35.08],[136.83,-35.26],[137.35,-34.71],[137.5,-34.13],[137.89,-33.64],[137.81,-32.9],[137.0,-33.75],[136.37,-34.09],[135.99,-34.89],[135.21,-34.48],[135.24,-33.95],[134.61,-33.22],[134.09,-32.85],[134.27,-32.62],[132.99,-32.01],[132.29,-31.98],[131.33,-31.5],[129.54,-31.59],[128.24,-31.95],[127.1,-32.28],[126.15,-32.22],[125.09,-32.73],[124.22,-32.96],[124.03,-33.48],[123.66,-33.89],[122.81,-33.91],[122.18,-34.0],[121.3,-33.82],[120.58,-33.93],[119.89,-33.98],[119.3,-34.51],[119.01,-34.46],[118.51,-34.75],[118.02,-35.06],[117.3,-35.03],[116.63,-35.03],[115.56,-34.39],[115.03,-34.2],[115.05,-33.62],[115.55,-33.49],[115.71,-33.26],[115.68,-32.9],[115.8,-32.21],[115.69,-31.61],[115.16,-30.6],[115.0,-30.03],[115.04,-29.46],[114.64,-28.81],[114.62,-28.52],[114.17,-28.12],[114.05,-27.33],[113.48,-26.54],[113.34,-26.12],[113.78,-26.55],[113.44,-25.62],[113.94,-25.91],[114.23,-26.3],[114.22,-25.79],[113.72,-25.0],[113.63,-24.68],[113.39,-24.38],[113.5,-23.81],[113.71,-23.56],[113.84,-23.06],[113.74,-22.48],[114.15,-21.76],[114.23,-22.52],[114.65,-21.83],[115.46,-21.5],[115.95,-21.07],[116.71,-20.7],[117.17,-20.62],[117.44,-20.75],[118.23,-20.37],[118.84,-20.26],[118.99,-20.04],[119.25,-19.95],[119.81,-19.98],[120.86,-19.68],[121.4,-19.24],[121.66,-18.71],[122.24,-18.2],[122.29,-17.8],[122.31,-17.25],[123.01,-16.41],[123.43,-17.27],[123.86,-17.07],[123.5,-16.6],[123.82,-16.11],[124.26,-16.33],[124.38,-15.57],[124.93,-15.08],[125.17,-14.68],[125.67,-14.51],[125.69,-14.23],[126.13,-14.35],[126.14,-14.1],[126.58,-13.95],[127.07,-13.82],[127.8,-14.28],[128.36,-14.87],[128.99,-14.88],[129.62,-14.97],[129.41,-14.42],[129.89,-13.62],[130.34,-13.36],[130.18,-13.11],[130.62,-12.54],[131.22,-12.18],[131.74,-12.3],[132.58,-12.11],[132.56,-11.6],[131.82,-11.27],[132.36,-11.13],[133.02,-11.38],[133.55,-11.79],[134.39,-12.04],[134.68,-11.94],[135.3,-12.25],[135.88,-11.96],[136.26,-12.05],[136.49,-11.86],[136.95,-12.35],[136.69,-12.89],[136.31,-13.29],[135.96,-13.32],[136.08,-13.72],[135.78,-14.22],[135.43,-14.72],[135.5,-15.0],[136.3,-15.55],[137.07,-15.87],[137.58,-16.22],[138.3,-16.81],[138.59,-16.81],[139.11,-17.06],[139.26,-17.37],[140.22,-17.71],[140.88,-17.37],[141.07,-16.83],[141.27,-16.39],[141.4,-15.84],[141.7,-15.04],[141.56,-14.56],[141.64,-14.27],[141.52,-13.7],[141.65,-12.94],[141.84,-12.74],[141.69,-12.41],[141.93,-11.88],[142.12,-11.33],[142.14,-11.04],[142.52,-10.67],[142.8,-11.16],[142.87,-11.78],[143.12,-11.91],[143.16,-12.33],[143.52,-12.83],[143.6,-13.4],[143.56,-13.76]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[162.12,-10.48],[162.4,-10.83],[161.7,-10.82],[161.32,-10.2],[161.92,-10.45],[162.12,-10.48]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[120.72,-10.24],[120.3,-10.26],[118.97,-9.56],[119.9,-9.36],[120.43,-9.67],[120.78,-9.97],[120.72,-10.24]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[160.85,-9.87],[160.46,-9.9],[159.85,-9.79],[159.64,-9.64],[159.7,-9.24],[160.36,-9.4],[160.69,-9.61],[160.85,-9.87]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[161.68,-9.6],[161.53,-9.78],[160.79,-8.92],[160.58,-8.32],[160.92,-8.32],[161.28,-9.12],[161.68,-9.6]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[124.44,-10.14],[123.58,-10.36],[123.46,-10.24],[123.55,-9.9],[123.98,-9.29],[124.97,-8.89],[125.09,-8.66],[125.95,-8.43],[126.64,-8.4],[126.96,-8.27],[127.34,-8.4],[126.97,-8.67],[125.93,-9.11],[125.09,-9.39],[124.44,-10.14]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[117.9,-8.1],[118.26,-8.36],[118.88,-8.28],[119.13,-8.71],[117.97,-8.91],[117.28,-9.04],[116.74,-9.03],[117.08,-8.46],[117.63,-8.45],[117.9,-8.1]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[122.9,-8.09],[122.76,-8.65],[121.25,-8.93],[119.92,-8.81],[119.92,-8.44],[120.72,-8.24],[121.34,-8.54],[122.01,-8.46],[122.9,-8.09]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[159.88,-8.34],[159.92,-8.54],[159.13,-8.11],[158.59,-7.75],[158.21,-7.42],[158.36,-7.32],[158.82,-7.56],[159.64,-8.02],[159.88,-8.34]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[157.54,-7.35],[157.34,-7.4],[156.9,-7.18],[156.49,-6.77],[156.54,-6.6],[157.14,-7.02],[157.54,-7.35]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[108.62,-6.78],[110.54,-6.88],[110.76,-6.47],[112.61,-6.95],[112.98,-7.59],[114.48,-7.78],[115.71,-8.37],[114.56,-8.75],[113.46,-8.35],[112.56,-8.38],[111.52,-8.3],[110.59,-8.12],[109.43,-7.74],[108.69,-7.64],[108.28,-7.77],[106.45,-7.35],[106.28,-6.92],[105.37,-6.85],[106.05,-5.9],[107.27,-5.95],[108.07,-6.35],[108.49,-6.42],[108.62,-6.78]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[134.72,-6.21],[134.21,-6.9],[134.11,-6.14],[134.29,-5.78],[134.5,-5.45],[134.73,-5.74],[134.72,-6.21]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[155.88,-6.82],[155.6,-6.92],[155.17,-6.54],[154.73,-5.9],[154.51,-5.14],[154.65,-5.04],[154.76,-5.34],[155.06,-5.57],[155.55,-6.2],[156.02,-6.54],[155.88,-6.82]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[151.98,-5.48],[151.46,-5.56],[151.3,-5.84],[150.75,-6.08],[150.24,-6.32],[149.71,-6.32],[148.89,-6.03],[148.32,-5.75],[148.4,-5.44],[149.3,-5.58],[149.85,-5.51],[150.0,-5.03],[150.14,-5.0],[150.24,-5.53],[150.81,-5.46],[151.09,-5.11],[151.65,-4.76],[151.54,-4.17],[152.14,-4.15],[152.34,-4.31],[152.32,-4.87],[151.98,-5.48]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[127.25,-3.46],[126.87,-3.79],[126.18,-3.61],[125.99,-3.18],[127.0,-3.13],[127.25,-3.46]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[130.47,-3.09],[130.83,-3.86],[129.99,-3.45],[129.16,-3.36],[128.59,-3.43],[127.9,-3.39],[128.14,-2.84],[129.37,-2.8],[130.47,-3.09]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[153.14,-4.5],[152.83,-4.77],[152.64,-4.18],[152.41,-3.79],[151.95,-3.46],[151.38,-3.04],[150.66,-2.74],[150.94,-2.5],[151.48,-2.78],[151.82,-3.0],[152.24,-3.24],[152.64,-3.66],[153.02,-3.98],[153.14,-4.5]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[134.14,-1.15],[134.42,-2.77],[135.46,-3.37],[136.29,-2.31],[137.44,-1.7],[138.33,-1.7],[139.18,-2.05],[139.93,-2.41],[141.0,-2.6],[142.74,-3.29],[144.58,-3.86],[145.27,-4.37],[145.83,-4.88],[145.98,-5.47],[147.65,-6.08],[147.89,-6.61],[146.97,-6.72],[147.19,-7.39],[148.08,-8.04],[148.73,-9.1],[149.31,-9.07],[149.27,-9.51],[150.04,-9.68],[149.74,-9.87],[150.8,-10.29],[150.69,-10.58],[150.03,-10.65],[149.78,-10.39],[148.92,-10.28],[147.91,-10.13],[147.14,-9.49],[146.57,-8.94],[146.05,-8.07],[144.74,-7.63],[143.9,-7.92],[143.29,-8.25],[143.41,-8.98],[142.63,-9.33],[142.07,-9.16],[141.03,-9.12],[140.14,-8.3],[139.13,-8.1],[138.88,-8.38],[137.61,-8.41],[138.04,-7.6],[138.67,-7.32],[138.41,-6.23],[137.93,-5.39],[135.99,-4.55],[135.16,-4.46],[133.66,-3.54],[133.37,-4.02],[132.98,-4.11],[132.76,-3.75],[132.75,-3.31],[131.99,-2.82],[133.07,-2.46],[133.78,-2.48],[133.7,-2.21],[132.23,-2.21],[131.84,-1.62],[130.94,-1.43],[130.52,-0.94],[131.87,-0.7],[132.38,-0.37],[133.99,-0.78],[134.14,-1.15]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[125.24,1.42],[124.44,0.43],[123.69,0.24],[122.72,0.43],[121.06,0.38],[120.18,0.24],[120.04,-0.52],[120.94,-1.41],[121.48,-0.96],[123.34,-0.62],[123.26,-1.08],[122.82,-0.93],[122.39,-1.52],[121.51,-1.9],[122.45,-3.19],[122.27,-3.53],[123.17,-4.68],[123.16,-5.34],[122.63,-5.63],[122.24,-5.28],[122.72,-4.46],[121.74,-4.85],[121.49,-4.57],[121.62,-4.19],[120.9,-3.6],[120.97,-2.63],[120.31,-2.93],[120.39,-4.1],[120.43,-5.53],[119.8,-5.67],[119.37,-5.38],[119.65,-4.46],[119.5,-3.49],[119.08,-3.49],[118.77,-2.8],[119.18,-2.15],[119.32,-1.35],[119.83,0.15],[120.04,0.57],[120.89,1.31],[121.67,1.01],[122.93,0.88],[124.08,0.92],[125.07,1.64],[125.24,1.42]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[128.69,1.13],[128.64,0.26],[128.12,0.36],[127.97,-0.25],[128.38,-0.78],[128.1,-0.9],[127.7,-0.27],[127.4,1.01],[127.6,1.81],[127.93,2.17],[128.0,1.63],[128.59,1.54],[128.69,1.13]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[105.82,-5.85],[104.71,-5.87],[103.87,-5.04],[102.58,-4.22],[102.16,-3.61],[101.4,-2.8],[100.9,-2.05],[100.14,-0.65],[99.26,0.18],[98.97,1.04],[98.6,1.82],[97.7,2.45],[97.18,3.31],[96.42,3.87],[95.38,4.97],[95.29,5.48],[95.94,5.44],[97.48,5.25],[98.37,4.27],[99.14,3.59],[99.69,3.17],[100.64,2.1],[101.66,2.08],[102.5,1.4],[103.08,0.56],[103.84,0.1],[103.44,-0.71],[104.01,-1.06],[104.37,-1.08],[104.54,-1.78],[104.89,-2.34],[105.62,-2.43],[106.11,-3.06],[105.86,-4.31],[105.82,-5.85]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[117.88,1.83],[119.0,0.9],[117.81,0.78],[117.48,0.1],[117.52,-0.8],[116.56,-1.49],[116.53,-2.48],[116.15,-4.01],[116.0,-3.66],[114.86,-4.11],[114.47,-3.5],[113.76,-3.44],[113.26,-3.12],[112.07,-3.48],[111.7,-2.99],[111.05,-3.05],[110.22,-2.93],[110.07,-1.59],[109.57,-1.31],[109.09,-0.46],[108.95,0.42],[109.07,1.34],[109.66,2.01],[110.4,1.66],[111.17,1.85],[111.37,2.7],[111.8,2.89],[113.0,3.1],[113.71,3.89],[114.2,4.53],[114.6,4.9],[115.45,5.45],[116.22,6.14],[116.73,6.92],[117.13,6.93],[117.64,6.42],[117.69,5.99],[118.35,5.71],[119.18,5.41],[119.11,5.02],[118.44,4.97],[118.62,4.48],[117.88,4.14],[117.31,3.23],[118.05,2.29],[117.88,1.83]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[126.38,8.41],[126.48,7.75],[126.54,7.19],[126.2,6.27],[125.83,7.29],[125.36,6.79],[125.68,6.05],[125.4,5.58],[124.22,6.16],[123.94,6.89],[124.24,7.36],[123.61,7.83],[123.3,7.42],[122.83,7.46],[122.09,6.9],[121.92,7.19],[122.31,8.03],[122.94,8.32],[123.49,8.69],[123.84,8.24],[124.6,8.51],[124.76,8.96],[125.47,8.99],[125.41,9.76],[126.22,9.29],[126.31,8.78],[126.38,8.41]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[81.22,6.2],[80.35,5.97],[79.87,6.76],[79.7,8.2],[80.15,9.82],[80.84,9.27],[81.3,8.56],[81.79,7.52],[81.64,6.48],[81.22,6.2]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-60.94,10.11],[-61.77,10],[-61.95,10.09],[-61.66,10.37],[-61.68,10.76],[-61.1,10.89],[-60.9,10.86],[-60.94,10.11]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[123.98,10.28],[123.62,9.95],[123.31,9.32],[123.0,9.02],[122.38,9.71],[122.59,9.98],[122.84,10.26],[122.95,10.88],[123.5,10.94],[123.34,10.27],[124.08,11.23],[123.98,10.28]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[118.5,9.32],[117.17,8.37],[117.66,9.07],[118.39,9.68],[118.99,10.38],[119.51,11.37],[119.69,10.55],[119.03,10.0],[118.5,9.32]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[121.88,11.89],[122.48,11.58],[123.12,11.58],[123.1,11.17],[122.64,10.74],[122.0,10.44],[121.97,10.91],[122.04,11.42],[121.88,11.89]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[125.5,12.16],[125.78,11.05],[125.01,11.31],[125.03,10.98],[125.28,10.36],[124.8,10.13],[124.76,10.84],[124.46,10.89],[124.3,11.5],[124.89,11.42],[124.88,11.79],[124.27,12.56],[125.23,12.54],[125.5,12.16]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[121.53,13.07],[121.26,12.21],[120.83,12.7],[120.32,13.47],[121.18,13.43],[121.53,13.07]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[121.32,18.5],[121.94,18.22],[122.25,18.48],[122.34,18.22],[122.17,17.81],[122.52,17.09],[122.25,16.26],[121.66,15.93],[121.51,15.12],[121.73,14.33],[122.26,14.22],[122.7,14.34],[123.95,13.78],[123.86,13.24],[124.18,13.0],[124.08,12.54],[123.3,13.03],[122.93,13.55],[122.67,13.19],[122.03,13.78],[121.13,13.64],[120.63,13.86],[120.68,14.27],[120.99,14.53],[120.69,14.76],[120.56,14.4],[120.07,14.97],[119.92,15.41],[119.88,16.36],[120.29,16.03],[120.39,17.6],[120.72,18.51],[121.32,18.5]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-65.59,18.23],[-65.85,17.98],[-66.6,17.98],[-67.18,17.95],[-67.24,18.37],[-67.1,18.52],[-66.28,18.51],[-65.77,18.43],[-65.59,18.23]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-76.9,17.87],[-77.21,17.7],[-77.77,17.86],[-78.34,18.23],[-78.22,18.45],[-77.8,18.52],[-77.57,18.49],[-76.9,18.4],[-76.37,18.16],[-76.2,17.89],[-76.9,17.87]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-72.58,19.87],[-71.71,19.71],[-71.59,19.88],[-70.81,19.88],[-70.21,19.62],[-69.95,19.65],[-69.77,19.29],[-69.22,19.31],[-69.25,19.02],[-68.81,18.98],[-68.32,18.61],[-68.69,18.21],[-69.16,18.42],[-69.62,18.38],[-69.95,18.43],[-70.13,18.25],[-70.52,18.18],[-70.67,18.43],[-71.0,18.28],[-71.4,17.6],[-71.66,17.76],[-71.71,18.04],[-72.37,18.21],[-72.84,18.15],[-73.45,18.22],[-73.92,18.03],[-74.46,18.34],[-74.37,18.66],[-73.45,18.53],[-72.69,18.45],[-72.33,18.67],[-72.79,19.1],[-72.78,19.48],[-73.42,19.64],[-73.19,19.92],[-72.58,19.87]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[110.34,18.68],[109.48,18.2],[108.66,18.51],[108.63,19.37],[109.12,19.82],[110.21,20.1],[110.79,20.08],[111.01,19.7],[110.57,19.26],[110.34,18.68]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-155.54,19.08],[-155.69,18.92],[-155.94,19.06],[-155.91,19.34],[-156.07,19.7],[-156.02,19.81],[-155.85,19.98],[-155.92,20.17],[-155.86,20.27],[-155.79,20.25],[-155.4,20.08],[-155.22,19.99],[-155.06,19.86],[-154.81,19.51],[-154.83,19.45],[-155.22,19.24],[-155.54,19.08]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-156.08,20.64],[-156.41,20.57],[-156.59,20.78],[-156.7,20.86],[-156.71,20.93],[-156.61,21.01],[-156.26,20.92],[-156.0,20.76],[-156.08,20.64]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-156.76,21.18],[-156.79,21.07],[-157.33,21.1],[-157.25,21.22],[-156.76,21.18]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-157.65,21.32],[-157.71,21.26],[-157.78,21.28],[-158.13,21.31],[-158.25,21.54],[-158.29,21.58],[-158.03,21.72],[-157.94,21.65],[-157.65,21.32]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-159.35,21.98],[-159.46,21.88],[-159.8,22.07],[-159.75,22.14],[-159.6,22.24],[-159.37,22.21],[-159.35,21.98]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-79.68,22.77],[-79.28,22.4],[-78.35,22.51],[-77.99,22.28],[-77.15,21.66],[-76.52,21.21],[-76.19,21.22],[-75.6,21.02],[-75.67,20.74],[-74.93,20.69],[-74.18,20.28],[-74.3,20.05],[-74.96,19.92],[-75.63,19.87],[-76.32,19.95],[-77.76,19.86],[-77.09,20.41],[-77.49,20.67],[-78.14,20.74],[-78.48,21.03],[-78.72,21.6],[-79.28,21.56],[-80.22,21.83],[-80.52,22.04],[-81.82,22.19],[-82.17,22.39],[-81.8,22.64],[-82.78,22.69],[-83.49,22.17],[-83.91,22.15],[-84.05,21.91],[-84.55,21.8],[-84.97,21.9],[-84.45,22.2],[-84.23,22.57],[-83.78,22.79],[-83.27,22.98],[-82.51,23.08],[-82.27,23.19],[-81.4,23.12],[-80.62,23.11],[-79.68,22.77]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-77.53,23.76],[-77.78,23.71],[-78.03,24.29],[-78.41,24.58],[-78.19,25.21],[-77.89,25.17],[-77.54,24.34],[-77.53,23.76]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[121.18,22.79],[120.75,21.97],[120.22,22.81],[120.11,23.56],[120.69,24.54],[121.5,25.3],[121.95,25.0],[121.78,24.39],[121.18,22.79]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-77.82,26.58],[-78.91,26.42],[-78.98,26.79],[-78.51,26.87],[-77.85,26.84],[-77.82,26.58]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-77,26.59],[-77.17,25.88],[-77.36,26.01],[-77.34,26.53],[-77.79,26.93],[-77.79,27.04],[-77,26.59]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[134.64,34.15],[134.77,33.81],[134.2,33.2],[133.79,33.52],[133.28,33.29],[133.01,32.7],[132.36,32.99],[132.37,33.46],[132.92,34.06],[133.49,33.94],[133.9,34.36],[134.64,34.15]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[34.58,35.67],[33.9,35.25],[33.97,35.06],[34.0,34.98],[32.98,34.57],[32.49,34.7],[32.26,35.1],[32.73,35.14],[32.8,35.15],[32.95,35.39],[33.67,35.37],[34.58,35.67]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[23.7,35.71],[24.25,35.37],[25.03,35.42],[25.77,35.35],[25.75,35.18],[26.29,35.3],[26.16,35.0],[24.72,34.92],[24.74,35.08],[23.51,35.28],[23.7,35.71]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[15.52,38.23],[15.16,37.44],[15.31,37.13],[15.1,36.62],[14.34,37.0],[13.83,37.1],[12.43,37.61],[12.57,38.13],[13.74,38.03],[14.76,38.14],[15.52,38.23]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[9.21,41.21],[9.81,40.5],[9.67,39.18],[9.21,39.24],[8.81,38.91],[8.43,39.17],[8.39,40.38],[8.16,40.95],[8.71,40.9],[9.21,41.21]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[140.98,37.14],[140.6,36.34],[140.77,35.84],[140.25,35.14],[138.98,34.67],[137.22,34.61],[135.79,33.46],[135.12,33.85],[135.08,34.6],[133.34,34.38],[132.16,33.9],[130.99,33.89],[132.0,33.15],[131.33,31.45],[130.69,31.03],[130.2,31.42],[130.45,32.32],[129.81,32.61],[129.41,33.3],[130.35,33.6],[130.88,34.23],[131.88,34.75],[132.62,35.43],[134.61,35.73],[135.68,35.53],[136.72,37.3],[137.39,36.83],[138.86,37.83],[139.43,38.22],[140.05,39.44],[139.88,40.56],[140.31,41.2],[141.37,41.38],[141.91,39.99],[141.88,39.18],[140.96,38.17],[140.98,37.14]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[9.56,42.15],[9.23,41.38],[8.78,41.58],[8.54,42.26],[8.75,42.63],[9.39,43.01],[9.56,42.15]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[143.91,44.17],[144.61,43.96],[145.32,44.38],[145.54,43.26],[144.06,42.99],[143.18,42.0],[141.61,42.68],[141.07,41.58],[139.96,41.57],[139.82,42.56],[140.31,43.33],[141.38,43.39],[141.67,44.77],[141.97,45.55],[143.14,44.51],[143.91,44.17]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-63.66,46.55],[-62.94,46.42],[-62.01,46.44],[-62.5,46.03],[-62.87,45.97],[-64.14,46.39],[-64.39,46.73],[-64.01,47.04],[-63.66,46.55]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-61.81,49.11],[-62.29,49.09],[-63.59,49.4],[-64.52,49.87],[-64.17,49.96],[-62.86,49.71],[-61.84,49.29],[-61.81,49.11]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-123.51,48.51],[-124.01,48.37],[-125.66,48.83],[-125.95,49.18],[-126.85,49.53],[-127.03,49.81],[-128.06,49.99],[-128.44,50.54],[-128.36,50.77],[-127.31,50.55],[-126.7,50.4],[-125.76,50.3],[-125.42,49.95],[-124.92,49.48],[-123.92,49.06],[-123.51,48.51]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-56.13,50.69],[-56.8,49.81],[-56.14,50.15],[-55.47,49.94],[-55.82,49.59],[-54.94,49.31],[-54.47,49.56],[-53.48,49.25],[-53.79,48.52],[-53.09,48.69],[-52.96,48.16],[-52.65,47.54],[-53.07,46.66],[-53.52,46.62],[-54.18,46.81],[-53.96,47.63],[-54.24,47.75],[-55.4,46.88],[-56.0,46.92],[-55.29,47.39],[-56.25,47.63],[-57.33,47.57],[-59.27,47.6],[-59.42,47.9],[-58.8,48.25],[-59.23,48.52],[-58.39,49.13],[-57.36,50.72],[-56.74,51.29],[-55.87,51.63],[-55.41,51.59],[-55.6,51.32],[-56.13,50.69]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-132.71,54.04],[-132.71,54.04],[-132.71,54.04],[-132.71,54.04],[-131.75,54.12],[-132.05,52.98],[-131.18,52.18],[-131.58,52.18],[-132.18,52.64],[-132.55,53.1],[-133.05,53.41],[-133.24,53.85],[-133.18,54.17],[-132.71,54.04]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[143.65,50.75],[144.65,48.98],[143.17,49.31],[142.56,47.86],[143.53,46.84],[143.51,46.14],[142.75,46.74],[142.09,45.97],[141.91,46.81],[142.02,47.78],[141.9,48.86],[142.14,49.62],[142.18,50.95],[141.59,51.94],[141.68,53.3],[142.61,53.76],[142.21,54.23],[142.65,54.37],[142.91,53.7],[143.26,52.74],[143.24,51.76],[143.65,50.75]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-6.79,52.26],[-8.56,51.67],[-9.98,51.82],[-9.17,52.86],[-9.69,53.88],[-8.33,54.66],[-7.57,55.13],[-6.73,55.17],[-5.66,54.55],[-6.2,53.87],[-6.03,53.15],[-6.79,52.26]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[12.69,55.61],[12.09,54.8],[11.04,55.36],[10.9,55.78],[12.37,56.11],[12.69,55.61]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-153.01,57.12],[-154.01,56.73],[-154.52,56.99],[-154.67,57.46],[-153.76,57.82],[-153.23,57.97],[-152.56,57.9],[-152.14,57.59],[-153.01,57.12]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-3.01,58.63],[-4.07,57.55],[-3.06,57.69],[-1.96,57.68],[-2.22,56.87],[-3.12,55.97],[-2.09,55.91],[-1.11,54.62],[-0.43,54.46],[0.18,53.33],[0.47,52.93],[1.68,52.74],[1.56,52.1],[1.05,51.81],[1.45,51.29],[0.55,50.77],[-0.79,50.77],[-2.49,50.5],[-2.96,50.7],[-3.62,50.23],[-4.54,50.34],[-5.25,49.96],[-5.78,50.16],[-4.31,51.21],[-3.41,51.43],[-4.98,51.59],[-5.27,51.99],[-4.22,52.3],[-4.77,52.84],[-4.58,53.5],[-3.09,53.4],[-2.95,53.98],[-3.63,54.62],[-4.84,54.79],[-5.08,55.06],[-4.72,55.51],[-5.05,55.78],[-5.59,55.31],[-5.64,56.28],[-6.15,56.79],[-5.79,57.82],[-5.01,58.63],[-4.21,58.55],[-3.01,58.63]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-165.58,59.91],[-166.19,59.75],[-166.85,59.94],[-167.46,60.21],[-166.47,60.38],[-165.67,60.29],[-165.58,59.91]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-79.27,62.16],[-79.66,61.63],[-80.1,61.72],[-80.36,62.02],[-80.32,62.09],[-79.93,62.39],[-79.52,62.36],[-79.27,62.16]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-81.9,62.71],[-83.07,62.16],[-83.77,62.18],[-83.99,62.45],[-83.25,62.91],[-81.88,62.9],[-81.9,62.71]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-171.73,63.78],[-171.11,63.59],[-170.49,63.69],[-169.68,63.43],[-168.69,63.3],[-168.77,63.19],[-169.53,62.98],[-170.29,63.19],[-170.67,63.38],[-171.55,63.32],[-171.79,63.41],[-171.73,63.78]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-85.16,65.66],[-84.98,65.22],[-84.46,65.37],[-83.88,65.11],[-82.79,64.77],[-81.64,64.46],[-81.55,63.98],[-80.82,64.06],[-80.1,63.73],[-80.99,63.41],[-82.55,63.65],[-83.11,64.1],[-84.1,63.57],[-85.52,63.05],[-85.87,63.64],[-87.22,63.54],[-86.35,64.04],[-86.22,64.82],[-85.88,65.74],[-85.16,65.66]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-14.51,66.46],[-14.74,65.81],[-13.61,65.13],[-14.91,64.36],[-17.79,63.68],[-18.66,63.5],[-19.97,63.64],[-22.76,63.96],[-21.78,64.4],[-23.96,64.89],[-22.18,65.08],[-22.23,65.38],[-24.33,65.61],[-23.65,66.26],[-22.13,66.41],[-20.58,65.73],[-19.06,66.28],[-17.8,65.99],[-16.17,66.53],[-14.51,66.46]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-75.87,67.15],[-76.99,67.1],[-77.24,67.59],[-76.81,68.15],[-75.9,68.29],[-75.11,68.01],[-75.1,67.58],[-75.22,67.44],[-75.87,67.15]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-175.01,66.58],[-174.34,66.34],[-174.57,67.06],[-171.86,66.91],[-169.9,65.98],[-170.89,65.54],[-172.53,65.44],[-172.56,64.46],[-172.96,64.25],[-173.89,64.28],[-174.65,64.63],[-175.98,64.92],[-176.21,65.36],[-177.22,65.52],[-178.36,65.39],[-178.9,65.74],[-178.69,66.11],[-179.88,65.87],[-179.43,65.4],[-180,64.98],[-180,68.96],[-177.55,68.2],[-174.93,67.21],[-175.01,66.58]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-95.65,69.11],[-96.27,68.76],[-97.62,69.06],[-98.43,68.95],[-99.8,69.4],[-98.92,69.71],[-98.22,70.14],[-97.16,69.86],[-96.56,69.68],[-96.26,69.49],[-95.65,69.11]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[180,70.83],[178.9,70.78],[178.73,71.1],[180,71.52],[180,70.83]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-178.69,70.89],[-180,70.83],[-180,71.52],[-179.87,71.56],[-179.02,71.56],[-177.58,71.27],[-177.66,71.13],[-178.69,70.89]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-90.55,69.5],[-90.55,68.48],[-89.22,69.26],[-88.02,68.62],[-88.32,67.87],[-87.35,67.2],[-86.31,67.92],[-85.58,68.78],[-85.52,69.88],[-84.1,69.81],[-82.62,69.66],[-81.28,69.16],[-81.22,68.67],[-81.96,68.13],[-81.26,67.6],[-81.39,67.11],[-83.34,66.41],[-84.74,66.26],[-85.77,66.56],[-86.07,66.06],[-87.03,65.21],[-87.32,64.78],[-88.48,64.1],[-89.91,64.03],[-90.7,63.61],[-90.77,62.96],[-91.93,62.84],[-93.16,62.02],[-94.24,60.9],[-94.63,60.11],[-94.68,58.95],[-93.22,58.78],[-92.76,57.85],[-92.3,57.09],[-90.9,57.28],[-89.04,56.85],[-88.04,56.47],[-87.32,56.0],[-86.07,55.72],[-85.01,55.3],[-83.36,55.24],[-82.27,55.15],[-82.44,54.28],[-82.12,53.28],[-81.4,52.16],[-79.91,51.21],[-79.14,51.53],[-78.6,52.56],[-79.12,54.14],[-79.83,54.67],[-78.23,55.14],[-77.1,55.84],[-76.54,56.53],[-76.62,57.2],[-77.3,58.05],[-78.52,58.8],[-77.34,59.85],[-77.77,60.76],[-78.11,62.32],[-77.41,62.55],[-75.7,62.28],[-74.67,62.18],[-73.84,62.44],[-72.91,62.11],[-71.68,61.53],[-71.37,61.14],[-69.59,61.06],[-69.62,60.22],[-69.29,58.96],[-68.37,58.8],[-67.65,58.21],[-66.2,58.77],[-65.25,59.87],[-64.58,60.34],[-63.8,59.44],[-62.5,58.17],[-61.4,56.97],[-61.8,56.34],[-60.47,55.78],[-59.57,55.2],[-57.98,54.95],[-57.33,54.63],[-56.94,53.78],[-56.16,53.65],[-55.76,53.27],[-55.68,52.15],[-56.41,51.77],[-57.13,51.42],[-58.77,51.06],[-60.03,50.24],[-61.72,50.08],[-63.86,50.29],[-65.36,50.3],[-66.4,50.23],[-67.24,49.51],[-68.51,49.07],[-69.95,47.74],[-71.1,46.82],[-70.26,46.99],[-68.65,48.3],[-66.55,49.13],[-65.06,49.23],[-64.17,48.74],[-65.12,48.07],[-64.8,46.99],[-64.47,46.24],[-63.17,45.74],[-61.52,45.88],[-60.52,47.01],[-60.45,46.28],[-59.8,45.92],[-61.04,45.27],[-63.25,44.67],[-64.25,44.27],[-65.36,43.55],[-66.12,43.62],[-66.16,44.47],[-64.43,45.29],[-66.03,45.26],[-67.14,45.14],[-66.96,44.81],[-68.03,44.33],[-69.06,43.98],[-70.12,43.68],[-70.69,43.03],[-70.81,42.87],[-70.83,42.34],[-70.49,41.81],[-70.08,41.78],[-70.18,42.15],[-69.88,41.92],[-69.97,41.64],[-70.64,41.48],[-71.12,41.49],[-71.86,41.32],[-72.29,41.27],[-72.88,41.22],[-73.71,40.93],[-72.24,41.12],[-71.95,40.93],[-73.34,40.63],[-73.98,40.63],[-73.95,40.75],[-74.26,40.47],[-73.96,40.43],[-74.18,39.71],[-74.91,38.94],[-74.98,39.2],[-75.2,39.25],[-75.53,39.5],[-75.32,38.96],[-75.08,38.78],[-75.06,38.4],[-75.38,38.02],[-75.94,37.22],[-76.03,37.26],[-75.72,37.94],[-76.23,38.32],[-76.35,39.15],[-76.54,38.72],[-76.33,38.08],[-76.96,38.23],[-76.3,37.92],[-76.26,36.97],[-75.97,36.9],[-75.87,36.55],[-75.73,35.55],[-76.36,34.81],[-77.4,34.51],[-78.05,33.93],[-78.55,33.86],[-79.06,33.49],[-79.2,33.16],[-80.3,32.51],[-80.86,32.03],[-81.34,31.44],[-81.49,30.73],[-81.31,30.04],[-80.98,29.18],[-80.54,28.47],[-80.53,28.04],[-80.06,26.88],[-80.09,26.21],[-80.13,25.82],[-80.38,25.21],[-80.68,25.08],[-81.17,25.2],[-81.33,25.64],[-81.71,25.87],[-82.24,26.73],[-82.71,27.5],[-82.86,27.89],[-82.65,28.55],[-82.93,29.1],[-83.71,29.94],[-84.1,30.09],[-85.11,29.64],[-85.29,29.69],[-85.77,30.15],[-86.4,30.4],[-87.53,30.27],[-88.42,30.38],[-89.18,30.32],[-89.6,30.18],[-89.41,29.89],[-89.43,29.49],[-89.22,29.29],[-89.41,29.16],[-89.78,29.31],[-90.15,29.12],[-90.88,29.15],[-91.63,29.68],[-92.5,29.55],[-93.23,29.78],[-93.85,29.71],[-94.69,29.48],[-95.6,28.74],[-96.59,28.31],[-97.14,27.83],[-97.37,27.38],[-97.38,26.69],[-97.33,26.21],[-97.14,25.87],[-97.14,25.87],[-97.14,25.87],[-97.53,24.99],[-97.7,24.27],[-97.78,22.93],[-97.87,22.44],[-97.7,21.9],[-97.39,21.41],[-97.19,20.64],[-96.53,19.89],[-96.29,19.32],[-95.9,18.83],[-94.84,18.56],[-94.43,18.14],[-93.55,18.42],[-92.79,18.52],[-92.04,18.7],[-91.41,18.88],[-90.77,19.28],[-90.53,19.87],[-90.45,20.71],[-90.28,21.0],[-89.6,21.26],[-88.54,21.49],[-87.66,21.46],[-87.05,21.54],[-86.81,21.33],[-86.85,20.85],[-87.38,20.26],[-87.62,19.65],[-87.44,19.47],[-87.59,19.04],[-87.84,18.26],[-88.09,18.52],[-88.3,18.5],[-88.3,18.35],[-88.11,18.35],[-88.12,18.08],[-88.29,17.64],[-88.2,17.49],[-88.3,17.13],[-88.24,17.04],[-88.36,16.53],[-88.55,16.27],[-88.73,16.23],[-88.93,15.89],[-88.6,15.71],[-88.52,15.86],[-88.22,15.73],[-88.12,15.69],[-87.9,15.86],[-87.62,15.88],[-87.52,15.8],[-87.37,15.85],[-86.9,15.76],[-86.44,15.78],[-86.12,15.89],[-86.0,16.01],[-85.68,15.95],[-85.44,15.89],[-85.18,15.91],[-84.98,16.0],[-84.53,15.86],[-84.37,15.84],[-84.06,15.65],[-83.77,15.42],[-83.41,15.27],[-83.15,15.0],[-83.23,14.9],[-83.28,14.68],[-83.18,14.31],[-83.41,13.97],[-83.52,13.57],[-83.55,13.13],[-83.5,12.87],[-83.47,12.42],[-83.63,12.32],[-83.72,11.89],[-83.65,11.63],[-83.86,11.37],[-83.81,11.1],[-83.66,10.94],[-83.4,10.4],[-83.02,9.99],[-82.55,9.57],[-82.19,9.21],[-82.21,9.0],[-81.81,8.95],[-81.71,9.03],[-81.44,8.79],[-80.95,8.86],[-80.52,9.11],[-79.91,9.31],[-79.57,9.61],[-79.02,9.55],[-79.06,9.45],[-78.5,9.42],[-78.06,9.25],[-77.73,8.95],[-77.35,8.67],[-76.84,8.64],[-76.09,9.34],[-75.67,9.44],[-75.66,9.77],[-75.48,10.62],[-74.91,11.08],[-74.28,11.1],[-74.2,11.31],[-73.41,11.23],[-72.63,11.73],[-72.24,11.96],[-71.75,12.44],[-71.4,12.38],[-71.14,12.11],[-71.33,11.78],[-71.36,11.54],[-71.95,11.42],[-71.62,10.97],[-71.63,10.45],[-72.07,9.87],[-71.7,9.07],[-71.26,9.14],[-71.04,9.86],[-71.35,10.21],[-71.4,10.97],[-70.16,11.38],[-70.29,11.85],[-69.94,12.16],[-69.58,11.46],[-68.88,11.44],[-68.23,10.89],[-68.19,10.55],[-67.3,10.55],[-66.23,10.65],[-65.66,10.2],[-64.89,10.08],[-64.33,10.39],[-64.32,10.64],[-63.08,10.7],[-61.88,10.72],[-62.73,10.42],[-62.39,9.95],[-61.59,9.87],[-60.83,9.38],[-60.67,8.58],[-60.15,8.6],[-59.76,8.37],[-59.1,8.0],[-58.48,7.35],[-58.45,6.83],[-58.08,6.81],[-57.54,6.32],[-57.15,5.97],[-55.95,5.77],[-55.84,5.95],[-55.03,6.03],[-53.96,5.76],[-53.62,5.65],[-52.88,5.41],[-51.82,4.57],[-51.66,4.16],[-51.32,4.2],[-51.07,3.65],[-50.51,1.9],[-49.97,1.74],[-49.95,1.05],[-50.7,0.22],[-50.39,-0.08],[-48.62,-0.24],[-48.58,-1.24],[-47.82,-0.58],[-46.57,-0.94],[-44.91,-1.55],[-44.42,-2.14],[-44.58,-2.69],[-43.42,-2.38],[-41.47,-2.91],[-39.98,-2.87],[-38.5,-3.7],[-37.22,-4.82],[-36.45,-5.11],[-35.6,-5.15],[-35.24,-5.46],[-34.9,-6.74],[-34.73,-7.34],[-35.13,-9.0],[-35.64,-9.65],[-37.05,-11.04],[-37.68,-12.17],[-38.42,-13.04],[-38.67,-13.06],[-38.95,-13.79],[-38.88,-15.67],[-39.16,-17.21],[-39.27,-17.87],[-39.58,-18.26],[-39.76,-19.6],[-40.77,-20.9],[-40.94,-21.94],[-41.75,-22.37],[-41.99,-22.97],[-43.07,-22.97],[-44.65,-23.35],[-45.35,-23.8],[-46.47,-24.09],[-47.65,-24.89],[-48.5,-25.88],[-48.64,-26.62],[-48.47,-27.18],[-48.66,-28.19],[-48.89,-28.67],[-49.59,-29.22],[-50.7,-30.98],[-51.58,-31.78],[-52.26,-32.25],[-52.71,-33.2],[-53.37,-33.77],[-53.81,-34.4],[-54.94,-34.95],[-55.67,-34.75],[-56.22,-34.86],[-57.14,-34.43],[-57.82,-34.46],[-58.43,-33.91],[-58.5,-34.43],[-57.23,-35.29],[-57.36,-35.98],[-56.74,-36.41],[-56.79,-36.9],[-57.75,-38.18],[-59.23,-38.72],[-61.24,-38.93],[-62.34,-38.83],[-62.13,-39.42],[-62.33,-40.17],[-62.15,-40.68],[-62.75,-41.03],[-63.77,-41.17],[-64.73,-40.8],[-65.12,-41.06],[-64.98,-42.06],[-64.3,-42.36],[-63.76,-42.04],[-63.46,-42.56],[-64.38,-42.87],[-65.18,-43.5],[-65.33,-44.5],[-65.57,-45.04],[-66.51,-45.04],[-67.29,-45.55],[-67.58,-46.3],[-66.6,-47.03],[-65.64,-47.24],[-65.99,-48.13],[-67.17,-48.7],[-67.82,-49.87],[-68.73,-50.26],[-69.14,-50.73],[-68.82,-51.77],[-68.15,-52.35],[-68.57,-52.3],[-69.46,-52.29],[-69.94,-52.54],[-70.85,-52.9],[-71.01,-53.83],[-71.43,-53.86],[-72.56,-53.53],[-73.7,-52.84],[-74.95,-52.26],[-75.26,-51.63],[-74.98,-51.04],[-75.48,-50.38],[-75.61,-48.67],[-75.18,-47.71],[-74.13,-46.94],[-75.64,-46.65],[-74.69,-45.76],[-74.35,-44.1],[-73.24,-44.45],[-72.72,-42.38],[-73.39,-42.12],[-73.7,-43.37],[-74.33,-43.22],[-74.02,-41.79],[-73.68,-39.94],[-73.22,-39.26],[-73.51,-38.28],[-73.59,-37.16],[-73.17,-37.12],[-72.55,-35.51],[-71.86,-33.91],[-71.44,-32.42],[-71.67,-30.92],[-71.37,-30.1],[-71.49,-28.86],[-70.91,-27.64],[-70.72,-25.71],[-70.4,-23.63],[-70.09,-21.39],[-70.16,-19.76],[-70.37,-18.35],[-71.38,-17.77],[-71.46,-17.36],[-73.44,-16.36],[-75.24,-15.27],[-76.01,-14.65],[-76.42,-13.82],[-76.26,-13.53],[-77.11,-12.22],[-78.09,-10.38],[-79.04,-8.39],[-79.45,-7.93],[-79.76,-7.19],[-80.54,-6.54],[-81.25,-6.14],[-80.93,-5.69],[-81.41,-4.74],[-81.1,-4.04],[-80.3,-3.4],[-79.77,-2.66],[-79.99,-2.22],[-80.37,-2.69],[-80.97,-2.25],[-80.76,-1.97],[-80.93,-1.06],[-80.58,-0.91],[-80.4,-0.28],[-80.02,0.36],[-80.09,0.77],[-79.54,0.98],[-78.86,1.38],[-78.99,1.69],[-78.62,1.77],[-78.66,2.27],[-78.43,2.63],[-77.93,2.7],[-77.51,3.33],[-77.13,3.85],[-77.5,4.09],[-77.31,4.67],[-77.53,5.58],[-77.32,5.85],[-77.48,6.69],[-77.88,7.22],[-78.21,7.51],[-78.43,8.05],[-78.18,8.32],[-78.44,8.39],[-78.62,8.72],[-79.12,9.0],[-79.56,8.93],[-79.76,8.58],[-80.16,8.33],[-80.38,8.3],[-80.48,8.09],[-80.0,7.55],[-80.28,7.42],[-80.42,7.27],[-80.89,7.22],[-81.06,7.82],[-81.19,7.65],[-81.52,7.71],[-81.72,8.11],[-82.13,8.18],[-82.39,8.29],[-82.82,8.29],[-82.85,8.07],[-82.97,8.23],[-83.51,8.45],[-83.71,8.66],[-83.6,8.83],[-83.63,9.05],[-83.91,9.29],[-84.3,9.49],[-84.65,9.62],[-84.71,9.91],[-84.98,10.09],[-84.91,9.8],[-85.11,9.56],[-85.34,9.83],[-85.66,9.93],[-85.8,10.13],[-85.79,10.44],[-85.66,10.75],[-85.94,10.9],[-85.71,11.09],[-86.06,11.4],[-86.53,11.81],[-86.75,12.14],[-87.17,12.46],[-87.67,12.91],[-87.56,13.06],[-87.39,12.91],[-87.32,12.98],[-87.49,13.3],[-87.79,13.38],[-87.9,13.15],[-88.48,13.16],[-88.84,13.26],[-89.26,13.46],[-89.81,13.52],[-90.1,13.74],[-90.61,13.91],[-91.23,13.93],[-91.69,14.13],[-92.23,14.54],[-93.36,15.62],[-93.88,15.94],[-94.69,16.2],[-95.25,16.13],[-96.05,15.75],[-96.56,15.65],[-97.26,15.92],[-98.01,16.11],[-98.95,16.57],[-99.7,16.71],[-100.83,17.17],[-101.67,17.65],[-101.92,17.92],[-102.48,17.98],[-103.5,18.29],[-103.92,18.75],[-104.99,19.32],[-105.49,19.95],[-105.73,20.43],[-105.4,20.53],[-105.5,20.82],[-105.27,21.08],[-105.27,21.42],[-105.6,21.87],[-105.69,22.27],[-106.03,22.77],[-106.91,23.77],[-107.92,24.55],[-108.4,25.17],[-109.26,25.58],[-109.44,25.82],[-109.29,26.44],[-109.8,26.68],[-110.39,27.16],[-110.64,27.86],[-111.18,27.94],[-111.76,28.47],[-112.23,28.95],[-112.27,29.27],[-112.81,30.02],[-113.16,30.79],[-113.15,31.17],[-113.87,31.57],[-114.21,31.52],[-114.78,31.8],[-114.94,31.39],[-114.77,30.91],[-114.67,30.16],[-114.33,29.75],[-113.59,29.06],[-113.42,28.83],[-113.27,28.75],[-113.14,28.41],[-112.96,28.43],[-112.76,27.78],[-112.46,27.53],[-112.24,27.17],[-111.62,26.66],[-111.28,25.73],[-110.99,25.29],[-110.71,24.83],[-110.66,24.3],[-110.17,24.27],[-109.77,23.81],[-109.41,23.36],[-109.43,23.19],[-109.85,22.82],[-110.03,22.82],[-110.29,23.43],[-110.95,24.0],[-111.67,24.48],[-112.18,24.74],[-112.15,25.47],[-112.3,26.01],[-112.78,26.32],[-113.46,26.77],[-113.6,26.64],[-113.85,26.9],[-114.47,27.14],[-115.06,27.72],[-114.98,27.8],[-114.57,27.74],[-114.2,28.12],[-114.16,28.57],[-114.93,29.28],[-115.52,29.56],[-115.89,30.18],[-116.26,30.84],[-116.72,31.64],[-117.13,32.54],[-117.3,33.05],[-117.94,33.62],[-118.41,33.74],[-118.52,34.03],[-119.08,34.08],[-119.44,34.35],[-120.37,34.45],[-120.62,34.61],[-120.74,35.16],[-121.71,36.16],[-122.55,37.55],[-122.51,37.78],[-122.95,38.11],[-123.73,38.95],[-123.87,39.77],[-124.4,40.31],[-124.18,41.14],[-124.21,42.0],[-124.53,42.77],[-124.14,43.71],[-123.9,45.52],[-124.08,46.86],[-124.4,47.72],[-124.69,48.18],[-124.57,48.38],[-123.12,48.04],[-122.59,47.1],[-122.34,47.36],[-122.5,48.18],[-122.84,49.0],[-122.97,49.0],[-124.91,49.98],[-125.62,50.42],[-127.44,50.83],[-127.99,51.72],[-127.85,52.33],[-129.13,52.76],[-129.31,53.56],[-130.51,54.29],[-130.54,54.8],[-131.09,55.18],[-131.97,55.5],[-132.25,56.37],[-133.54,57.18],[-134.08,58.12],[-135.04,58.19],[-136.63,58.21],[-137.8,58.5],[-139.87,59.54],[-140.83,59.73],[-142.57,60.08],[-143.96,60.0],[-145.93,60.46],[-147.11,60.88],[-148.22,60.67],[-148.02,59.98],[-148.57,59.91],[-149.73,59.71],[-150.61,59.37],[-151.72,59.16],[-151.86,59.75],[-151.41,60.73],[-150.35,61.03],[-150.62,61.28],[-151.9,60.73],[-152.58,60.06],[-154.02,59.35],[-153.29,58.86],[-154.23,58.15],[-155.31,57.73],[-156.31,57.42],[-156.56,56.98],[-158.12,56.46],[-158.43,55.99],[-159.6,55.57],[-160.29,55.64],[-161.22,55.36],[-162.24,55.02],[-163.07,54.69],[-164.79,54.4],[-164.94,54.57],[-163.85,55.04],[-162.87,55.35],[-161.8,55.9],[-160.56,56.01],[-160.07,56.42],[-158.68,57.02],[-158.46,57.22],[-157.72,57.57],[-157.55,58.33],[-157.04,58.92],[-158.19,58.62],[-158.52,58.79],[-159.06,58.42],[-159.71,58.93],[-159.98,58.57],[-160.36,59.07],[-161.35,58.67],[-161.97,58.67],[-162.05,59.27],[-161.87,59.63],[-162.52,59.99],[-163.82,59.8],[-164.66,60.27],[-165.35,60.51],[-165.35,61.07],[-166.12,61.5],[-165.73,62.08],[-164.92,62.63],[-164.56,63.15],[-163.75,63.22],[-163.07,63.06],[-162.26,63.54],[-161.53,63.46],[-160.77,63.77],[-160.96,64.22],[-161.52,64.4],[-160.78,64.79],[-161.39,64.78],[-162.45,64.56],[-162.76,64.34],[-163.55,64.56],[-164.96,64.45],[-166.43,64.69],[-166.84,65.09],[-168.11,65.67],[-166.71,66.09],[-164.47,66.58],[-163.65,66.58],[-163.79,66.08],[-161.68,66.12],[-162.49,66.74],[-163.72,67.12],[-164.43,67.62],[-165.39,68.04],[-166.76,68.36],[-166.2,68.88],[-164.43,68.92],[-163.17,69.37],[-162.93,69.86],[-161.91,70.33],[-160.93,70.45],[-159.04,70.89],[-158.12,70.82],[-156.58,71.36],[-155.07,71.15],[-154.34,70.7],[-153.9,70.89],[-152.21,70.83],[-152.27,70.6],[-150.74,70.43],[-149.72,70.53],[-147.61,70.21],[-145.69,70.12],[-144.92,69.99],[-143.59,70.15],[-142.07,69.85],[-140.99,69.71],[-139.12,69.47],[-137.55,68.99],[-136.5,68.9],[-135.63,69.32],[-134.41,69.63],[-132.93,69.51],[-131.43,69.94],[-129.79,70.19],[-129.11,69.78],[-128.36,70.01],[-128.14,70.48],[-127.45,70.38],[-125.76,69.48],[-124.42,70.16],[-124.29,69.4],[-123.06,69.56],[-122.68,69.86],[-121.47,69.8],[-119.94,69.38],[-117.6,69.01],[-116.23,68.84],[-115.25,68.91],[-113.9,68.4],[-115.3,67.9],[-113.5,67.69],[-110.8,67.81],[-109.95,67.98],[-108.88,67.38],[-107.79,67.89],[-108.81,68.31],[-108.17,68.65],[-106.95,68.7],[-106.15,68.8],[-105.34,68.56],[-104.34,68.02],[-103.22,68.1],[-101.45,67.65],[-99.9,67.81],[-98.44,67.78],[-98.56,68.4],[-97.67,68.58],[-96.12,68.24],[-96.13,67.29],[-95.49,68.09],[-94.68,68.06],[-94.23,69.07],[-95.3,69.69],[-96.47,70.09],[-96.39,71.19],[-95.21,71.92],[-93.89,71.76],[-92.88,71.32],[-91.52,70.19],[-92.41,69.7],[-90.55,69.5]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-114.17,73.12],[-114.67,72.65],[-112.44,72.96],[-111.05,72.45],[-109.92,72.96],[-109.01,72.63],[-108.19,71.65],[-107.69,72.07],[-108.4,73.09],[-107.52,73.24],[-106.52,73.08],[-105.4,72.67],[-104.77,71.7],[-104.46,70.99],[-102.79,70.5],[-100.98,70.02],[-101.09,69.58],[-102.73,69.5],[-102.09,69.12],[-102.43,68.75],[-104.24,68.91],[-105.96,69.18],[-107.12,69.12],[-109,68.78],[-111.97,68.6],[-113.31,68.54],[-113.85,69.01],[-115.22,69.28],[-116.11,69.17],[-117.34,69.96],[-116.67,70.07],[-115.13,70.24],[-113.72,70.19],[-112.42,70.37],[-114.35,70.6],[-116.49,70.52],[-117.9,70.54],[-118.43,70.91],[-116.11,71.31],[-117.66,71.3],[-119.4,71.56],[-118.56,72.31],[-117.87,72.71],[-115.19,73.31],[-114.17,73.12]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-104.5,73.42],[-105.38,72.76],[-106.94,73.46],[-106.6,73.6],[-105.26,73.64],[-104.5,73.42]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-76.34,73.1],[-76.25,72.83],[-77.31,72.86],[-78.39,72.88],[-79.49,72.74],[-79.78,72.8],[-80.88,73.33],[-80.83,73.69],[-80.35,73.76],[-78.06,73.65],[-76.34,73.1]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-86.56,73.16],[-85.77,72.53],[-84.85,73.34],[-82.32,73.75],[-80.6,72.72],[-80.75,72.06],[-78.77,72.35],[-77.82,72.75],[-75.61,72.24],[-74.23,71.77],[-74.1,71.33],[-72.24,71.56],[-71.2,70.92],[-68.79,70.53],[-67.91,70.12],[-66.97,69.19],[-68.81,68.72],[-66.45,68.07],[-64.86,67.85],[-63.42,66.93],[-61.85,66.86],[-62.16,66.16],[-63.92,65.0],[-65.15,65.43],[-66.72,66.39],[-68.02,66.26],[-68.14,65.69],[-67.09,65.11],[-65.73,64.65],[-65.32,64.38],[-64.67,63.39],[-65.01,62.67],[-66.28,62.95],[-68.78,63.75],[-67.37,62.88],[-66.33,62.28],[-66.17,61.93],[-68.88,62.33],[-71.02,62.91],[-72.24,63.4],[-71.89,63.68],[-73.38,64.19],[-74.83,64.68],[-74.82,64.39],[-77.71,64.23],[-78.56,64.57],[-77.9,65.31],[-76.02,65.33],[-73.96,65.45],[-74.29,65.81],[-73.94,66.31],[-72.65,67.28],[-72.93,67.73],[-73.31,68.07],[-74.84,68.55],[-76.87,68.89],[-76.23,69.15],[-77.29,69.77],[-78.17,69.83],[-78.96,70.17],[-79.49,69.87],[-81.31,69.74],[-84.94,69.97],[-87.06,70.26],[-88.68,70.41],[-89.51,70.76],[-88.47,71.22],[-89.89,71.22],[-90.21,72.24],[-89.44,73.13],[-88.41,73.54],[-85.83,73.8],[-86.56,73.16]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-100.36,73.84],[-99.16,73.63],[-97.38,73.76],[-97.12,73.47],[-98.05,72.99],[-96.54,72.56],[-96.72,71.66],[-98.36,71.27],[-99.32,71.36],[-100.01,71.74],[-102.5,72.51],[-102.48,72.83],[-100.44,72.71],[-101.54,73.36],[-100.36,73.84]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[143.6,73.21],[142.09,73.21],[140.04,73.32],[139.86,73.37],[140.81,73.77],[142.06,73.86],[143.48,73.48],[143.6,73.21]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-93.2,72.77],[-94.27,72.02],[-95.41,72.06],[-96.03,72.94],[-96.02,73.44],[-95.5,73.86],[-94.5,74.13],[-92.42,74.1],[-90.51,73.86],[-92.0,72.97],[-93.2,72.77]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-120.46,71.4],[-123.09,70.9],[-123.62,71.34],[-125.93,71.87],[-125.59,72.19],[-124.81,73.02],[-123.94,73.68],[-124.92,74.29],[-121.54,74.45],[-120.11,74.24],[-117.56,74.19],[-116.58,73.9],[-115.51,73.48],[-116.77,73.22],[-119.22,72.52],[-120.46,71.82],[-120.46,71.4]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[150.73,75.08],[149.58,74.69],[147.98,74.78],[146.12,75.17],[146.36,75.5],[148.22,75.35],[150.73,75.08]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-93.61,74.98],[-94.16,74.59],[-95.61,74.67],[-96.82,74.93],[-96.29,75.38],[-94.85,75.65],[-93.98,75.3],[-93.61,74.98]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[145.09,75.56],[144.3,74.82],[140.61,74.85],[138.96,74.61],[136.97,75.26],[137.51,75.95],[138.83,76.14],[141.47,76.09],[145.09,75.56]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-98.5,76.72],[-97.74,76.26],[-97.7,75.74],[-98.16,75],[-99.81,74.9],[-100.88,75.06],[-100.86,75.64],[-102.5,75.56],[-102.57,76.34],[-101.49,76.31],[-99.98,76.65],[-98.58,76.59],[-98.5,76.72]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-108.21,76.2],[-107.82,75.85],[-106.93,76.01],[-105.88,75.97],[-105.7,75.48],[-106.31,75.01],[-109.7,74.85],[-112.22,74.42],[-113.74,74.39],[-113.87,74.72],[-111.79,75.16],[-116.31,75.04],[-117.71,75.22],[-116.35,76.2],[-115.4,76.48],[-112.59,76.14],[-110.81,75.55],[-109.07,75.47],[-110.5,76.43],[-109.58,76.79],[-108.55,76.68],[-108.21,76.2]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[57.54,70.72],[56.94,70.63],[53.68,70.76],[53.41,71.21],[51.6,71.47],[51.46,72.01],[52.48,72.23],[52.44,72.77],[54.43,73.63],[53.51,73.75],[55.9,74.63],[55.63,75.08],[57.87,75.61],[61.17,76.25],[64.5,76.44],[66.21,76.81],[68.16,76.94],[68.85,76.54],[68.18,76.23],[64.64,75.74],[61.58,75.26],[58.48,74.31],[56.99,73.33],[55.42,72.37],[55.62,71.54],[57.54,70.72]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-94.68,77.1],[-93.57,76.78],[-91.61,76.78],[-90.74,76.45],[-90.97,76.07],[-89.82,75.85],[-89.19,75.61],[-87.84,75.57],[-86.38,75.48],[-84.79,75.7],[-82.75,75.78],[-81.13,75.71],[-80.06,75.34],[-79.83,74.92],[-80.46,74.66],[-81.95,74.44],[-83.23,74.56],[-86.1,74.41],[-88.15,74.39],[-89.76,74.52],[-92.42,74.84],[-92.77,75.39],[-92.89,75.88],[-93.89,76.32],[-95.96,76.44],[-97.12,76.75],[-96.75,77.16],[-94.68,77.1]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-116.2,77.65],[-116.34,76.88],[-117.11,76.53],[-118.04,76.48],[-119.9,76.05],[-121.5,75.9],[-122.85,76.12],[-122.85,76.12],[-121.16,76.86],[-119.1,77.51],[-117.57,77.5],[-116.2,77.65]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[106.97,76.97],[107.24,76.48],[108.15,76.72],[111.08,76.71],[113.33,76.22],[114.13,75.85],[113.89,75.33],[112.78,75.03],[110.15,74.48],[109.4,74.18],[110.64,74.04],[112.12,73.79],[113.02,73.98],[113.53,73.34],[113.97,73.59],[115.57,73.75],[118.78,73.59],[119.02,73.12],[123.2,72.97],[123.26,73.74],[125.38,73.56],[126.98,73.57],[128.59,73.04],[129.05,72.4],[128.46,71.98],[129.72,71.19],[131.29,70.79],[132.25,71.84],[133.86,71.39],[135.56,71.66],[137.5,71.35],[138.23,71.63],[139.87,71.49],[139.15,72.42],[140.47,72.85],[149.5,72.2],[150.35,71.61],[152.97,70.84],[157.01,71.03],[159.0,70.87],[159.83,70.45],[159.71,69.72],[160.94,69.44],[162.28,69.64],[164.05,69.67],[165.94,69.47],[167.84,69.58],[169.58,68.69],[170.82,69.01],[170.01,69.65],[170.45,70.1],[173.64,69.82],[175.72,69.88],[178.6,69.4],[180,68.96],[180,64.98],[179.99,64.97],[178.71,64.53],[177.41,64.61],[178.31,64.08],[178.91,63.25],[179.37,62.98],[179.49,62.57],[179.23,62.3],[177.36,62.52],[174.57,61.77],[173.68,61.65],[172.15,60.95],[170.7,60.34],[170.33,59.88],[168.9,60.57],[166.3,59.79],[165.84,60.16],[164.88,59.73],[163.54,59.87],[163.22,59.21],[162.02,58.24],[162.05,57.84],[163.19,57.62],[163.06,56.16],[162.13,56.12],[161.7,55.29],[162.12,54.86],[160.37,54.34],[160.02,53.2],[158.53,52.96],[158.23,51.94],[156.79,51.01],[156.42,51.7],[155.99,53.16],[155.43,55.38],[155.91,56.77],[156.76,57.36],[156.81,57.83],[158.36,58.06],[160.15,59.31],[161.87,60.34],[163.67,61.14],[164.47,62.55],[163.26,62.47],[162.66,61.64],[160.12,60.54],[159.3,61.77],[156.72,61.43],[154.22,59.76],[155.04,59.15],[152.81,58.88],[151.27,58.78],[151.34,59.5],[149.78,59.66],[148.54,59.16],[145.49,59.34],[142.2,59.04],[138.96,57.09],[135.13,54.73],[136.7,54.6],[137.19,53.98],[138.16,53.76],[138.8,54.25],[139.9,54.19],[141.35,53.09],[141.38,52.24],[140.6,51.24],[140.51,50.05],[140.06,48.45],[138.55,47.0],[138.22,46.31],[136.86,45.14],[135.52,43.99],[134.87,43.4],[133.54,42.81],[132.91,42.8],[132.28,43.28],[130.94,42.55],[130.78,42.22],[130.4,42.28],[129.97,41.94],[129.67,41.6],[129.71,40.88],[129.19,40.66],[129.01,40.49],[128.63,40.19],[127.97,40.03],[127.53,39.76],[127.5,39.32],[127.39,39.21],[127.78,39.05],[128.35,38.61],[129.21,37.43],[129.46,36.78],[129.47,35.63],[129.09,35.08],[128.19,34.89],[127.39,34.48],[126.49,34.39],[126.37,34.93],[126.56,35.68],[126.12,36.73],[126.86,36.89],[126.17,37.75],[125.69,37.94],[125.57,37.75],[125.28,37.67],[125.24,37.86],[124.98,37.95],[124.71,38.11],[124.99,38.55],[125.22,38.67],[125.13,38.85],[125.39,39.39],[125.32,39.55],[124.74,39.66],[124.27,39.93],[122.87,39.64],[122.13,39.17],[121.05,38.9],[121.59,39.36],[121.38,39.75],[122.17,40.42],[121.64,40.95],[120.77,40.59],[119.64,39.9],[119.02,39.25],[118.04,39.2],[117.53,38.74],[118.06,38.06],[118.88,37.9],[118.91,37.45],[119.7,37.16],[120.82,37.87],[121.71,37.48],[122.36,37.45],[122.52,36.93],[121.1,36.65],[120.64,36.11],[119.66,35.61],[119.15,34.91],[120.23,34.36],[120.62,33.38],[121.23,32.46],[121.91,31.69],[121.89,30.95],[121.26,30.68],[121.5,30.14],[122.09,29.83],[121.94,29.02],[121.68,28.23],[121.13,28.14],[120.4,27.05],[119.59,25.74],[118.66,24.55],[117.28,23.62],[115.89,22.78],[114.76,22.67],[114.15,22.22],[113.81,22.55],[113.24,22.05],[111.84,21.55],[110.79,21.4],[110.44,20.34],[109.89,20.28],[109.63,21.01],[109.86,21.4],[108.52,21.72],[108.05,21.55],[106.72,20.7],[105.88,19.75],[105.66,19.06],[106.43,18.0],[107.36,16.7],[108.27,16.08],[108.88,15.28],[109.34,13.43],[109.2,11.67],[108.37,11.01],[107.22,10.36],[106.41,9.53],[105.16,8.6],[104.8,9.24],[105.08,9.92],[104.33,10.49],[103.5,10.63],[103.09,11.15],[102.59,12.19],[101.69,12.65],[100.83,12.63],[100.98,13.41],[100.1,13.41],[100.02,12.31],[99.48,10.85],[99.15,9.96],[99.22,9.24],[99.87,9.21],[100.28,8.3],[100.46,7.43],[101.02,6.86],[101.62,6.74],[102.14,6.22],[102.37,6.13],[102.96,5.52],[103.38,4.86],[103.44,4.18],[103.33,3.73],[103.43,3.38],[103.5,2.79],[103.85,2.52],[104.25,1.63],[104.23,1.29],[103.52,1.23],[102.57,1.97],[101.39,2.76],[101.27,3.27],[100.7,3.94],[100.56,4.77],[100.2,5.31],[100.31,6.04],[100.09,6.46],[99.69,6.85],[99.52,7.34],[98.99,7.91],[98.5,8.38],[98.34,7.79],[98.15,8.35],[98.26,8.97],[98.55,9.93],[98.46,10.68],[98.76,11.44],[98.43,12.03],[98.51,13.12],[98.1,13.64],[97.78,14.84],[97.6,16.1],[97.16,16.93],[96.51,16.43],[95.37,15.71],[94.81,15.8],[94.19,16.04],[94.53,17.28],[94.32,18.21],[93.54,19.37],[93.66,19.73],[93.08,19.86],[92.37,20.67],[92.08,21.19],[92.03,21.7],[91.83,22.18],[91.42,22.77],[90.5,22.81],[90.59,22.39],[90.27,21.84],[89.85,22.04],[89.7,21.86],[89.42,21.97],[89.03,22.06],[88.89,21.69],[88.21,21.7],[86.98,21.5],[87.03,20.74],[86.5,20.15],[85.06,19.48],[83.94,18.3],[83.19,17.67],[82.19,17.02],[82.19,16.56],[81.69,16.31],[80.79,15.95],[80.32,15.9],[80.03,15.14],[80.23,13.84],[80.29,13.01],[79.86,12.06],[79.86,10.36],[79.34,10.31],[78.89,9.55],[79.19,9.22],[78.28,8.93],[77.94,8.25],[77.54,7.97],[76.59,8.9],[76.13,10.3],[75.75,11.31],[75.4,11.78],[74.86,12.74],[74.62,13.99],[74.44,14.62],[73.53,15.99],[73.12,17.93],[72.82,19.21],[72.82,20.42],[72.63,21.36],[71.18,20.76],[70.47,20.88],[69.16,22.09],[69.64,22.45],[69.35,22.84],[68.18,23.69],[67.44,23.94],[67.15,24.66],[66.37,25.43],[64.53,25.24],[62.91,25.22],[61.5,25.08],[59.62,25.38],[58.53,25.61],[57.4,25.74],[56.97,26.97],[56.49,27.14],[55.72,26.96],[54.72,26.48],[53.49,26.81],[52.48,27.58],[51.52,27.87],[50.85,28.81],[50.12,30.15],[49.58,29.99],[48.94,30.32],[48.57,29.93],[47.97,29.98],[48.18,29.53],[48.09,29.31],[48.42,28.55],[48.81,27.69],[49.3,27.46],[49.47,27.11],[50.15,26.69],[50.21,26.28],[50.11,25.94],[50.24,25.61],[50.53,25.33],[50.66,25.0],[50.81,24.75],[50.74,25.48],[51.01,26.01],[51.29,26.11],[51.59,25.8],[51.61,25.22],[51.39,24.63],[51.58,24.25],[51.76,24.29],[51.79,24.02],[52.58,24.18],[53.4,24.15],[54.01,24.12],[54.69,24.8],[55.44,25.44],[56.07,26.06],[56.36,26.4],[56.49,26.31],[56.39,25.9],[56.26,25.71],[56.4,24.92],[56.85,24.24],[57.4,23.88],[58.14,23.75],[58.73,23.57],[59.18,22.99],[59.45,22.66],[59.81,22.53],[59.81,22.31],[59.44,21.71],[59.28,21.43],[58.86,21.11],[58.49,20.43],[58.03,20.48],[57.83,20.24],[57.67,19.74],[57.79,19.07],[57.69,18.94],[57.23,18.95],[56.61,18.57],[56.51,18.09],[56.28,17.88],[55.66,17.88],[55.27,17.63],[55.27,17.23],[54.79,16.95],[54.24,17.05],[53.57,16.71],[53.11,16.65],[52.39,16.38],[52.19,15.94],[52.17,15.6],[51.17,15.18],[49.57,14.71],[48.68,14.0],[48.24,13.95],[47.94,14.01],[47.35,13.59],[46.72,13.4],[45.88,13.35],[45.63,13.29],[45.41,13.03],[45.14,12.95],[44.99,12.7],[44.49,12.72],[44.18,12.59],[43.48,12.64],[43.22,13.22],[43.25,13.77],[43.09,14.06],[42.89,14.8],[42.6,15.21],[42.81,15.26],[42.7,15.72],[42.82,15.91],[42.78,16.35],[42.65,16.77],[42.35,17.08],[42.27,17.47],[41.75,17.83],[41.22,18.67],[40.94,19.49],[40.25,20.17],[39.8,20.34],[39.14,21.29],[39.02,21.99],[39.07,22.58],[38.49,23.69],[38.02,24.08],[37.48,24.29],[37.15,24.86],[37.21,25.08],[36.93,25.6],[36.64,25.83],[36.25,26.57],[35.64,27.38],[35.13,28.06],[34.63,28.06],[34.79,28.61],[34.83,28.96],[34.96,29.36],[34.92,29.5],[34.64,29.1],[34.43,28.34],[34.15,27.82],[33.92,27.65],[33.59,27.97],[33.14,28.42],[32.42,29.85],[32.32,29.76],[32.73,28.71],[33.35,27.7],[34.1,26.14],[34.47,25.6],[34.8,25.03],[35.69,23.93],[35.49,23.75],[35.53,23.1],[36.69,22.2],[36.87,22.0],[37.19,21.02],[36.97,20.84],[37.11,19.81],[37.48,18.61],[37.86,18.37],[38.41,18.0],[38.99,16.84],[39.27,15.92],[39.81,15.44],[41.18,14.49],[41.73,13.92],[42.28,13.34],[42.59,13.0],[43.08,12.7],[43.32,12.39],[43.29,11.97],[42.72,11.74],[43.15,11.46],[43.47,11.28],[43.67,10.86],[44.12,10.45],[44.61,10.44],[45.56,10.7],[46.65,10.82],[47.53,11.13],[48.02,11.19],[48.38,11.38],[48.95,11.41],[49.27,11.43],[49.73,11.58],[50.26,11.68],[50.73,12.02],[51.11,12.02],[51.13,11.75],[51.04,11.17],[51.05,10.64],[50.83,10.28],[50.55,9.2],[50.07,8.08],[49.45,6.8],[48.59,5.34],[47.74,4.22],[46.56,2.86],[45.56,2.05],[44.07,1.05],[43.14,0.29],[42.04,-0.92],[41.81,-1.45],[41.59,-1.68],[40.88,-2.08],[40.64,-2.5],[40.26,-2.57],[40.12,-3.28],[39.8,-3.68],[39.6,-4.35],[39.2,-4.68],[38.74,-5.91],[38.8,-6.48],[39.44,-6.84],[39.47,-7.1],[39.19,-7.7],[39.25,-8.01],[39.19,-8.49],[39.54,-9.11],[39.95,-10.1],[40.32,-10.32],[40.48,-10.77],[40.44,-11.76],[40.56,-12.64],[40.6,-14.2],[40.78,-14.69],[40.48,-15.41],[40.09,-16.1],[39.45,-16.72],[38.54,-17.1],[37.41,-17.59],[36.28,-18.66],[35.9,-18.84],[35.2,-19.55],[34.79,-19.78],[34.7,-20.5],[35.18,-21.25],[35.37,-21.84],[35.39,-22.14],[35.56,-22.09],[35.53,-23.07],[35.37,-23.54],[35.61,-23.71],[35.46,-24.12],[35.04,-24.48],[34.22,-24.82],[33.01,-25.36],[32.57,-25.73],[32.66,-26.15],[32.92,-26.22],[32.83,-26.74],[32.58,-27.47],[32.46,-28.3],[32.2,-28.75],[31.52,-29.26],[31.33,-29.4],[30.9,-29.91],[30.62,-30.42],[30.06,-31.14],[28.93,-32.17],[28.22,-32.77],[27.46,-33.23],[26.42,-33.61],[25.91,-33.67],[25.78,-33.94],[25.17,-33.8],[24.68,-33.99],[23.59,-33.79],[22.99,-33.92],[22.57,-33.86],[21.54,-34.26],[20.69,-34.42],[20.07,-34.8],[19.62,-34.82],[19.19,-34.46],[18.86,-34.44],[18.42,-34.0],[18.38,-34.14],[18.24,-33.87],[18.25,-33.28],[17.93,-32.61],[18.25,-32.43],[18.22,-31.66],[17.57,-30.73],[17.06,-29.88],[17.06,-29.88],[16.35,-28.58],[15.6,-27.82],[15.21,-27.09],[14.99,-26.12],[14.74,-25.39],[14.41,-23.85],[14.39,-22.66],[14.26,-22.11],[13.87,-21.7],[13.35,-20.87],[12.83,-19.67],[12.61,-19.05],[11.79,-18.07],[11.73,-17.3],[11.64,-16.67],[11.78,-15.79],[12.12,-14.88],[12.18,-14.45],[12.5,-13.55],[12.74,-13.14],[13.31,-12.48],[13.63,-12.04],[13.74,-11.3],[13.69,-10.73],[13.39,-10.37],[13.12,-9.77],[12.88,-9.17],[12.93,-8.96],[13.24,-8.56],[12.93,-7.6],[12.73,-6.93],[12.23,-6.29],[12.32,-6.1],[12.18,-5.79],[11.92,-5.04],[11.09,-3.98],[10.07,-2.97],[9.41,-2.14],[8.8,-1.11],[8.83,-0.78],[9.05,-0.46],[9.29,0.27],[9.49,1.01],[9.31,1.16],[9.65,2.28],[9.8,3.07],[9.4,3.73],[8.95,3.9],[8.74,4.35],[8.49,4.5],[8.5,4.77],[7.46,4.41],[7.08,4.46],[6.7,4.24],[5.9,4.26],[5.36,4.89],[5.03,5.61],[4.33,6.27],[3.57,6.26],[2.69,6.26],[1.87,6.14],[1.06,5.93],[-0.51,5.34],[-1.06,5.0],[-1.96,4.71],[-2.86,4.99],[-3.31,4.98],[-4.01,5.18],[-4.65,5.17],[-5.83,4.99],[-6.53,4.71],[-7.52,4.34],[-7.71,4.36],[-7.97,4.36],[-9.0,4.83],[-9.91,5.59],[-10.77,6.14],[-11.44,6.79],[-11.71,6.86],[-12.43,7.26],[-12.95,7.8],[-13.12,8.16],[-13.25,8.9],[-13.69,9.49],[-14.07,9.89],[-14.33,10.02],[-14.58,10.21],[-14.69,10.66],[-14.84,10.88],[-15.13,11.04],[-15.66,11.46],[-16.09,11.52],[-16.31,11.81],[-16.31,11.96],[-16.61,12.17],[-16.68,12.38],[-16.84,13.15],[-16.71,13.6],[-17.13,14.37],[-17.62,14.73],[-17.19,14.92],[-16.7,15.62],[-16.46,16.14],[-16.55,16.67],[-16.27,17.17],[-16.15,18.11],[-16.26,19.1],[-16.38,19.59],[-16.28,20.09],[-16.54,20.57],[-17.06,21.0],[-17.02,21.42],[-16.97,21.89],[-16.59,22.16],[-16.26,22.68],[-16.33,23.02],[-15.98,23.72],[-15.43,24.36],[-15.09,24.52],[-14.82,25.1],[-14.8,25.64],[-14.44,26.25],[-13.77,26.62],[-13.14,27.64],[-12.62,28.04],[-11.69,28.15],[-10.9,28.83],[-10.4,29.1],[-9.56,29.93],[-9.81,31.18],[-9.43,32.04],[-9.3,32.56],[-8.66,33.24],[-7.65,33.7],[-6.91,34.11],[-6.24,35.15],[-5.93,35.76],[-5.19,35.76],[-4.59,35.33],[-3.64,35.4],[-2.6,35.18],[-2.17,35.17],[-1.21,35.71],[-0.13,35.89],[0.5,36.3],[1.47,36.61],[3.16,36.78],[4.82,36.87],[5.32,36.72],[6.26,37.11],[7.33,37.12],[7.74,36.89],[8.42,36.95],[9.51,37.35],[10.21,37.23],[10.18,36.72],[11.03,37.09],[11.1,36.9],[10.6,36.41],[10.59,35.95],[10.94,35.7],[10.81,34.83],[10.15,34.33],[10.34,33.79],[10.86,33.77],[11.11,33.29],[11.49,33.14],[12.66,32.79],[13.08,32.88],[13.92,32.71],[15.25,32.27],[15.71,31.38],[16.61,31.18],[18.02,30.76],[19.09,30.27],[19.57,30.53],[20.05,30.99],[19.82,31.75],[20.13,32.24],[20.85,32.71],[21.54,32.84],[22.9,32.64],[23.24,32.19],[23.61,32.19],[23.93,32.02],[24.92,31.9],[25.16,31.57],[26.5,31.59],[27.46,31.32],[28.45,31.03],[28.91,30.87],[29.68,31.19],[30.1,31.47],[30.98,31.56],[31.69,31.43],[31.96,30.93],[32.19,31.26],[32.99,31.02],[33.77,30.97],[34.27,31.22],[34.56,31.55],[34.49,31.61],[34.75,32.07],[34.96,32.83],[35.1,33.08],[35.13,33.09],[35.48,33.91],[35.98,34.61],[36.0,34.64],[35.91,35.41],[36.15,35.82],[35.78,36.28],[36.16,36.65],[35.55,36.57],[34.71,36.8],[34.03,36.22],[32.51,36.11],[31.7,36.64],[30.62,36.68],[30.39,36.26],[29.7,36.14],[28.73,36.68],[27.64,36.66],[27.05,37.65],[26.32,38.21],[26.8,38.99],[26.17,39.46],[27.28,40.42],[28.82,40.46],[29.24,41.22],[31.15,41.09],[32.35,41.74],[33.51,42.02],[35.17,42.04],[36.91,41.34],[38.35,40.95],[39.51,41.1],[40.37,41.01],[41.55,41.54],[41.7,41.96],[41.45,42.65],[40.88,43.01],[40.32,43.13],[39.96,43.44],[38.68,44.28],[37.54,44.66],[36.68,45.24],[37.4,45.4],[38.23,46.24],[37.67,46.64],[39.15,47.04],[39.12,47.26],[38.22,47.1],[37.43,47.02],[36.76,46.7],[35.82,46.65],[34.96,46.27],[35.02,45.65],[35.51,45.41],[36.53,45.47],[36.33,45.11],[35.24,44.94],[33.88,44.36],[33.33,44.56],[33.55,45.03],[32.45,45.33],[32.63,45.52],[33.59,45.85],[33.3,46.08],[31.74,46.33],[31.68,46.71],[30.75,46.58],[30.38,46.03],[29.6,45.29],[29.63,45.04],[29.14,44.82],[28.84,44.91],[28.56,43.71],[28.04,43.29],[27.67,42.58],[28.0,42.01],[28.12,41.62],[28.99,41.3],[28.81,41.05],[27.62,41.0],[27.19,40.69],[26.36,40.15],[26.04,40.62],[26.06,40.82],[25.45,40.85],[24.93,40.95],[23.71,40.69],[24.41,40.13],[23.9,39.96],[23.34,39.96],[22.81,40.48],[22.63,40.26],[22.85,39.66],[23.35,39.19],[22.97,38.97],[23.53,38.51],[24.03,38.22],[24.04,37.66],[23.12,37.92],[23.41,37.41],[22.78,37.31],[23.15,36.42],[22.49,36.41],[21.67,36.85],[21.3,37.65],[21.12,38.31],[20.73,38.77],[20.22,39.34],[20.15,39.63],[19.98,39.7],[19.96,39.92],[19.41,40.25],[19.32,40.73],[19.4,41.41],[19.54,41.72],[19.37,41.88],[19.16,41.96],[18.88,42.28],[18.45,42.48],[17.51,42.85],[16.93,43.21],[16.02,43.51],[15.17,44.24],[15.38,44.32],[14.92,44.74],[14.9,45.08],[14.26,45.23],[13.95,44.8],[13.66,45.14],[13.68,45.48],[13.72,45.5],[13.94,45.59],[13.14,45.74],[12.33,45.38],[12.38,44.89],[12.26,44.6],[12.59,44.09],[13.53,43.59],[14.03,42.76],[15.14,41.96],[15.93,41.96],[16.17,41.74],[15.89,41.54],[16.79,41.18],[17.52,40.88],[18.38,40.36],[18.48,40.17],[18.29,39.81],[17.74,40.28],[16.87,40.44],[16.45,39.8],[17.17,39.42],[17.05,38.9],[16.64,38.84],[16.1,37.99],[15.68,37.91],[15.69,38.21],[15.89,38.75],[16.11,38.96],[15.72,39.54],[15.41,40.05],[15.0,40.17],[14.7,40.6],[14.06,40.79],[13.63,41.19],[12.89,41.25],[12.11,41.7],[11.19,42.36],[10.51,42.93],[10.2,43.92],[9.7,44.04],[8.89,44.37],[8.43,44.23],[7.85,43.77],[7.44,43.69],[6.53,43.13],[4.56,43.4],[3.1,43.08],[2.99,42.47],[3.04,41.89],[2.09,41.23],[0.81,41.01],[0.72,40.68],[0.11,40.12],[-0.28,39.31],[0.11,38.74],[-0.47,38.29],[-0.68,37.64],[-1.44,37.44],[-2.15,36.67],[-3.42,36.66],[-4.37,36.68],[-5.0,36.32],[-5.38,35.95],[-5.87,36.03],[-6.24,36.37],[-6.52,36.94],[-7.45,37.1],[-7.86,36.84],[-8.38,36.98],[-8.9,36.87],[-8.75,37.65],[-8.84,38.27],[-9.29,38.36],[-9.53,38.74],[-9.45,39.39],[-9.05,39.76],[-8.98,40.16],[-8.77,40.76],[-8.79,41.18],[-8.99,41.54],[-9.03,41.88],[-8.98,42.59],[-9.39,43.03],[-7.98,43.75],[-6.75,43.57],[-5.41,43.57],[-4.35,43.4],[-3.52,43.46],[-1.9,43.42],[-1.38,44.02],[-1.19,46.01],[-2.23,47.06],[-2.96,47.57],[-4.49,47.96],[-4.59,48.68],[-3.3,48.9],[-1.62,48.64],[-1.93,49.78],[-0.99,49.35],[1.34,50.13],[1.64,50.95],[2.51,51.15],[3.32,51.35],[3.83,51.62],[4.71,53.09],[6.07,53.51],[6.91,53.48],[7.1,53.69],[7.94,53.75],[8.12,53.53],[8.8,54.02],[8.57,54.4],[8.53,54.96],[8.12,55.52],[8.09,56.54],[8.26,56.81],[8.54,57.11],[9.42,57.17],[9.78,57.45],[10.58,57.73],[10.55,57.22],[10.25,56.89],[10.37,56.61],[10.91,56.46],[10.67,56.08],[10.37,56.19],[9.65,55.47],[9.92,54.98],[9.94,54.6],[10.95,54.36],[10.94,54.01],[11.96,54.2],[12.52,54.47],[13.65,54.08],[14.12,53.76],[14.8,54.05],[16.36,54.51],[17.62,54.85],[18.62,54.68],[18.7,54.44],[19.66,54.43],[19.89,54.87],[21.27,55.19],[21.06,56.03],[21.09,56.78],[21.58,57.41],[22.52,57.75],[23.32,57.01],[24.12,57.03],[24.31,57.79],[24.43,58.38],[24.06,58.26],[23.43,58.61],[23.34,59.19],[24.6,59.47],[25.86,59.61],[26.95,59.45],[27.98,59.48],[29.12,60.03],[28.07,60.5],[26.26,60.42],[24.5,60.06],[22.87,59.85],[22.29,60.39],[21.32,60.72],[21.54,61.71],[21.06,62.61],[21.54,63.19],[22.44,63.82],[24.73,64.9],[25.4,65.11],[25.29,65.53],[23.9,66.01],[22.18,65.72],[21.21,65.03],[21.37,64.41],[19.78,63.61],[17.85,62.75],[17.12,61.34],[17.83,60.64],[18.79,60.08],[17.87,58.95],[16.83,58.72],[16.45,57.04],[15.88,56.1],[14.67,56.2],[14.1,55.41],[12.94,55.36],[12.63,56.31],[11.79,57.44],[11.03,58.86],[10.36,59.47],[8.38,58.31],[7.05,58.08],[5.67,58.59],[5.31,59.66],[4.99,61.97],[5.91,62.61],[8.55,63.45],[10.53,64.49],[12.36,65.88],[14.76,67.81],[16.44,68.56],[19.18,69.82],[21.38,70.26],[23.02,70.2],[24.55,71.03],[26.37,70.99],[28.17,71.19],[31.29,70.45],[30.01,70.19],[31.1,69.56],[32.13,69.91],[33.78,69.3],[36.51,69.06],[40.29,67.93],[41.06,67.46],[41.13,66.79],[40.02,66.27],[38.38,66.0],[33.92,66.76],[33.18,66.63],[34.81,65.9],[34.94,64.41],[36.23,64.11],[37.01,63.85],[37.14,64.33],[36.52,64.78],[37.18,65.14],[39.59,64.52],[40.44,64.76],[39.76,65.5],[42.09,66.48],[43.02,66.42],[43.95,66.07],[44.53,66.76],[43.7,67.35],[44.19,67.95],[43.45,68.57],[46.25,68.25],[46.82,67.69],[45.56,67.57],[45.56,67.01],[46.35,66.67],[47.89,66.88],[48.14,67.52],[50.23,68.0],[53.72,68.86],[54.47,68.81],[53.49,68.2],[54.73,68.1],[55.44,68.44],[57.32,68.47],[58.8,68.88],[59.94,68.28],[61.08,68.94],[60.03,69.52],[60.55,69.85],[63.5,69.55],[64.89,69.23],[68.51,68.09],[69.18,68.62],[68.16,69.14],[68.14,69.36],[66.93,69.45],[67.26,69.93],[66.72,70.71],[66.69,71.03],[68.54,71.93],[69.2,72.84],[69.94,73.04],[72.59,72.78],[72.8,72.22],[71.85,71.41],[72.47,71.09],[72.79,70.39],[72.56,69.02],[73.67,68.41],[73.24,67.74],[71.28,66.32],[72.42,66.17],[72.82,66.53],[73.92,66.79],[74.19,67.28],[75.05,67.76],[74.47,68.33],[74.94,68.99],[73.84,69.07],[73.6,69.63],[74.4,70.63],[73.1,71.45],[74.89,72.12],[74.66,72.83],[75.16,72.86],[75.68,72.3],[75.29,71.34],[76.36,71.15],[75.9,71.87],[77.58,72.27],[79.65,72.32],[81.5,71.75],[80.61,72.58],[80.51,73.65],[82.25,73.85],[84.66,73.81],[86.82,73.94],[86.01,74.46],[87.17,75.12],[88.32,75.14],[90.26,75.64],[92.9,75.77],[93.23,76.05],[95.86,76.14],[96.68,75.92],[98.92,76.45],[100.76,76.43],[101.04,76.86],[101.99,77.29],[104.35,77.7],[106.07,77.37],[104.71,77.13],[106.97,76.97]],[[49.11,41.28],[49.62,40.57],[50.08,40.53],[50.39,40.26],[49.57,40.18],[49.4,39.4],[49.22,39.05],[48.86,38.82],[48.88,38.32],[49.2,37.58],[50.15,37.37],[50.84,36.87],[52.26,36.7],[53.83,36.97],[53.92,37.2],[53.74,37.91],[53.88,38.95],[53.1,39.29],[53.36,39.98],[52.69,40.03],[52.92,40.88],[53.86,40.63],[54.74,40.95],[54.01,41.55],[53.72,42.12],[52.92,41.87],[52.81,41.14],[52.5,41.78],[52.45,42.03],[52.69,42.44],[52.5,42.79],[51.34,43.13],[50.89,44.03],[50.34,44.28],[50.31,44.61],[51.28,44.51],[51.32,45.25],[52.17,45.41],[53.04,45.26],[53.22,46.23],[53.04,46.85],[52.04,46.8],[51.19,47.05],[50.03,46.61],[49.1,46.4],[48.65,45.81],[47.68,45.64],[46.68,44.61],[47.59,43.66],[47.49,42.99],[48.58,41.81],[49.11,41.28]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-93.84,77.52],[-94.3,77.49],[-96.17,77.56],[-96.44,77.83],[-94.42,77.82],[-93.72,77.63],[-93.84,77.52]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-110.19,77.7],[-112.05,77.41],[-113.53,77.73],[-112.72,78.05],[-111.26,78.15],[-109.85,78.0],[-110.19,77.7]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[24.72,77.85],[22.49,77.44],[20.73,77.68],[21.42,77.94],[20.81,78.25],[22.88,78.45],[23.28,78.08],[24.72,77.85]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-109.66,78.6],[-110.88,78.41],[-112.54,78.41],[-112.53,78.55],[-111.5,78.85],[-110.96,78.8],[-109.66,78.6]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-95.83,78.06],[-97.31,77.85],[-98.12,78.08],[-98.55,78.46],[-98.63,78.87],[-97.34,78.83],[-96.75,78.77],[-95.56,78.42],[-95.83,78.06]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-100.06,78.32],[-99.67,77.91],[-101.3,78.02],[-102.95,78.34],[-105.18,78.38],[-104.21,78.68],[-105.42,78.92],[-105.49,79.3],[-103.53,79.17],[-100.83,78.8],[-100.06,78.32]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[105.08,78.31],[99.44,77.92],[101.26,79.23],[102.09,79.35],[102.84,79.28],[105.37,78.71],[105.08,78.31]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[18.25,79.7],[21.54,78.96],[19.03,78.56],[18.47,77.83],[17.59,77.64],[17.12,76.81],[15.91,76.77],[13.76,77.38],[14.67,77.74],[13.17,78.02],[11.22,78.87],[10.44,79.65],[13.17,80.01],[13.72,79.66],[15.14,79.67],[15.52,80.02],[16.99,80.05],[18.25,79.7]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[25.45,80.41],[27.41,80.06],[25.92,79.52],[23.02,79.4],[20.08,79.57],[19.9,79.84],[18.46,79.86],[17.37,80.32],[20.46,80.6],[21.91,80.36],[22.92,80.66],[25.45,80.41]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[51.14,80.55],[49.79,80.42],[48.89,80.34],[48.75,80.18],[47.59,80.01],[46.5,80.25],[47.07,80.56],[44.85,80.59],[46.8,80.77],[48.32,80.78],[48.52,80.51],[49.1,80.75],[50.04,80.92],[51.52,80.7],[51.14,80.55]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[99.94,78.88],[97.76,78.76],[94.97,79.04],[93.31,79.43],[92.55,80.14],[91.18,80.34],[93.78,81.02],[95.94,81.25],[97.88,80.75],[100.19,79.78],[99.94,78.88]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-87.02,79.66],[-85.81,79.34],[-87.19,79.04],[-89.04,78.29],[-90.8,78.22],[-92.88,78.34],[-93.95,78.75],[-93.94,79.11],[-93.15,79.38],[-94.97,79.37],[-96.08,79.71],[-96.71,80.16],[-96.02,80.6],[-95.32,80.91],[-94.3,80.98],[-94.74,81.21],[-92.41,81.26],[-91.13,80.72],[-89.45,80.51],[-87.81,80.32],[-87.02,79.66]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-68.5,83.11],[-65.83,83.03],[-63.68,82.9],[-61.85,82.63],[-61.89,82.36],[-64.33,81.93],[-66.75,81.73],[-67.66,81.5],[-65.48,81.51],[-67.84,80.9],[-69.47,80.62],[-71.18,79.8],[-73.24,79.63],[-73.88,79.43],[-76.91,79.32],[-75.53,79.2],[-76.22,79.02],[-75.39,78.53],[-76.34,78.18],[-77.89,77.9],[-78.36,77.51],[-79.76,77.21],[-79.62,76.98],[-77.91,77.02],[-77.89,76.78],[-80.56,76.18],[-83.17,76.45],[-86.11,76.3],[-87.6,76.42],[-89.49,76.47],[-89.62,76.95],[-87.77,77.18],[-88.26,77.9],[-87.65,77.97],[-84.98,77.54],[-86.34,78.18],[-87.96,78.37],[-87.15,78.76],[-85.38,79.0],[-85.09,79.35],[-86.51,79.74],[-86.93,80.25],[-84.2,80.21],[-83.41,80.1],[-81.85,80.46],[-84.1,80.58],[-87.6,80.52],[-89.37,80.86],[-90.2,81.26],[-91.37,81.55],[-91.59,81.89],[-90.1,82.08],[-88.93,82.12],[-86.97,82.28],[-85.5,82.65],[-84.26,82.6],[-83.18,82.32],[-82.42,82.86],[-81.1,83.02],[-79.31,83.13],[-76.25,83.17],[-75.72,83.06],[-72.83,83.23],[-70.67,83.17],[-68.5,83.11]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-27.1,83.52],[-20.85,82.73],[-22.69,82.34],[-26.52,82.3],[-31.9,82.2],[-31.4,82.02],[-27.86,82.13],[-24.84,81.79],[-22.9,82.09],[-22.07,81.73],[-23.17,81.15],[-20.62,81.52],[-15.77,81.91],[-12.77,81.72],[-12.21,81.29],[-16.29,80.58],[-16.85,80.35],[-20.05,80.18],[-17.73,80.13],[-18.9,79.4],[-19.7,78.75],[-19.67,77.64],[-18.47,76.99],[-20.04,76.94],[-21.68,76.63],[-19.83,76.1],[-19.6,75.25],[-20.67,75.16],[-19.37,74.3],[-21.59,74.22],[-20.43,73.82],[-20.76,73.46],[-22.17,73.31],[-23.57,73.31],[-22.31,72.63],[-22.3,72.18],[-24.28,72.6],[-24.79,72.33],[-23.44,72.08],[-22.13,71.47],[-21.75,70.66],[-23.54,70.47],[-24.31,70.86],[-25.54,71.43],[-25.2,70.75],[-26.36,70.23],[-23.73,70.18],[-22.35,70.13],[-25.03,69.26],[-27.75,68.47],[-30.67,68.13],[-31.78,68.12],[-32.81,67.74],[-34.2,66.68],[-36.35,65.98],[-37.04,65.94],[-38.38,65.69],[-39.81,65.46],[-40.67,64.84],[-40.68,64.14],[-41.19,63.48],[-42.82,62.68],[-42.42,61.9],[-42.87,61.07],[-43.38,60.1],[-44.79,60.04],[-46.26,60.85],[-48.26,60.86],[-49.23,61.41],[-49.9,62.38],[-51.63,63.63],[-52.14,64.28],[-52.28,65.18],[-53.66,66.1],[-53.3,66.84],[-53.97,67.19],[-52.98,68.36],[-51.48,68.73],[-51.08,69.15],[-50.87,69.93],[-52.01,69.57],[-52.56,69.43],[-53.46,69.28],[-54.68,69.61],[-54.75,70.29],[-54.36,70.82],[-53.43,70.84],[-51.39,70.57],[-53.11,71.2],[-54.0,71.55],[-55,71.41],[-55.83,71.65],[-54.72,72.59],[-55.33,72.96],[-56.12,73.65],[-57.32,74.71],[-58.6,75.1],[-58.59,75.52],[-61.27,76.1],[-63.39,76.18],[-66.06,76.13],[-68.5,76.06],[-69.66,76.38],[-71.4,77.01],[-68.78,77.32],[-66.76,77.38],[-71.04,77.64],[-73.3,78.04],[-73.16,78.43],[-69.37,78.91],[-65.71,79.39],[-65.32,79.76],[-68.02,80.12],[-67.15,80.52],[-63.69,81.21],[-62.23,81.32],[-62.65,81.77],[-60.28,82.03],[-57.21,82.19],[-54.13,82.2],[-53.04,81.89],[-50.39,82.44],[-48.0,82.06],[-46.6,81.99],[-44.52,81.66],[-46.9,82.2],[-46.76,82.63],[-43.41,83.23],[-39.9,83.18],[-38.62,83.55],[-35.09,83.65],[-27.1,83.52]]]}}]}
|
frontend/src/components/CompareView.tsx
CHANGED
|
@@ -27,6 +27,7 @@ interface Props {
|
|
| 27 |
showOverlay: boolean;
|
| 28 |
loading?: boolean;
|
| 29 |
legendLabel?: string;
|
|
|
|
| 30 |
}
|
| 31 |
|
| 32 |
// Cover-fit: fill the whole stage edge-to-edge (no letterbox voids), cropping any overflow. The
|
|
@@ -89,6 +90,7 @@ export default function CompareView({
|
|
| 89 |
showOverlay,
|
| 90 |
loading = false,
|
| 91 |
legendLabel,
|
|
|
|
| 92 |
}: Props) {
|
| 93 |
const beforeDiv = useRef<HTMLDivElement>(null);
|
| 94 |
const afterDiv = useRef<HTMLDivElement>(null);
|
|
@@ -221,7 +223,7 @@ export default function CompareView({
|
|
| 221 |
<div className="hud chip legendchip">
|
| 222 |
<span className="sq" /> CHANGE MASK{legendLabel ? ` · ${legendLabel}` : ""}
|
| 223 |
</div>
|
| 224 |
-
<div className="hud chip gsd">
|
| 225 |
|
| 226 |
<div
|
| 227 |
className="swipe-divider"
|
|
|
|
| 27 |
showOverlay: boolean;
|
| 28 |
loading?: boolean;
|
| 29 |
legendLabel?: string;
|
| 30 |
+
gsdLabel?: string; // corner GSD chip; defaults to the aerial track's 0.5 m/px
|
| 31 |
}
|
| 32 |
|
| 33 |
// Cover-fit: fill the whole stage edge-to-edge (no letterbox voids), cropping any overflow. The
|
|
|
|
| 90 |
showOverlay,
|
| 91 |
loading = false,
|
| 92 |
legendLabel,
|
| 93 |
+
gsdLabel = "SOURCE GSD 0.5 m/px",
|
| 94 |
}: Props) {
|
| 95 |
const beforeDiv = useRef<HTMLDivElement>(null);
|
| 96 |
const afterDiv = useRef<HTMLDivElement>(null);
|
|
|
|
| 223 |
<div className="hud chip legendchip">
|
| 224 |
<span className="sq" /> CHANGE MASK{legendLabel ? ` · ${legendLabel}` : ""}
|
| 225 |
</div>
|
| 226 |
+
<div className="hud chip gsd">{gsdLabel}</div>
|
| 227 |
|
| 228 |
<div
|
| 229 |
className="swipe-divider"
|
frontend/src/components/Sentinel2View.tsx
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import maplibregl from "maplibre-gl";
|
| 2 |
+
import { useEffect, useMemo, useRef, useState } from "react";
|
| 3 |
+
import { api } from "../api";
|
| 4 |
+
import type { Sentinel2AOI } from "../types";
|
| 5 |
+
import landGeo from "../assets/land-110m.json";
|
| 6 |
+
import CompareView from "./CompareView";
|
| 7 |
+
|
| 8 |
+
// Vendored Natural Earth 110 m land (public domain, inlined at build — no CDN at runtime).
|
| 9 |
+
const LAND = landGeo as unknown as GeoJSON.FeatureCollection;
|
| 10 |
+
|
| 11 |
+
interface Props {
|
| 12 |
+
aois: Sentinel2AOI[];
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
// Minimal self-hosted locator: dark land polygons + a pin per AOI. Clicking a pin selects it.
|
| 16 |
+
// (A world basemap would need CDN tiles; the land outline keeps the app's no-CDN-at-runtime ethos.)
|
| 17 |
+
function LocatorMap({
|
| 18 |
+
aois,
|
| 19 |
+
selectedId,
|
| 20 |
+
onSelect,
|
| 21 |
+
}: {
|
| 22 |
+
aois: Sentinel2AOI[];
|
| 23 |
+
selectedId: string;
|
| 24 |
+
onSelect: (id: string) => void;
|
| 25 |
+
}) {
|
| 26 |
+
const div = useRef<HTMLDivElement>(null);
|
| 27 |
+
const map = useRef<maplibregl.Map | null>(null);
|
| 28 |
+
const markers = useRef<Record<string, HTMLButtonElement>>({});
|
| 29 |
+
|
| 30 |
+
useEffect(() => {
|
| 31 |
+
if (!div.current || map.current) return;
|
| 32 |
+
const m = new maplibregl.Map({
|
| 33 |
+
container: div.current,
|
| 34 |
+
style: { version: 8, sources: {}, layers: [] },
|
| 35 |
+
center: [60, 25],
|
| 36 |
+
zoom: 1,
|
| 37 |
+
attributionControl: false,
|
| 38 |
+
dragRotate: false,
|
| 39 |
+
pitchWithRotate: false,
|
| 40 |
+
renderWorldCopies: false,
|
| 41 |
+
});
|
| 42 |
+
map.current = m;
|
| 43 |
+
m.on("error", (e) => console.warn("locator map error:", e.error?.message ?? e));
|
| 44 |
+
|
| 45 |
+
m.on("load", () => {
|
| 46 |
+
m.addSource("land", { type: "geojson", data: LAND });
|
| 47 |
+
m.addLayer({ id: "land-fill", type: "fill", source: "land", paint: { "fill-color": "#141d2b" } });
|
| 48 |
+
m.addLayer({
|
| 49 |
+
id: "land-line",
|
| 50 |
+
type: "line",
|
| 51 |
+
source: "land",
|
| 52 |
+
paint: { "line-color": "#2b3a4f", "line-width": 0.6 },
|
| 53 |
+
});
|
| 54 |
+
|
| 55 |
+
// pins
|
| 56 |
+
for (const a of aois) {
|
| 57 |
+
const el = document.createElement("button");
|
| 58 |
+
el.className = "s2-pin";
|
| 59 |
+
el.type = "button";
|
| 60 |
+
el.title = a.title;
|
| 61 |
+
el.setAttribute("aria-label", a.title);
|
| 62 |
+
el.addEventListener("click", (ev) => {
|
| 63 |
+
ev.stopPropagation();
|
| 64 |
+
onSelect(a.id);
|
| 65 |
+
});
|
| 66 |
+
markers.current[a.id] = el;
|
| 67 |
+
new maplibregl.Marker({ element: el, anchor: "center" })
|
| 68 |
+
.setLngLat(a.center)
|
| 69 |
+
.addTo(m);
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
// frame all AOIs
|
| 73 |
+
if (aois.length) {
|
| 74 |
+
const b = new maplibregl.LngLatBounds();
|
| 75 |
+
aois.forEach((a) => b.extend(a.center));
|
| 76 |
+
m.fitBounds(b, { padding: 44, maxZoom: 3.4, duration: 0 });
|
| 77 |
+
}
|
| 78 |
+
});
|
| 79 |
+
|
| 80 |
+
return () => {
|
| 81 |
+
m.remove();
|
| 82 |
+
map.current = null;
|
| 83 |
+
markers.current = {};
|
| 84 |
+
};
|
| 85 |
+
// markers/pins depend on the AOI set; it is stable for the app's lifetime
|
| 86 |
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
| 87 |
+
}, [aois]);
|
| 88 |
+
|
| 89 |
+
// reflect the current selection on the pins
|
| 90 |
+
useEffect(() => {
|
| 91 |
+
for (const [id, el] of Object.entries(markers.current)) {
|
| 92 |
+
el.classList.toggle("on", id === selectedId);
|
| 93 |
+
}
|
| 94 |
+
}, [selectedId]);
|
| 95 |
+
|
| 96 |
+
return <div ref={div} className="s2-locator" />;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
const fmtCloud = (v: number | null | undefined) =>
|
| 100 |
+
v == null ? "—" : v < 0.1 ? "<0.1%" : `${v.toFixed(1)}%`;
|
| 101 |
+
|
| 102 |
+
export default function Sentinel2View({ aois }: Props) {
|
| 103 |
+
const [selectedId, setSelectedId] = useState<string>(aois[0]?.id ?? "");
|
| 104 |
+
const [showOverlay, setShowOverlay] = useState(true);
|
| 105 |
+
const [opacity, setOpacity] = useState(0.75);
|
| 106 |
+
const selected = useMemo(
|
| 107 |
+
() => aois.find((a) => a.id === selectedId) ?? aois[0],
|
| 108 |
+
[aois, selectedId],
|
| 109 |
+
);
|
| 110 |
+
|
| 111 |
+
useEffect(() => {
|
| 112 |
+
if (!selectedId && aois.length) setSelectedId(aois[0].id);
|
| 113 |
+
}, [aois, selectedId]);
|
| 114 |
+
|
| 115 |
+
if (!selected) return <div className="empty">No Sentinel-2 AOIs baked.</div>;
|
| 116 |
+
|
| 117 |
+
return (
|
| 118 |
+
<>
|
| 119 |
+
<div className="s2-honesty">
|
| 120 |
+
<span className="bicon">◐</span>
|
| 121 |
+
<span>
|
| 122 |
+
<b>Sentinel-2 · 10 m/px.</b> A deliberately modest, coarser-resolution track than the
|
| 123 |
+
high-accuracy aerial mode — <b>directionally correct on large real-world change, not a
|
| 124 |
+
fine-grained detector</b>. Curated real-world examples run through a Sentinel-2-native OSCD
|
| 125 |
+
model; aerial models do not transfer to 10 m.
|
| 126 |
+
</span>
|
| 127 |
+
</div>
|
| 128 |
+
<div className="s2-body">
|
| 129 |
+
<aside className="rail">
|
| 130 |
+
<section className="sec">
|
| 131 |
+
<h2>
|
| 132 |
+
<span className="idx">01</span> Locations <span className="rule" />
|
| 133 |
+
</h2>
|
| 134 |
+
<LocatorMap aois={aois} selectedId={selected.id} onSelect={setSelectedId} />
|
| 135 |
+
<div className="s2-list">
|
| 136 |
+
{aois.map((a) => (
|
| 137 |
+
<button
|
| 138 |
+
key={a.id}
|
| 139 |
+
className={`scene ${a.id === selected.id ? "on" : ""}`}
|
| 140 |
+
onClick={() => setSelectedId(a.id)}
|
| 141 |
+
title={a.description}
|
| 142 |
+
>
|
| 143 |
+
<span className="tag">{a.tile || "S2"}</span>
|
| 144 |
+
<span className="meta">
|
| 145 |
+
<span className="t">{a.title}</span>
|
| 146 |
+
<span className="bar">
|
| 147 |
+
<i style={{ width: `${Math.min(100, a.stats.changed_percent)}%` }} />
|
| 148 |
+
</span>
|
| 149 |
+
</span>
|
| 150 |
+
<span className="pct">{a.stats.changed_percent}%</span>
|
| 151 |
+
</button>
|
| 152 |
+
))}
|
| 153 |
+
</div>
|
| 154 |
+
<p className="scene-note">
|
| 155 |
+
Curated real-world change · click a pin or a site · the bar is model-<b>detected</b>{" "}
|
| 156 |
+
change area.
|
| 157 |
+
</p>
|
| 158 |
+
</section>
|
| 159 |
+
|
| 160 |
+
<section className="sec">
|
| 161 |
+
<h2>
|
| 162 |
+
<span className="idx">02</span> Acquisitions <span className="rule" />
|
| 163 |
+
</h2>
|
| 164 |
+
<div className="s2-acq">
|
| 165 |
+
<div className="row">
|
| 166 |
+
<span className="k">Before</span>
|
| 167 |
+
<span className="v">
|
| 168 |
+
{selected.date_before} <small>· cloud {fmtCloud(selected.cloud_before)}</small>
|
| 169 |
+
</span>
|
| 170 |
+
</div>
|
| 171 |
+
<div className="row">
|
| 172 |
+
<span className="k">After</span>
|
| 173 |
+
<span className="v">
|
| 174 |
+
{selected.date_after} <small>· cloud {fmtCloud(selected.cloud_after)}</small>
|
| 175 |
+
</span>
|
| 176 |
+
</div>
|
| 177 |
+
<div className="row">
|
| 178 |
+
<span className="k">MGRS tile</span>
|
| 179 |
+
<span className="v">{selected.tile}</span>
|
| 180 |
+
</div>
|
| 181 |
+
</div>
|
| 182 |
+
<p className="scene-note">{selected.description}</p>
|
| 183 |
+
</section>
|
| 184 |
+
|
| 185 |
+
<section className="sec">
|
| 186 |
+
<h2>
|
| 187 |
+
<span className="idx">03</span> Change overlay <span className="rule" />
|
| 188 |
+
</h2>
|
| 189 |
+
<div className="toggle">
|
| 190 |
+
<span className="lbl">Show detected change</span>
|
| 191 |
+
<div
|
| 192 |
+
className={`sw ${showOverlay ? "on" : ""}`}
|
| 193 |
+
role="switch"
|
| 194 |
+
aria-checked={showOverlay}
|
| 195 |
+
onClick={() => setShowOverlay((v) => !v)}
|
| 196 |
+
/>
|
| 197 |
+
</div>
|
| 198 |
+
<div className="slabel">
|
| 199 |
+
Opacity <b>{Math.round(opacity * 100)}%</b>
|
| 200 |
+
</div>
|
| 201 |
+
<input
|
| 202 |
+
type="range"
|
| 203 |
+
min={0}
|
| 204 |
+
max={1}
|
| 205 |
+
step={0.01}
|
| 206 |
+
value={opacity}
|
| 207 |
+
style={{ ["--fill" as string]: `${opacity * 100}%` }}
|
| 208 |
+
onChange={(e) => setOpacity(Number(e.target.value))}
|
| 209 |
+
/>
|
| 210 |
+
<div className="legend">
|
| 211 |
+
<span className="sq" /> Detected change · thr {selected.threshold.toFixed(3)}
|
| 212 |
+
</div>
|
| 213 |
+
</section>
|
| 214 |
+
|
| 215 |
+
<section className="sec">
|
| 216 |
+
<h2>
|
| 217 |
+
<span className="idx">04</span> Analysis <span className="rule" />
|
| 218 |
+
</h2>
|
| 219 |
+
<div className="readout">
|
| 220 |
+
<div className="row">
|
| 221 |
+
<span className="k">Area changed</span>
|
| 222 |
+
<span className="v">
|
| 223 |
+
{selected.stats.changed_percent}
|
| 224 |
+
<small>%</small>
|
| 225 |
+
</span>
|
| 226 |
+
</div>
|
| 227 |
+
<div className="row">
|
| 228 |
+
<span className="k">Mean conf · changed</span>
|
| 229 |
+
<span className="v">{selected.stats.mean_confidence_changed.toFixed(3)}</span>
|
| 230 |
+
</div>
|
| 231 |
+
<div className="row">
|
| 232 |
+
<span className="k">Changed pixels</span>
|
| 233 |
+
<span className="v">{selected.stats.changed_pixels.toLocaleString()}</span>
|
| 234 |
+
</div>
|
| 235 |
+
<div className="row">
|
| 236 |
+
<span className="k">Detection grid</span>
|
| 237 |
+
<span className="v">
|
| 238 |
+
{selected.n_tiles}
|
| 239 |
+
<small>× {selected.input_size}px</small>
|
| 240 |
+
</span>
|
| 241 |
+
</div>
|
| 242 |
+
</div>
|
| 243 |
+
<div className="gauge">
|
| 244 |
+
<div className="track">
|
| 245 |
+
<i style={{ width: `${Math.min(100, selected.stats.changed_percent)}%` }} />
|
| 246 |
+
</div>
|
| 247 |
+
<div className="cap">
|
| 248 |
+
<span>0%</span>
|
| 249 |
+
<span>AREA CHANGED</span>
|
| 250 |
+
<span>100%</span>
|
| 251 |
+
</div>
|
| 252 |
+
</div>
|
| 253 |
+
<p className="readout-caption">
|
| 254 |
+
OSCD Sentinel-2 model · precomputed offline, served from cache · thr{" "}
|
| 255 |
+
{selected.threshold.toFixed(3)}
|
| 256 |
+
</p>
|
| 257 |
+
</section>
|
| 258 |
+
</aside>
|
| 259 |
+
|
| 260 |
+
<main className="stage">
|
| 261 |
+
<div className="viewer">
|
| 262 |
+
<CompareView
|
| 263 |
+
key={selected.id}
|
| 264 |
+
beforeUrl={api.sentinel2ImageUrl(selected.id, "before")}
|
| 265 |
+
afterUrl={api.sentinel2ImageUrl(selected.id, "after")}
|
| 266 |
+
overlayUrl={api.sentinel2ImageUrl(selected.id, "overlay")}
|
| 267 |
+
overlayOpacity={opacity}
|
| 268 |
+
showOverlay={showOverlay}
|
| 269 |
+
legendLabel="OSCD · S2 10 m"
|
| 270 |
+
gsdLabel="SOURCE GSD 10 m/px"
|
| 271 |
+
/>
|
| 272 |
+
</div>
|
| 273 |
+
|
| 274 |
+
<footer className="telemetry">
|
| 275 |
+
<div className="cell accent">
|
| 276 |
+
<span className="k">SITE</span> <b>{selected.title}</b>
|
| 277 |
+
</div>
|
| 278 |
+
<div className="cell hide-narrow">
|
| 279 |
+
<span className="k">TILE</span> <b>{selected.tile}</b>
|
| 280 |
+
</div>
|
| 281 |
+
<div className="cell">
|
| 282 |
+
<span className="k">THR</span> <b>{selected.threshold.toFixed(3)}</b>
|
| 283 |
+
</div>
|
| 284 |
+
<div className="spacer" />
|
| 285 |
+
<div className="cell hide-narrow">
|
| 286 |
+
<span className="k">BANDS</span> <b>RGBN</b>
|
| 287 |
+
</div>
|
| 288 |
+
<div className="cell">
|
| 289 |
+
<span className="k">GSD</span> <b>10 m/px</b>
|
| 290 |
+
</div>
|
| 291 |
+
<div className="cell">
|
| 292 |
+
<span className="k">MODEL</span> <b>OSCD</b>
|
| 293 |
+
</div>
|
| 294 |
+
</footer>
|
| 295 |
+
</main>
|
| 296 |
+
</div>
|
| 297 |
+
</>
|
| 298 |
+
);
|
| 299 |
+
}
|
frontend/src/styles.css
CHANGED
|
@@ -294,6 +294,7 @@ body {
|
|
| 294 |
min-width: 0;
|
| 295 |
}
|
| 296 |
.scene .meta .t {
|
|
|
|
| 297 |
font-size: 12.5px;
|
| 298 |
font-weight: 500;
|
| 299 |
white-space: nowrap;
|
|
@@ -1002,7 +1003,8 @@ input[type="range"]::-moz-range-thumb {
|
|
| 1002 |
body {
|
| 1003 |
overflow: auto;
|
| 1004 |
}
|
| 1005 |
-
.body
|
|
|
|
| 1006 |
flex-direction: column;
|
| 1007 |
}
|
| 1008 |
.rail {
|
|
@@ -1014,3 +1016,114 @@ input[type="range"]::-moz-range-thumb {
|
|
| 1014 |
min-height: 62vh;
|
| 1015 |
}
|
| 1016 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
min-width: 0;
|
| 295 |
}
|
| 296 |
.scene .meta .t {
|
| 297 |
+
display: block; /* required for overflow/ellipsis to clip long titles (S2 sites) */
|
| 298 |
font-size: 12.5px;
|
| 299 |
font-weight: 500;
|
| 300 |
white-space: nowrap;
|
|
|
|
| 1003 |
body {
|
| 1004 |
overflow: auto;
|
| 1005 |
}
|
| 1006 |
+
.body,
|
| 1007 |
+
.s2-body {
|
| 1008 |
flex-direction: column;
|
| 1009 |
}
|
| 1010 |
.rail {
|
|
|
|
| 1016 |
min-height: 62vh;
|
| 1017 |
}
|
| 1018 |
}
|
| 1019 |
+
|
| 1020 |
+
/* ============================================================================
|
| 1021 |
+
Sentinel-2 (Track B) tab
|
| 1022 |
+
========================================================================== */
|
| 1023 |
+
.s2-honesty {
|
| 1024 |
+
flex: none;
|
| 1025 |
+
display: flex;
|
| 1026 |
+
align-items: flex-start;
|
| 1027 |
+
gap: 10px;
|
| 1028 |
+
padding: 9px 20px;
|
| 1029 |
+
font-size: 12px;
|
| 1030 |
+
line-height: 1.5;
|
| 1031 |
+
color: var(--text-dim);
|
| 1032 |
+
background: linear-gradient(90deg, rgba(63, 208, 230, 0.06), transparent 70%);
|
| 1033 |
+
border-bottom: 1px solid var(--line);
|
| 1034 |
+
}
|
| 1035 |
+
.s2-honesty .bicon {
|
| 1036 |
+
color: var(--accent);
|
| 1037 |
+
font-size: 13px;
|
| 1038 |
+
line-height: 1.4;
|
| 1039 |
+
flex: none;
|
| 1040 |
+
}
|
| 1041 |
+
.s2-honesty b {
|
| 1042 |
+
color: var(--text);
|
| 1043 |
+
font-weight: 600;
|
| 1044 |
+
}
|
| 1045 |
+
|
| 1046 |
+
.s2-body {
|
| 1047 |
+
flex: 1;
|
| 1048 |
+
display: flex;
|
| 1049 |
+
min-height: 0;
|
| 1050 |
+
}
|
| 1051 |
+
|
| 1052 |
+
/* locator map */
|
| 1053 |
+
.s2-locator {
|
| 1054 |
+
height: 188px;
|
| 1055 |
+
border-radius: var(--radius);
|
| 1056 |
+
border: 1px solid var(--line);
|
| 1057 |
+
background: #070a10;
|
| 1058 |
+
overflow: hidden;
|
| 1059 |
+
position: relative;
|
| 1060 |
+
}
|
| 1061 |
+
.s2-locator .maplibregl-canvas {
|
| 1062 |
+
cursor: default;
|
| 1063 |
+
}
|
| 1064 |
+
.s2-pin {
|
| 1065 |
+
width: 12px;
|
| 1066 |
+
height: 12px;
|
| 1067 |
+
padding: 0;
|
| 1068 |
+
border: 0;
|
| 1069 |
+
border-radius: 50%;
|
| 1070 |
+
cursor: pointer;
|
| 1071 |
+
background: rgba(63, 208, 230, 0.35);
|
| 1072 |
+
box-shadow:
|
| 1073 |
+
0 0 0 1.5px var(--accent),
|
| 1074 |
+
0 0 8px rgba(63, 208, 230, 0.4);
|
| 1075 |
+
transition:
|
| 1076 |
+
transform 0.15s ease,
|
| 1077 |
+
background 0.15s ease;
|
| 1078 |
+
}
|
| 1079 |
+
.s2-pin:hover {
|
| 1080 |
+
transform: scale(1.2);
|
| 1081 |
+
}
|
| 1082 |
+
.s2-pin.on {
|
| 1083 |
+
width: 15px;
|
| 1084 |
+
height: 15px;
|
| 1085 |
+
background: var(--signal);
|
| 1086 |
+
box-shadow:
|
| 1087 |
+
0 0 0 2px #fff,
|
| 1088 |
+
0 0 12px rgba(255, 140, 80, 0.7);
|
| 1089 |
+
}
|
| 1090 |
+
|
| 1091 |
+
.s2-list {
|
| 1092 |
+
margin-top: 12px;
|
| 1093 |
+
display: flex;
|
| 1094 |
+
flex-direction: column;
|
| 1095 |
+
gap: 6px;
|
| 1096 |
+
}
|
| 1097 |
+
|
| 1098 |
+
/* acquisitions block — a compact readout with left-aligned date/text values */
|
| 1099 |
+
.s2-acq {
|
| 1100 |
+
font-family: var(--mono);
|
| 1101 |
+
}
|
| 1102 |
+
.s2-acq .row {
|
| 1103 |
+
display: flex;
|
| 1104 |
+
align-items: baseline;
|
| 1105 |
+
justify-content: space-between;
|
| 1106 |
+
gap: 10px;
|
| 1107 |
+
padding: 8px 0;
|
| 1108 |
+
border-bottom: 1px solid var(--line-2);
|
| 1109 |
+
}
|
| 1110 |
+
.s2-acq .row:last-child {
|
| 1111 |
+
border-bottom: 0;
|
| 1112 |
+
}
|
| 1113 |
+
.s2-acq .k {
|
| 1114 |
+
font-size: 10.5px;
|
| 1115 |
+
letter-spacing: 0.06em;
|
| 1116 |
+
text-transform: uppercase;
|
| 1117 |
+
color: var(--text-faint);
|
| 1118 |
+
flex: none;
|
| 1119 |
+
}
|
| 1120 |
+
.s2-acq .v {
|
| 1121 |
+
font-size: 13px;
|
| 1122 |
+
color: var(--text);
|
| 1123 |
+
font-variant-numeric: tabular-nums;
|
| 1124 |
+
text-align: right;
|
| 1125 |
+
}
|
| 1126 |
+
.s2-acq .v small {
|
| 1127 |
+
font-size: 10.5px;
|
| 1128 |
+
color: var(--text-dim);
|
| 1129 |
+
}
|
frontend/src/types.ts
CHANGED
|
@@ -37,3 +37,27 @@ export interface PredictResult {
|
|
| 37 |
model_id: string;
|
| 38 |
n_tiles?: number;
|
| 39 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
model_id: string;
|
| 38 |
n_tiles?: number;
|
| 39 |
}
|
| 40 |
+
|
| 41 |
+
// A curated Sentinel-2 AOI: metadata + the offline-baked prediction summary (overlay served as a
|
| 42 |
+
// PNG file, not embedded). Everything here comes straight from the cache — no runtime inference.
|
| 43 |
+
export interface Sentinel2AOI {
|
| 44 |
+
id: string;
|
| 45 |
+
title: string;
|
| 46 |
+
description: string;
|
| 47 |
+
source: string;
|
| 48 |
+
tile: string;
|
| 49 |
+
center: [number, number]; // [lng, lat]
|
| 50 |
+
width: number;
|
| 51 |
+
height: number;
|
| 52 |
+
date_before: string;
|
| 53 |
+
date_after: string;
|
| 54 |
+
cloud_before: number;
|
| 55 |
+
cloud_after: number;
|
| 56 |
+
model_id: string;
|
| 57 |
+
threshold: number;
|
| 58 |
+
is_placeholder: boolean;
|
| 59 |
+
n_tiles: number;
|
| 60 |
+
input_size: number;
|
| 61 |
+
elapsed_ms: number;
|
| 62 |
+
stats: PredictStats;
|
| 63 |
+
}
|