From bbed0e10827720e443a909519f3f9ca6e5d1b8b6 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Thu, 4 Jun 2026 06:29:41 +0000 Subject: [PATCH] feat(gps-tracks): lower public-tracks minzoom to z5 (ET-012) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Калибровка существующих 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) --- CHANGELOG.md | 15 + pyproject.toml | 3 +- src/api/gps_tracks/mvt.py | 36 +- src/web/gps_tracks.js | 25 +- src/web/index.html | 2 +- tests/api/test_gps_mvt_simplify.py | 186 +++++++++++ tests/api/test_gps_mvt_zoom_tiers.py | 257 ++++++++++++++ tests/integration/test_gps_tile_z5_z7.py | 386 ++++++++++++++++++++++ tests/performance/__init__.py | 0 tests/performance/test_gps_mvt_z5_perf.py | 152 +++++++++ 10 files changed, 1049 insertions(+), 13 deletions(-) create mode 100644 tests/api/test_gps_mvt_simplify.py create mode 100644 tests/api/test_gps_mvt_zoom_tiers.py create mode 100644 tests/integration/test_gps_tile_z5_z7.py create mode 100644 tests/performance/__init__.py create mode 100644 tests/performance/test_gps_mvt_z5_perf.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 13b9287..2537081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) ## [Unreleased] +### Changed +- ET-012: Слой публичных GPS-треков теперь виден с зума z=5 (раньше — с z=8). + Калибровка существующей tier-структуры `build_gps_mvt`/`_simplify_coords` + (ADR-016): для z≤5 фильтр `min_length=10 км`, `limit=1500`; для z=6 — + `5 км`/`2000`; для z=7 — без изменений (`2 км`/`3000`). DP-tolerance + расширен парой стопов: z=6 → 0.018° (~2 км), z≤5 → 0.04° (~4 км). + На клиенте константа `GPS_TRACKS_MIN_ZOOM` понижена до 5; + `line-width`/halo-stops в MapLibre получили stop на z=5 (0.8/1.8 px), + hint обновлён с «Зум 8+» на «Зум 5+». Контракт API + `/api/gps-tracks/tiles/{z}/{x}/{y}.mvt` не изменился (REQ-F-15); + z≥8 не затронут (регрессия). Тесты: 18 unit zoom-tier+simplify, + 9 integration endpoint z5-z7, 2 perf (PERF-Z5-01/02; avg ~64 мс, + p95 ~89 мс при 500 треках — ниже бюджета 200 мс/500 мс по M-6). + Refs: ET-012. + ## [v0.0.3] — 2026-06-03 (tagged, NOT deployed) > ⚠️ Тег создан и запушен, PR смерджен в `main`, но docker-образ на test diff --git a/pyproject.toml b/pyproject.toml index fe87d40..0abb1bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,5 +40,6 @@ asyncio_mode = "auto" testpaths = ["tests"] markers = [ "network: contract smoke tests that hit live HTTP endpoints (deselect with '-m \"not network\"')", + "perf: performance tests; run on-demand with '-m perf' (ET-012 REQ-F-13)", ] -addopts = "-m 'not network'" +addopts = "-m 'not network and not perf'" diff --git a/src/api/gps_tracks/mvt.py b/src/api/gps_tracks/mvt.py index f2b7c00..a59edda 100644 --- a/src/api/gps_tracks/mvt.py +++ b/src/api/gps_tracks/mvt.py @@ -31,15 +31,28 @@ def clear_gps_tile_cache() -> None: # ─── Geometry helpers ──────────────────────────────────────────────────────── def _simplify_coords(coords: list, z: int) -> list: - """Упрощает геометрию трека по зуму через Douglas-Peucker.""" + """Упрощает геометрию трека по зуму через Douglas-Peucker. + + Tolerance задаётся в градусах WGS84. На широте 55° с.ш. 1° долготы + ≈ 64 км, поэтому tolerance=0.04 ≈ 2.6 км. На z5 один пиксель карты + ≈ 5 км по долготе на 55° с.ш., так что 2.6 км даёт «одна точка на + пиксель» — оптимум обзорного зума. + + ET-012 (ADR-016): добавлены тиры z==6 и z<=5; для z>=7 поведение + не меняется (регрессия). + """ if z >= 12: return coords elif z >= 10: - tolerance = 0.0005 # ~50м + tolerance = 0.0005 # ~50 м elif z >= 8: - tolerance = 0.002 # ~200м + tolerance = 0.002 # ~200 м + elif z == 7: + tolerance = 0.008 # ~800 м (как было до ET-012) + elif z == 6: + tolerance = 0.018 # ~2 км else: - tolerance = 0.008 # ~800м на z7 и ниже + tolerance = 0.04 # ~4 км (z5 и ниже) if len(coords) < 3: return coords @@ -101,9 +114,18 @@ def build_gps_mvt(rows: list, z: int, x: int, y: int) -> bytes: west, south, east, north = _tile_to_bbox(z, x, y) - # Min-length фильтр по зуму - if z <= 7: - min_length_m = 2000 + # Min-length фильтр и cap на число фич по зуму. + # ET-012 (ADR-016): добавлены тиры z<=5 и z==6, чтобы при понижении + # GPS_TRACKS_MIN_ZOOM до 5 размер тайла оставался <= 200 KB (M-8) + # и в кадре оставались только «магистральные» треки (M-9). + if z <= 5: + min_length_m = 10000 # 10 км — только «магистральные» треки + limit = 1500 + elif z == 6: + min_length_m = 5000 # 5 км + limit = 2000 + elif z == 7: + min_length_m = 2000 # как было для z<=7 до ET-012 limit = 3000 elif z <= 9: min_length_m = 0 diff --git a/src/web/gps_tracks.js b/src/web/gps_tracks.js index 9440bc8..a62e3e4 100644 --- a/src/web/gps_tracks.js +++ b/src/web/gps_tracks.js @@ -5,7 +5,10 @@ // ─── Константы ──────────────────────────────────────────────────── const GPS_TRACKS_ZOOM_CUTOFF = 12; // ниже — MVT, выше — GeoJSON -const GPS_TRACKS_MIN_ZOOM = 8; // ниже — слой скрыт +// ET-012 (ADR-016): порог понижен с 8 до 5, чтобы при обзорном зуме +// пользователь видел общее покрытие сети треков. Серверная сторона +// (build_gps_mvt z<=5 / z==6) даёт корректный размер MVT и читаемость. +const GPS_TRACKS_MIN_ZOOM = 5; // ниже — слой скрыт const GPS_SOURCE_COLORS = { osm: '#3cb44b', @@ -129,7 +132,14 @@ function _gpsLayerDef(id, source, sourceLayer) { 'source-layer': sourceLayer || undefined, paint: { 'line-color': colorExpr, - 'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1.0, 12, 2.0, 16, 3.0], + // ET-012 (REQ-F-05): stop на z=5 = 0.8 CSS-px. На 1×-дисплеях это + // даёт 1 физ.px (с округлением GPU), на 2× — 1.6, на 3× — 2.4. + // Линия гарантированно видна на любом DPR. + 'line-width': ['interpolate', ['linear'], ['zoom'], + 5, 0.8, + 8, 1.0, + 12, 2.0, + 16, 3.0], 'line-opacity': 0.75, }, layout: { 'line-cap': 'round', 'line-join': 'round', visibility: 'none' } @@ -144,7 +154,14 @@ function _gpsHaloDef(id, source, sourceLayer) { 'source-layer': sourceLayer || undefined, paint: { 'line-color': '#ffffff', - 'line-width': ['interpolate', ['linear'], ['zoom'], 8, 2.5, 12, 4.0, 16, 6.0], + // ET-012 (REQ-F-06): halo на z=5 = 1.8 CSS-px при основной линии 0.8 px + // (соотношение ~2.25×). Ореол не «съедает» линию: по 0.5 px с каждой + // стороны, остаётся видна цветная сердцевина. + 'line-width': ['interpolate', ['linear'], ['zoom'], + 5, 1.8, + 8, 2.5, + 12, 4.0, + 16, 6.0], 'line-opacity': 0.6, }, layout: { visibility: 'none' } @@ -355,7 +372,7 @@ function _syncGpsLayersVisibility(map) { setVis(window.gpsTracksLayer.layerId, mvtVisible); setVis(window.gpsTracksLayer.layerGeoId, geoVisible); - // Hint «Зум 8+» + // Hint «Зум 5+» (ET-012: порог переехал автоматически через GPS_TRACKS_MIN_ZOOM) const hint = document.getElementById('public-tracks-zoom-hint'); if (hint) { hint.style.display = (enabled && zoom < GPS_TRACKS_MIN_ZOOM) ? 'inline' : 'none'; diff --git a/src/web/index.html b/src/web/index.html index 4f983c0..6710435 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -77,7 +77,7 @@ Публичные треки - +