"""Unit-тесты ``_simplify_coords`` (ET-012, ADR-016). Покрытие из 04-test-plan.yaml / 02-trz.md REQ-F-10: UT-SIMP-Z5-01 — прямая 100 точек на z=5 → ≤ 5 точек. UT-SIMP-Z5-02 — зигзаг 100 точек, амплитуда < tolerance → 2 точки. UT-SIMP-Z6-01 — зигзаг с амплитудой ~5 км на z=6 → > 5 точек. UT-SIMP-Z7-01 — регрессия: tolerance = 0.008. UT-SIMP-Z10-01 — регрессия: tolerance = 0.0005. UT-SIMP-Z12-01 — регрессия: без упрощения. UT-SIMP-EDGE-01 — < 3 точек возвращаются без изменений. UT-SIMP-EDGE-02 — DP схлопнул < 2 точек → возвращаем оригинал. Замечание о масштабе: tolerance в градусах WGS84. На широте 55° с.ш. 1° долготы ≈ 64 км. Для зигзага амплитуда задаётся в градусах широты, 1° широты ≈ 111 км. """ from src.api.gps_tracks.mvt import _simplify_coords # ─── UT-SIMP-Z5-01: прямая → ≤ 5 точек ────────────────────────────────────── def test_ut_simp_z5_01_straight_line_collapses(): """REQ-F-10 / UT-SIMP-Z5-01: 100 точек по прямой на z=5 → ≤ 5 точек. DP с большим tolerance схлопывает прямую до начала и конца. """ # ~ 10 км по диагонали (шаг 0.001° × 100 = 0.1° ≈ 6.4 км по lon, 11 км по lat) coords = [(37.0 + i * 0.001, 55.0 + i * 0.001) for i in range(100)] result = _simplify_coords(coords, z=5) assert len(result) <= 5 assert len(result) >= 2 # не схлопывается до 1 или 0 # Концы сохранены assert result[0] == coords[0] assert result[-1] == coords[-1] # ─── UT-SIMP-Z5-02: зигзаг с амплитудой < tolerance → 2 точки ─────────────── def test_ut_simp_z5_02_zigzag_below_tolerance_collapses_to_endpoints(): """REQ-F-10 / UT-SIMP-Z5-02: зигзаг амплитудой ~0.01° (~1 км) на z=5. tolerance на z<=5 = 0.04° (~4 км по lon на 55° с.ш.), зигзаги меньше tolerance — схлопываются до концов. """ coords = [] base_lon = 37.0 base_lat = 55.0 for i in range(100): # Лонгитуда монотонно растёт, чтобы DP видел общее направление. # Латитуда зигзагит с амплитудой 0.01° (~1.1 км по широте). lon = base_lon + i * 0.002 lat = base_lat + (0.01 if i % 2 else -0.01) coords.append((lon, lat)) result = _simplify_coords(coords, z=5) # DP при таком tolerance оставит только начало и конец прямой. assert len(result) == 2 assert result[0] == coords[0] assert result[-1] == coords[-1] # ─── UT-SIMP-Z6-01: зигзаг 5 км на z=6 → видны крупные пики ───────────────── def test_ut_simp_z6_01_large_zigzag_keeps_peaks(): """REQ-F-10 / UT-SIMP-Z6-01: на z=6 (tolerance ~2 км) зигзаг 5 км оставляет крупные пики (> 5 точек). """ coords = [] base_lon = 37.0 base_lat = 55.0 for i in range(100): lon = base_lon + i * 0.005 lat = base_lat + (0.05 if i % 2 else -0.05) coords.append((lon, lat)) result = _simplify_coords(coords, z=6) assert len(result) > 5 # ─── UT-SIMP-Z7-01: регрессия — tolerance = 0.008 ─────────────────────────── def test_ut_simp_z7_01_regression_tolerance_unchanged(): """REQ-F-10 / UT-SIMP-Z7-01: tolerance на z=7 = 0.008 (как до ET-012). Контроль: на синтетике зигзаг с амплитудой 0.01° (выше tolerance 0.008°) — пики сохраняются (>5 точек), но число меньше, чем на z=6 (tolerance меньше). """ coords = [] base_lon = 37.0 base_lat = 55.0 for i in range(100): lon = base_lon + i * 0.002 lat = base_lat + (0.012 if i % 2 else -0.012) coords.append((lon, lat)) result_z7 = _simplify_coords(coords, z=7) # Зигзаг чуть больше tolerance → точки сохраняются. assert len(result_z7) > 2 # На z=10 с гораздо меньшим tolerance число сохранённых точек >= чем на z=7. result_z10 = _simplify_coords(coords, z=10) assert len(result_z10) >= len(result_z7) # ─── UT-SIMP-Z10-01: регрессия — tolerance = 0.0005 ───────────────────────── def test_ut_simp_z10_01_regression_fine_zigzag_kept(): """REQ-F-10 / UT-SIMP-Z10-01: tolerance на z=10 = 0.0005 (как до ET-012). Зигзаг с амплитудой 0.001° (~100 м) — выше tolerance, точки сохраняются. """ coords = [] base_lon = 37.0 base_lat = 55.0 for i in range(100): lon = base_lon + i * 0.0005 lat = base_lat + (0.001 if i % 2 else -0.001) coords.append((lon, lat)) result = _simplify_coords(coords, z=10) # На z=10 с tolerance 0.0005° зигзаг 0.001° сохраняет почти все точки. assert len(result) >= 50 # ─── UT-SIMP-Z12-01: регрессия — без упрощения ────────────────────────────── def test_ut_simp_z12_01_no_simplification(): """REQ-F-10 / UT-SIMP-Z12-01: на z=12 функция возвращает coords без изменений.""" coords = [(37.0 + i * 0.0001, 55.0 + i * 0.0001) for i in range(100)] result = _simplify_coords(coords, z=12) # Object identity preserved (return coords, не копия) assert result is coords def test_ut_simp_z12_01_high_zoom_no_simplification(): """REQ-F-10: на z>12 (например, z=15, z=22) — также без упрощения.""" coords = [(37.0 + i * 0.0001, 55.0 + i * 0.0001) for i in range(50)] assert _simplify_coords(coords, z=15) is coords assert _simplify_coords(coords, z=22) is coords # ─── UT-SIMP-EDGE-01: < 3 точек ───────────────────────────────────────────── def test_ut_simp_edge_01_two_points_returned_as_is(): """REQ-F-10 / UT-SIMP-EDGE-01: trace из 2 точек возвращается без изменений на любом z.""" coords = [(37.0, 55.0), (37.001, 55.001)] for z in (5, 6, 7, 8, 10, 12): result = _simplify_coords(coords, z) assert result == coords # ─── UT-SIMP-EDGE-02: вырожденный трек ────────────────────────────────────── def test_ut_simp_edge_02_degenerate_track_falls_back_to_original(): """REQ-F-10 / UT-SIMP-EDGE-02: 100 одинаковых точек. Shapely.simplify на дегенеративной геометрии может вернуть < 2 точек — функция должна fallback'нуть на оригинал, а не отдавать пустой список. """ coords = [(37.0, 55.0)] * 100 for z in (5, 6, 7, 8, 10): result = _simplify_coords(coords, z) # Минимум — оригинал, не пустой/одноточечный assert len(result) >= 2 # ─── Кросс-проверка: z=5 упрощает сильнее, чем z=6, чем z=7, чем z=10 ─────── def test_simp_tier_monotonic_for_complex_trace(): """Дополнительная проверка монотонности tolerance по зумам. На сложном треке (100 точек со случайной вариативностью) ожидается: len(simp(z=5)) <= len(simp(z=6)) <= len(simp(z=7)) <= len(simp(z=10)) <= len(simp(z=12)) == 100 """ # Детерминированный pseudo-noise через index (без random — стабильно в CI) coords = [] for i in range(100): lon = 37.0 + i * 0.003 + ((i * 7) % 13) * 0.0003 lat = 55.0 + i * 0.002 + ((i * 11) % 17) * 0.0004 coords.append((lon, lat)) n5 = len(_simplify_coords(coords, z=5)) n6 = len(_simplify_coords(coords, z=6)) n7 = len(_simplify_coords(coords, z=7)) n10 = len(_simplify_coords(coords, z=10)) n12 = len(_simplify_coords(coords, z=12)) assert n5 <= n6 <= n7 <= n10 <= n12 assert n12 == 100 # без упрощения