From d1e0267184a66cd3a67fed3caabde9e84b4ac3c0 Mon Sep 17 00:00:00 2001 From: Stream Date: Mon, 4 May 2026 11:20:01 +0300 Subject: [PATCH] auto-sync: 2026-05-04 11:20:01 --- tasks/enduro-trails/prototype/app.py | 110 ++++++++++++++------------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/tasks/enduro-trails/prototype/app.py b/tasks/enduro-trails/prototype/app.py index 4c7bc83..c390236 100644 --- a/tasks/enduro-trails/prototype/app.py +++ b/tasks/enduro-trails/prototype/app.py @@ -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