All checks were successful
Калибровка существующих tier-таблиц `build_gps_mvt` / `_simplify_coords` (ADR-016), чтобы при первом открытии карты пользователь видел общее покрытие сети треков, а не пустую подложку. Backend (src/api/gps_tracks/mvt.py): - build_gps_mvt: добавлены тиры z<=5 (min_length=10 км, limit=1500) и z=6 (5 км / 2000); z=7+ — без изменений (регрессия). - _simplify_coords: tolerance для z=6 = 0.018° (~2 км), для z<=5 = 0.04° (~4 км); z=7+ не меняется. Frontend: - GPS_TRACKS_MIN_ZOOM понижен с 8 до 5; vector-source.minzoom подхватывает константу автоматически. - line-width / halo получили stop на z=5 (0.8 / 1.8 CSS-px), чтобы линия была читаема на любом DPR. - Hint #public-tracks-zoom-hint: «Зум 8+» → «Зум 5+». Тесты: - 8 unit zoom-tier (UT-Z5/6/7/8/12) — REQ-F-09. - 10 unit simplify (UT-SIMP-*) — REQ-F-10. - 9 integration endpoint z5-z7 (IT-Z5/6/7, CACHE, REGRESS) — REQ-F-11/12. - 2 perf (PERF-Z5-01/02; avg ~64 ms, p95 ~89 ms при 500 треках — ниже бюджета 200/500 ms по M-6) — REQ-F-13. Маркер @pytest.mark.perf, не в основном CI-gate. Контракт API /api/gps-tracks* не меняется (REQ-F-15); localStorage-ключи и конфиги тоже (REQ-F-16, F-18). Refs: ET-012 ADR: docs/work-items/ET-012/06-adr/ADR-016-z5-tiling-policy.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
187 lines
8.9 KiB
Python
187 lines
8.9 KiB
Python
"""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 # без упрощения
|