added conopy

This commit is contained in:
2026-06-24 09:12:33 +03:00
parent 7ec7a51648
commit 4989face48
+30 -5
View File
@@ -8,13 +8,15 @@ from collections.abc import Iterable
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
from urllib.request import urlopen, urlretrieve from urllib.request import Request, urlopen
META_CHM_V2_BASE_URL = ( META_CHM_V2_INDEX_BASE_URL = (
"https://dataforgood-fb-data.s3.amazonaws.com/" "https://dataforgood-fb-data.s3.amazonaws.com/"
"forests/v2/global/dinov3_global_chm_v2_ml3" "forests/v2/global/dinov3_global_chm_v2_ml3"
) )
META_CHM_V2_INDEX_URL = f"{META_CHM_V2_BASE_URL}/tiles.geojson" META_CHM_V2_BASE_URL = "https://data.source.coop/tge-labs/meta-chm-v2/chm"
META_CHM_V2_INDEX_URL = f"{META_CHM_V2_INDEX_BASE_URL}/tiles.geojson"
USER_AGENT = "RadioApi canopy bootstrap/0.1"
def parse_bbox(raw: str) -> tuple[float, float, float, float]: def parse_bbox(raw: str) -> tuple[float, float, float, float]:
@@ -83,11 +85,28 @@ def _asset_url(feature: dict[str, Any], base_url: str = META_CHM_V2_BASE_URL) ->
return None return None
def validate_urls(urls: list[str], sample_size: int = 2) -> None:
for url in urls[:sample_size]:
request = Request(url, headers={"Range": "bytes=0-0", "User-Agent": USER_AGENT})
try:
with urlopen(request) as response:
if response.status >= 400:
raise SystemExit(f"Canopy URL is not reachable: {url} ({response.status})")
except HTTPError as exc:
raise SystemExit(
f"Canopy URL is not reachable: {url} ({exc.code}). "
"Check --index-url/--urls-file or the dataset URL template."
) from exc
except URLError as exc:
raise SystemExit(f"Canopy URL validation failed: {url}: {exc}") from exc
def load_index(index_url: str) -> dict[str, Any]: def load_index(index_url: str) -> dict[str, Any]:
path = Path(index_url) path = Path(index_url)
if path.exists(): if path.exists():
return json.loads(path.read_text(encoding="utf-8")) return json.loads(path.read_text(encoding="utf-8"))
with urlopen(index_url) as response: request = Request(index_url, headers={"User-Agent": USER_AGENT})
with urlopen(request) as response:
return json.loads(response.read().decode("utf-8")) return json.loads(response.read().decode("utf-8"))
@@ -138,7 +157,9 @@ def download_urls(urls: list[str], output_dir: Path, overwrite: bool) -> tuple[i
print(f"download {url}") print(f"download {url}")
try: try:
urlretrieve(url, destination) request = Request(url, headers={"User-Agent": USER_AGENT})
with urlopen(request) as response, destination.open("wb") as output:
output.write(response.read())
except (HTTPError, URLError) as exc: except (HTTPError, URLError) as exc:
failed.append(f"{url}: {exc}") failed.append(f"{url}: {exc}")
if destination.exists(): if destination.exists():
@@ -156,6 +177,7 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("--source", default="meta-chm-v2", choices=["meta-chm-v2"]) parser.add_argument("--source", default="meta-chm-v2", choices=["meta-chm-v2"])
parser.add_argument("--index-url", default=META_CHM_V2_INDEX_URL) parser.add_argument("--index-url", default=META_CHM_V2_INDEX_URL)
parser.add_argument("--urls-file", type=Path) parser.add_argument("--urls-file", type=Path)
parser.add_argument("--skip-url-check", action="store_true")
parser.add_argument("--overwrite", action="store_true") parser.add_argument("--overwrite", action="store_true")
return parser.parse_args() return parser.parse_args()
@@ -174,6 +196,9 @@ def main() -> None:
if not urls: if not urls:
raise SystemExit("No canopy tiles intersect requested AOI") raise SystemExit("No canopy tiles intersect requested AOI")
if not args.skip_url_check:
validate_urls(urls)
downloaded, skipped, failed = download_urls(urls, args.output_dir, args.overwrite) downloaded, skipped, failed = download_urls(urls, args.output_dir, args.overwrite)
print(f"downloaded={downloaded} skipped={skipped} failed={len(failed)}") print(f"downloaded={downloaded} skipped={skipped} failed={len(failed)}")
if failed: if failed: