diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index f684c17..1958926 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -96,6 +96,39 @@ app data dir; профиль trekking (идея 2) и shortest (идея 1); л --- +## Stage 6 — POI-оверлеи на картах BalDav (АНАЛИЗ, вне BRouter, запрошено 2026-09-09) +**Идея:** показывать OSM-объекты (amenity/shop/tourism) на картах BalDav — при трекинге и просмотре: +ATM, банк, скамейка, туалеты, вода, кафе/бар/фастфуд/ресторан/паб, велопарковка/веломастерская, +автобусная остановка, пекарня/супермаркет/кофе, смотровая, укрытие, пикник и т.д. (набор — как в +brouter-web overlays, см. скрины). С фильтром категорий (тумблеры), иконки скачать и рисовать. +**Что проанализировать перед реализацией:** +- Источник данных POI ОФФЛАЙН: Overpass-выгрузка по региону (bbox ~100км вокруг город) в + локальный стор (SQLite/JSON, как seed/tiles), т.к. сеть в поле недоступна. rd5 не годится как + POI-хранилище. Обновление — по проводу с зеркала (как тайлы/сегменты). +- Иконки: взять набор (напр. используемый brouter-web / maki / OSM-carto sprites), лицензия, + положить в assets, рисовать как Leaflet divIcon/marker с кластеризацией на низком зуме (перф). +- UI: панель категорий с чекбоксами (как в brouter-web), сохранение выбора (localStorage), + z-order (POI над тайлами/дорогами, под треком/паком). +- Перф: сотни-тысячи точек — кластеризация/культинг по зуму и bbox видимой области. +**Статус:** not started (анализ). + +## Stage 7 — Тёмные тайлы карты в BalDav (запрошено 2026-09-09) +**Идея:** заменить фон OSM на тёмный (пользователю нравится ÖPNV, но хочет тёмное). ВАЖНО (проверено +2026-09-09): хорошего тёмного растра БЕЗ ключа сейчас нет — CARTO Dark теперь тоже требует ключ +(без ключа отдаёт тайлы с ватермаркой "API KEY REQUIRED"). Кандидаты: Thunderforest Transport Dark +(ÖPNV-стиль тёмный, ближе всего к желаемому — бесплатный ключ), CARTO Dark (ключ), Stadia (ключ); +без ключа и чисто — только светлые (ÖPNVKarte/OpenTopoMap/OSM) или спутник Esri. Источник в +`frontend/js/tilecache.js` хардкодом +`https://a.tile.openstreetmap.org/{z}/{x}/{y}.png` (3 места); оффлайн-тайлы — в `tiles_*.db` (OSM). +**РЕШЕНО (2026-09-09): живой CSS-фильтр, без перепаковки.** Провайдеров тёмных тайлов (CARTO/ +Thunderforest/2GIS/Yandex) отмели — ключи/квоты/ToS/проекция/недоступность в РФ. Транспортная карта +инвертом плохо (город). ПОБЕДИЛО: OSM-стандарт (те тайлы, что уже есть) + фильтр +`invert(1) hue-rotate(180deg) brightness(1.5)` на `.leaflet-tile-pane` — достойно и в городе, и на селе. +Реализация: класс `body.dark-map` + одно CSS-правило (только слой тайлов, оверлеи цветные), тумблер +`☾` на экранах записи/деталей трека/дорог (`toggleDarkMap`/`applyDarkMap`/`addDarkMapControl` в main.js), +выбор в localStorage `baldav_dark_map`. Работает одинаково на оффлайн-БД и онлайне, без ключей/квот. +**Статус:** DONE (проверено на устройстве: фильтр на тайлах, overlay `none`, кнопка активна, персист). + ## Потенциальные проблемы - Desugaring/`java.lang.invoke` при dex — проверить сборку (AGP обычно тянет). - Размер/имена тайлов региона на brouter.de — уточнить при Stage 2. diff --git a/README.md b/README.md index 24db5ac..669efb0 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ - **Высоты маршрута (рельеф до поездки)** — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом **уклон/высота**: уклон дискретными полосами по 1% (зелёный→жёлтый→красный→коричневый→чёрный, ≥10% чёрный) или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (`` из GPX или онлайн) тянутся по цепочке: **GPXZ** (Copernicus GLO-30, точнее для уклонов; ключ в GPS-настройках, хранится только на устройстве) → open-elevation → open-meteo → OpenTopoData (без ключа). Калории пересчитываются по сглаженным точкам; для уклона высоты сглаживаются (гасит вертикальный шум SRTM). - **Редактор маршрутов** — создание («Новый маршрут») и правка сохранённого маршрута прямо в приложении: тап по карте — добавить точку в конец, тап по линии — вставить точку в это место, тащить точку — двигать, тап по точке — удалить. Высоты подтягиваются онлайн после отрисовки (из предпросмотра). - **Де-джиттер трека** — сглаживание дрожащего GPS-трека методом Савицкого-Голея (настраиваемое окно/число итераций, живое превью на карте детали трека). Хранится обратимо: оригинал и сглаженный вариант лежат раздельно, калории пересчитываются по сглаженным точкам, откат к оригиналу в один тап. Точность каждого фикса (`accuracy_m`) пишется в БД: при пересчёте трека точки хуже порога «Макс. погрешность GPS» исключаются из расчёта и линии (обратимо — точки в БД остаются; поднять порог = вернуть). Работает во всех пересчётах (кнопка «Пересчитать», «Пересчитать всё», после де-джиттера); импорт/старьё без точности и dead-reckoning заполнения не трогает. +- **Тёмная карта** — тумблер `☾` на экранах записи, деталей трека и дорог: инвертирует ТОЛЬКО слой тайлов (`invert + hue-rotate 180° + brightness`, трек/маршрут/дороги остаются цветными), выбор запоминается. Работает одинаково на офлайн-тайлах и онлайне, без ключей/квот/перепаковки. - **Метки ветра** — кнопка «Ветер в лицо» на экране записи: тап открывает интервал встречного ветра, тап закрывает. Метки хранятся с треком (`wind_marks`) как эталон для проверки оценки ветра по треку; на расчёт калорий/мощности не влияют. Голосовые метки — на будущее. - **Экспорт/импорт** — свои продукты, рецепты и добавки одним файлом (JSON). - **Вес, сон, добавки** — логирование с историей. diff --git a/docs/session-logs/2026-09-07_brouter-findings.md b/docs/session-logs/2026-09-07_brouter-findings.md index aa154f7..1477ff3 100644 --- a/docs/session-logs/2026-09-07_brouter-findings.md +++ b/docs/session-logs/2026-09-07_brouter-findings.md @@ -74,3 +74,16 @@ readTimeout 60с, до 30 попыток. Итог на устройстве: E4 LAN-зеркала за <2с, маршрут на нём ок. Осталось: WAN-пробросы на Keenetic (PPPoE0 :8088→роутер:88, :17777→ПК:17777) — outward-facing, с подтверждением; для release добавить network-security-config под cleartext на mirror-хосты. + +## Дополнение 2026-09-09 (4): своё зеркало на роутере + BRouter-Web + тёмная карта +- Зеркало на Keenetic: тайлы+профили в /opt/share/www/brouter/ (uhttpd :88), доступно LAN 192.168.1.1:88 + и WAN mirror.invalid:8088 (static-NAT PPPoE0 :8088→:88). BalDav качает тайлы с зеркала (LAN→WAN→brouter.de). +- BRouter RouteServer на ПК: /mnt/02F6E7F6F6E7E7BD/brouter-mirror, СИСТЕМНЫЙ systemd brouter.service + (multi-user.target, автостарт без логина, поставил пользователь sudo). WAN :17777→ПК:17777. +- BRouter-Web (nrenner 0.18.1) на зеркале: http://192.168.1.1:88/brouter-web/ (+WAN), config.js → RouteServer, + дефолт-слой ÖPNV (CARTO/др. — «needs key»), центр город. +- Тёмная карта в BalDav: перебрали провайдеров (CARTO/Thunderforest/2GIS/Yandex — ключи/квоты/ToS/проекция/ + недоступность в РФ; проба доступности из BalDav по мобильному Tele2: доступны OSM/CARTO/ÖPNV/Esri/2GIS, + нет OpenTopo/Yandex-vec). Транспортную инвертом — плохо в городе. Решение: OSM-стандарт + CSS + `invert(1) hue-rotate(180deg) brightness(1.5)` на .leaflet-tile-pane, тумблер ☾ (запись/детали/дороги), + без перепаковки. Проверено на устройстве. diff --git a/frontend/css/styles.css b/frontend/css/styles.css index ff48d88..2c16fb8 100644 --- a/frontend/css/styles.css +++ b/frontend/css/styles.css @@ -1145,6 +1145,10 @@ select { .rec-roads-toggle { right: 8px; bottom: 124px; } .rec-mode-btn { right: 8px; bottom: 162px; font-size: 11px; } .rec-route-toggle { right: 8px; bottom: 200px; } +.rec-dark-toggle { right: 8px; bottom: 238px; } +/* Dark map: invert ONLY the tile layer (overlays — track, route, roads, puck — keep + their colours). User-tuned filter on OSM standard tiles. Applies to every Leaflet map. */ +body.dark-map .leaflet-tile-pane { filter: invert(1) hue-rotate(180deg) brightness(1.5); } /* Always-on compass needle: red half = North, grey = South. Rotates to point to real north. Top-RIGHT (zoom controls are top-left), below the status-bar safe area. */ /* Position/direction puck (SVG divIcon) — no default leaflet-div-icon box. */ diff --git a/frontend/js/main.js b/frontend/js/main.js index ecd2bb7..73dbda6 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -114,6 +114,7 @@ function winPopTop() { // --------------------------------------------------------------------------- async function init() { + applyDarkMap(); // restore dark-map tile filter preference before any map renders if (typeof DB === "undefined" || typeof DB.getFullProfile !== "function") { app.innerHTML = `
Ошибка: db.js не загрузился.
@@ -4102,6 +4103,14 @@ async function renderRecordingInline(container) { } routeBtn.onclick = () => setRouteShown(!routeLayer); btnWrap.appendChild(routeBtn); + + // Dark-map toggle (moon): invert the tile layer only, remembered globally. + let darkBtn = document.createElement("button"); + darkBtn.className = "rec-map-btn rec-dark-toggle" + (isDarkMap() ? " active" : ""); + darkBtn.textContent = "☾"; + darkBtn.title = "Тёмная карта"; + darkBtn.onclick = () => toggleDarkMap(); + btnWrap.appendChild(darkBtn); // The default-ON draw is deferred to the END of this function (after all the // critical wiring), where it awaits whenMapReady — so it can't delay setup. @@ -4723,6 +4732,7 @@ async function showSessionDetail(sessionId) { addZoomDisplay(map); let CachedLayer = TileCache.createLayer(); new CachedLayer({ maxZoom: 19 }).addTo(map); + addDarkMapControl(map); // dark-tiles toggle on the track view // Active saved-route overlay on the detail map + a toggle button (default ON when // a route is selected). Shares baldav_route_shown with the recording screen. @@ -7348,6 +7358,7 @@ async function showRoadsMap() { addZoomDisplay(map); let CachedLayer = TileCache.createLayer(); new CachedLayer({ maxZoom: 19 }).addTo(map); + addDarkMapControl(map); // dark-tiles toggle on the roads map let infoEl = document.querySelector("#roads-info"); infoEl.textContent = "Загрузка..."; @@ -7597,6 +7608,34 @@ function copyToClipboard(text, toastMsg) { } else fallback(); } +// Dark map: invert the tile layer only (overlays keep their colours). User-tuned filter +// on OSM standard tiles — invert(1) hue-rotate(180deg) brightness(1.5). Toggled by the +// user, remembered in localStorage, applied globally to every Leaflet tile pane. No +// tile repacking: works the same on offline (tiles_*.db) and online tiles. +function isDarkMap() { try { return localStorage.getItem("baldav_dark_map") === "1"; } catch (e) { return false; } } +function applyDarkMap() { document.body.classList.toggle("dark-map", isDarkMap()); } +function toggleDarkMap() { + let on = !isDarkMap(); + try { localStorage.setItem("baldav_dark_map", on ? "1" : "0"); } catch (e) {} + document.body.classList.toggle("dark-map", on); + document.querySelectorAll(".map-dark-btn, .rec-dark-toggle").forEach(b => b.classList.toggle("active", on)); + return on; +} +// Leaflet control button (top-right) for maps without a custom button stack. +function addDarkMapControl(map) { + let Ctl = L.Control.extend({ + options: { position: "topright" }, + onAdd: function () { + let b = L.DomUtil.create("button", "rec-map-btn map-dark-btn" + (isDarkMap() ? " active" : "")); + b.textContent = "☾"; b.title = "Тёмная карта"; b.style.position = "static"; + L.DomEvent.disableClickPropagation(b); + b.onclick = () => toggleDarkMap(); + return b; + }, + }); + map.addControl(new Ctl()); +} + function esc(str) { return String(str ?? "") .replace(/&/g, "&").replace(/