Umar666 commited on
Commit
6dad9a7
·
verified ·
1 Parent(s): d1ac326

M5: add curated Sentinel-2 (Track-B) tab — baked OSCD AOIs, cache-served

Browse files
.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(repo_id=repo, repo_type="model", local_dir=str(BUNDLES_DIR))
 
 
 
 
 
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

  • SHA256: 9720721a3465baf0068b95bf2d8b1d4a3eff5f622779df308bc250216e768c0a
  • Pointer size: 132 Bytes
  • Size of remote file: 1.98 MB
backend/data/sentinel2/beijing_daxing/before.png ADDED

Git LFS Details

  • SHA256: 5690e5cd6e5f06036df0b32690f2d97f277513cfcbb6ead1b161d4b37aab4cf2
  • Pointer size: 132 Bytes
  • Size of remote file: 1.69 MB
backend/data/sentinel2/beijing_daxing/overlay.png ADDED
backend/data/sentinel2/bhadla_solar/after.png ADDED

Git LFS Details

  • SHA256: 1dc34627fc988a25f034885c946001f1bfce2ec3c8fdb9e030f6f3c4e3a62c8a
  • Pointer size: 132 Bytes
  • Size of remote file: 3.7 MB
backend/data/sentinel2/bhadla_solar/before.png ADDED

Git LFS Details

  • SHA256: 985d2e2b5bfdf8aa9148fd9d0fce44c773cfe72cd63cd6cf6a488f740bc0bd31
  • Pointer size: 132 Bytes
  • Size of remote file: 3.87 MB
backend/data/sentinel2/bhadla_solar/overlay.png ADDED
backend/data/sentinel2/dubai_deira/after.png ADDED

Git LFS Details

  • SHA256: 9b2aad9cb1ecacc9f70bdd8e6ab180beade9258e9e68bcab74e65747700db320
  • Pointer size: 132 Bytes
  • Size of remote file: 2.3 MB
backend/data/sentinel2/dubai_deira/before.png ADDED

Git LFS Details

  • SHA256: a106aaa5278b7a877854296b039b3a3f5cc2084aaa2a021a3452b970a1a3d4b1
  • Pointer size: 132 Bytes
  • Size of remote file: 2.27 MB
backend/data/sentinel2/dubai_deira/overlay.png ADDED
backend/data/sentinel2/egypt_newcapital/after.png ADDED

Git LFS Details

  • SHA256: c048fe919d208343a4d90ec3a876860dc9195cc0c888fe35c04a5a743ccf39df
  • Pointer size: 132 Bytes
  • Size of remote file: 3.71 MB
backend/data/sentinel2/egypt_newcapital/before.png ADDED

Git LFS Details

  • SHA256: 8547f49f9bc9991e4367d18fced5be7481f54c4396949e9ddb4faf7592ec6ff0
  • Pointer size: 132 Bytes
  • Size of remote file: 3.31 MB
backend/data/sentinel2/egypt_newcapital/overlay.png ADDED
backend/data/sentinel2/gerd_reservoir/after.png ADDED

Git LFS Details

  • SHA256: 8b6dd43d99d88ac797af9d21bc9f3a698f54f9d6a19e5a12708d8b7c20b556c7
  • Pointer size: 132 Bytes
  • Size of remote file: 4.55 MB
backend/data/sentinel2/gerd_reservoir/before.png ADDED

Git LFS Details

  • SHA256: 5ba62e693ee845150793557637e8f5a3c111c5bc435a284a56b7a1f107a299e9
  • Pointer size: 132 Bytes
  • Size of remote file: 4.84 MB
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 type { CuratedPair, ModelSummary, PredictResult } from "./types";
 
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
- TRACK&nbsp;A · AERIAL 0.5&nbsp;M/PX · <b>ONNX · CPU</b>
 
 
 
 
 
 
 
 
99
  </div>
100
  </div>
101
  </div>
102
  <nav className="nav">
103
  <button className={`tab ${tab === "curated" ? "on" : ""}`} onClick={() => setTab("curated")}>
104
- Curated
 
 
 
 
 
 
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&nbsp;B · SENTINEL-2 10&nbsp;M/PX · <b>ONNX · CPU</b>
108
+ </>
109
+ ) : (
110
+ <>
111
+ TRACK&nbsp;A · AERIAL 0.5&nbsp;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">SOURCE GSD 0.5&nbsp;m/px</div>
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
+ }