auto-sync: 2026-05-04 11:20:01
This commit is contained in:
@@ -227,15 +227,15 @@ def haversine_m(lon1, lat1, lon2, lat2) -> float:
|
||||
def calc_route_stats(geometry: dict, conn) -> dict | None:
|
||||
"""
|
||||
Считает статистику покрытия маршрута по типам дорог.
|
||||
Оптимизированная версия: сэмплирует каждые ~500м, один запрос на сэмпл.
|
||||
geometry — GeoJSON LineString {"type":"LineString","coordinates":[[lon,lat],...]}
|
||||
Возвращает словарь с метрами и процентами по категориям.
|
||||
"""
|
||||
try:
|
||||
coords = geometry.get("coordinates", [])
|
||||
if len(coords) < 2:
|
||||
return None
|
||||
|
||||
# Считаем общую длину маршрута и шаг между точками
|
||||
# Считаем длины сегментов и общую длину
|
||||
total_len = 0.0
|
||||
seg_lengths = []
|
||||
for i in range(len(coords) - 1):
|
||||
@@ -246,12 +246,11 @@ def calc_route_stats(geometry: dict, conn) -> dict | None:
|
||||
if total_len < 1:
|
||||
return None
|
||||
|
||||
# Средний шаг между точками
|
||||
avg_step = total_len / len(seg_lengths)
|
||||
|
||||
# Сколько точек пропускать чтобы получить ~100м сегменты
|
||||
# Минимум 1 точка, максимум 50
|
||||
step = max(1, min(50, int(round(100.0 / avg_step)))) if avg_step > 0 else 5
|
||||
# Сэмплируем каждые ~500м (не чаще чем каждые 5 точек)
|
||||
# Для маршрута 100км → ~200 сэмплов, для 500км → ~1000 сэмплов
|
||||
avg_step = total_len / max(len(seg_lengths), 1)
|
||||
# Шаг в точках для ~500м интервала
|
||||
pts_per_500m = max(5, int(round(500.0 / avg_step))) if avg_step > 0 else 20
|
||||
|
||||
cur = conn.cursor()
|
||||
|
||||
@@ -262,10 +261,14 @@ def calc_route_stats(geometry: dict, conn) -> dict | None:
|
||||
"asphalt_m": 0.0,
|
||||
}
|
||||
|
||||
# Проходим по маршруту с шагом step, берём среднюю точку сегмента
|
||||
# Кэш результатов по ячейкам сетки ~0.01° (~1км)
|
||||
# Чтобы не делать повторные запросы для близких точек
|
||||
grid_cache: dict = {}
|
||||
|
||||
i = 0
|
||||
while i < len(coords) - 1:
|
||||
end_i = min(i + step, len(coords) - 1)
|
||||
end_i = min(i + pts_per_500m, len(coords) - 1)
|
||||
|
||||
# Средняя точка сегмента
|
||||
mid_lon = (coords[i][0] + coords[end_i][0]) / 2
|
||||
mid_lat = (coords[i][1] + coords[end_i][1]) / 2
|
||||
@@ -273,45 +276,50 @@ def calc_route_stats(geometry: dict, conn) -> dict | None:
|
||||
# Длина этого сегмента
|
||||
seg_len = sum(seg_lengths[i:end_i])
|
||||
|
||||
# Bbox для поиска ближайшего трека (~500м вокруг точки)
|
||||
delta = 0.005 # ~500м
|
||||
try:
|
||||
cur.execute("""
|
||||
SELECT highway_type, track_type, length_m
|
||||
FROM trails
|
||||
WHERE min_lon <= ? AND max_lon >= ?
|
||||
AND min_lat <= ? AND max_lat >= ?
|
||||
ORDER BY ABS(min_lon - ?) + ABS(min_lat - ?)
|
||||
LIMIT 1
|
||||
""", (
|
||||
mid_lon + delta, mid_lon - delta,
|
||||
mid_lat + delta, mid_lat - delta,
|
||||
mid_lon, mid_lat
|
||||
))
|
||||
row = cur.fetchone()
|
||||
except Exception:
|
||||
row = None
|
||||
# Ключ кэша: ячейка 0.01° (~1км)
|
||||
grid_key = (round(mid_lon * 100), round(mid_lat * 100))
|
||||
|
||||
if row:
|
||||
hw = (row["highway_type"] or "").lower()
|
||||
tt = (row["track_type"] or "").lower()
|
||||
if hw == "track":
|
||||
if tt in ("grade1", "grade2"):
|
||||
stats["track_lev12_m"] += seg_len
|
||||
else:
|
||||
# grade3/4/5 или NULL
|
||||
stats["track_lev345_m"] += seg_len
|
||||
elif hw in ("path", "bridleway", "footway"):
|
||||
stats["path_m"] += seg_len
|
||||
else:
|
||||
stats["asphalt_m"] += seg_len
|
||||
if grid_key in grid_cache:
|
||||
hw, tt = grid_cache[grid_key]
|
||||
else:
|
||||
# Bbox ~300м вокруг точки
|
||||
delta = 0.003
|
||||
try:
|
||||
cur.execute("""
|
||||
SELECT highway_type, track_type
|
||||
FROM trails
|
||||
WHERE min_lon <= ? AND max_lon >= ?
|
||||
AND min_lat <= ? AND max_lat >= ?
|
||||
ORDER BY ABS((min_lon + max_lon) / 2.0 - ?) +
|
||||
ABS((min_lat + max_lat) / 2.0 - ?)
|
||||
LIMIT 1
|
||||
""", (
|
||||
mid_lon + delta, mid_lon - delta,
|
||||
mid_lat + delta, mid_lat - delta,
|
||||
mid_lon, mid_lat
|
||||
))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
hw = (row["highway_type"] or "").lower()
|
||||
tt = (row["track_type"] or "").lower()
|
||||
else:
|
||||
hw, tt = "asphalt", ""
|
||||
except Exception:
|
||||
hw, tt = "asphalt", ""
|
||||
grid_cache[grid_key] = (hw, tt)
|
||||
|
||||
if hw == "track":
|
||||
if tt in ("grade1", "grade2"):
|
||||
stats["track_lev12_m"] += seg_len
|
||||
else:
|
||||
stats["track_lev345_m"] += seg_len
|
||||
elif hw in ("path", "bridleway", "footway"):
|
||||
stats["path_m"] += seg_len
|
||||
else:
|
||||
# Нет данных — считаем асфальтом
|
||||
stats["asphalt_m"] += seg_len
|
||||
|
||||
i = end_i
|
||||
|
||||
# Считаем итоговую длину из статистики
|
||||
computed_total = (
|
||||
stats["track_lev12_m"] + stats["track_lev345_m"] +
|
||||
stats["path_m"] + stats["asphalt_m"]
|
||||
@@ -325,15 +333,15 @@ def calc_route_stats(geometry: dict, conn) -> dict | None:
|
||||
dirt_total = stats["track_lev12_m"] + stats["track_lev345_m"] + stats["path_m"]
|
||||
|
||||
return {
|
||||
"track_lev12_m": round(stats["track_lev12_m"]),
|
||||
"track_lev345_m": round(stats["track_lev345_m"]),
|
||||
"path_m": round(stats["path_m"]),
|
||||
"asphalt_m": round(stats["asphalt_m"]),
|
||||
"track_lev12_pct": pct(stats["track_lev12_m"]),
|
||||
"track_lev12_m": round(stats["track_lev12_m"]),
|
||||
"track_lev345_m": round(stats["track_lev345_m"]),
|
||||
"path_m": round(stats["path_m"]),
|
||||
"asphalt_m": round(stats["asphalt_m"]),
|
||||
"track_lev12_pct": pct(stats["track_lev12_m"]),
|
||||
"track_lev345_pct": pct(stats["track_lev345_m"]),
|
||||
"path_pct": pct(stats["path_m"]),
|
||||
"asphalt_pct": pct(stats["asphalt_m"]),
|
||||
"dirt_total_pct": pct(dirt_total),
|
||||
"path_pct": pct(stats["path_m"]),
|
||||
"asphalt_pct": pct(stats["asphalt_m"]),
|
||||
"dirt_total_pct": pct(dirt_total),
|
||||
}
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user