-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoccupied_layer.py
More file actions
87 lines (74 loc) · 3.88 KB
/
Copy pathoccupied_layer.py
File metadata and controls
87 lines (74 loc) · 3.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
"""
occupied_layer.py — слой оккупированных территорий / линии соприкосновения
поверх карты. Источник: общедоступный, ежедневно обновляемый GeoJSON-мираж
данных deepstatemap.live (репозиторий cyterat/deepstate-map-data на GitHub,
собирает данные из открытого OSINT-проекта Deep State UA).
Каждый день публикуется отдельный лёгкий файл (~50-100 КБ) с текущей формой
оккупированной территории (MultiPolygon). Если файл за сегодня ещё не
опубликован (обновление раз в сутки, ~03:00 UTC) — скрипт откатывается на
более ранние даты, пока не найдёт последний доступный.
Кэшируется на диск по дате — при повторном запуске в тот же день сеть не
трогается вообще.
"""
from __future__ import annotations
import datetime
import json
import urllib.request
from pathlib import Path
from urllib.error import HTTPError, URLError
BASE_URL = "https://raw.githubusercontent.com/cyterat/deepstate-map-data/main/data/deepstatemap_data_{date}.geojson"
def fetch_occupied_geojson(
cache_dir: str,
max_days_back: int = 6,
timeout: float = 8.0,
) -> tuple[dict | None, str | None]:
"""
Возвращает (geojson_dict, дата_в_формате_YYYYMMDD) для последнего
доступного дня, или (None, None), если ничего не нашлось (нет сети
и нет кэша). Сначала проверяет диск, потом лезет в сеть.
"""
cache_path_dir = Path(cache_dir) / "deepstate"
cache_path_dir.mkdir(parents=True, exist_ok=True)
today = datetime.date.today()
for delta in range(max_days_back):
d = today - datetime.timedelta(days=delta)
date_str = d.strftime("%Y%m%d")
cache_file = cache_path_dir / f"deepstatemap_data_{date_str}.geojson"
if cache_file.exists():
try:
return json.loads(cache_file.read_text(encoding="utf-8")), date_str
except (OSError, json.JSONDecodeError):
pass # повреждённый кэш — попробуем скачать заново
url = BASE_URL.format(date=date_str)
req = urllib.request.Request(
url, headers={"User-Agent": "MissionAnalyzer/1.0 (personal UAV mission-planning tool)"}
)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read()
data = json.loads(raw)
try:
cache_file.write_bytes(raw)
except OSError:
pass
return data, date_str
except (URLError, HTTPError, TimeoutError, OSError, json.JSONDecodeError):
continue # пробуем на день раньше
return None, None
def extract_polygons(geojson: dict) -> list:
"""
Возвращает список полигонов из GeoJSON. Каждый полигон — список колец
[[(lon,lat), ...], ...], где кольцо 0 — внешний контур, остальные —
дырки (дырки при отрисовке игнорируются, это только для наглядности,
не для точных измерений).
"""
polygons = []
for feat in geojson.get("features", []):
geom = feat.get("geometry") or {}
gtype = geom.get("type")
coords = geom.get("coordinates", [])
if gtype == "MultiPolygon":
polygons.extend(coords)
elif gtype == "Polygon":
polygons.append(coords)
return polygons