Map: dark-map toggle (invert tiles only), no repacking
A moon button on the recording, track-detail and roads maps inverts ONLY the tile layer: body.dark-map applies `invert(1) hue-rotate(180deg) brightness(1.5)` to .leaflet-tile-pane, so the track/route/roads/puck overlays keep their colours. Works the same on offline (tiles_*.db) and online tiles — no keys, quotas or tile repacking. The choice persists in localStorage (baldav_dark_map). Chosen after ruling out dark tile providers (CARTO/Thunderforest/2GIS/Yandex — keys/quotas/ToS/projection/RF-reachability) and the transport style (inverts badly in dense city); plain OSM inverted looks good city and countryside. Verified on device: filter on the tile pane only, overlay pane filter none, button active and remembered.
This commit is contained in:
parent
990924a048
commit
a36d9fad9b
5 changed files with 90 additions and 0 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
- **Высоты маршрута (рельеф до поездки)** — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом **уклон/высота**: уклон дискретными полосами по 1% (зелёный→жёлтый→красный→коричневый→чёрный, ≥10% чёрный) или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (`<ele>` из 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).
|
||||
- **Вес, сон, добавки** — логирование с историей.
|
||||
|
|
|
|||
|
|
@ -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 77.66.192.45: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, тумблер ☾ (запись/детали/дороги),
|
||||
без перепаковки. Проверено на устройстве.
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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 = `<div class="loading" style="color:#cf6679">
|
||||
Ошибка: db.js не загрузился.<br>
|
||||
|
|
@ -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(/</g, "<")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue