GPS: course-up map rotation in landscape + robust screen-off import
- applyBearing: use h, not h - rotOffset. The .rec-screen (map + panel) is CSS-rotated by rotOffset in landscape and setBearing adds another rotate on the map pane; subtracting rotOffset cancelled the screen rotation for the map only, gluing course to the portrait top edge while the panel turned. Using h lets the map turn with the screen -> course points to physical top. - Poll: guard the requestAnimationFrame yield with !document.hidden. rAF never fires while the screen is off, so awaiting it there hung the import loop and froze point import mid-walk (regression from the progressive-draw change). - recoverSession: import missing native points by the SET of existing timestamps instead of ts > maxDbTs. Sparse screen-gated points can have a late max ts, so the old check imported nothing and left the track full of gaps; also pass sensorData so bearing/accel/gyro are stored.
This commit is contained in:
parent
ec5937ea7e
commit
84a2b786ca
7 changed files with 25 additions and 13 deletions
|
|
@ -23,7 +23,7 @@
|
|||
|------|-------|------------|
|
||||
| `js/main.js` | ~5800 | Экраны, навигация, модалки, графики, нутриенты, HR UI, компас (EMA + gimbal lock), ландшафт (3 режима), дороги |
|
||||
| `js/db.js` | ~1500 | SQLite через Tauri IPC. Миграции, CRUD, 41 поле добавок, water_pct, reserves, sync |
|
||||
| `js/gps.js` | ~1200 | GPS запись, физика ккал (power model), altitude filter, BLE HR polling, dead reckoning |
|
||||
| `js/gps.js` | ~1400 | GPS запись, физика ккал (power model), altitude filter, BLE HR polling, dead reckoning. Импорт нативных точек: JS-поллинг (экран-gated) + ПОЛНЫЙ до-импорт по множеству ts на «Стоп»/recoverSession (без потерь при экране-офф), отбор по точности (minAccuracyM), bearing_deg |
|
||||
| `js/sync.js` | ~300 | Синхронизация между устройствами (LAN, PHP сервер на роутере) |
|
||||
| `js/tilecache.js` | 178 | Оффлайн кэш тайлов карты (IndexedDB) |
|
||||
| `js/bmr.js` | ~70 | BMR (Mifflin-St Jeor), РСП из БД (33 нутриента), полураспад |
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
| `supplements` | Добавки (БАД) | name, daily_dose_g, витамины/минералы |
|
||||
| `supplement_log` | Приём добавок | date, supplement_id, dose_g |
|
||||
| `gps_sessions` | GPS тренировки | date, activity_type, distance, kcal, speed, elevation, bike_id |
|
||||
| `gps_points` | Точки трека | session_id, ts, lat, lon, altitude, speed, seq, heart_rate, spo2, bp_sys, bp_dia |
|
||||
| `gps_points` | Точки трека | session_id, ts, lat, lon, altitude_gps_m, altitude_srtm_m, speed_ms, seq, bearing_deg, heart_rate, spo2, bp_sys, bp_dia, accel_rms/max, gyro_pitch/yaw |
|
||||
| `bikes` | Велосипеды | name, weight_kg, wheel, звёзды, surface_type, is_active |
|
||||
| `rdi_standards` | Стандарты РСП (RU/EU/US) | code, name |
|
||||
| `rdi_values` | Нормы нутриентов | standard_id, nutrient_key, value, norm_type (rdi/aup) |
|
||||
|
|
@ -129,7 +129,7 @@
|
|||
### Экраны
|
||||
- **Сегодня** — баланс ккал, еда (продукты + блюда), GPS тренировки, активность, вес, сон, добавки, прогресс-бар жидкости, нутриенты (приход + запасы)
|
||||
- **Продукты** — подтабы [Продукты | Рецепты], поиск, карточки с КБЖУ, добавление в дневник
|
||||
- **Трек** — GPS тренировки (запись с HR), карта с компасом (leaflet-rotate), ландшафтный режим (CSS rotation), графики скорости/высоты/ЧСС, импорт GPX/FTB, dead reckoning, placeholder до GPS, карта дорог (дедуп 2м, вело/пешие)
|
||||
- **Трек** — GPS тренировки (запись с HR), карта с компасом (leaflet-rotate), course-up по GPS-курсу (карта и панель доворачиваются в ландшафт вместе, `bearing = h`), ландшафтный режим (CSS rotation), графики скорости/высоты/ЧСС, импорт GPX/FTB, dead reckoning, placeholder до GPS, карта дорог (дедуп 2м, вело/пешие)
|
||||
- **Добавки** — 41 нутриент (витамины, минералы, жирные кислоты, женьшень, ресвератрол, CoQ10)
|
||||
- **Настройки** — профиль, образ жизни, нормы ЧСС/давления, BLE пульсометр, велосипеды, GPS конфиг, OTA обновления, импорт тайлов, карта дорог
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
## Возможности
|
||||
|
||||
- **Дневник питания** — 24400+ продуктов (Магнит, 5ка, Health-Diet.ru, Ашан, Макдоналдс, KFC, Додо Пицца, KDV, ИОН, Гриднев). 37 нутриентов (витамины, минералы, omega-3/6, вода, клетчатка). Рецепты с термообработкой. Формулы порций (4*30, 300+50).
|
||||
- **GPS трекинг** — запись маршрутов на велосипеде/пешком в фоне (foreground service, точки пишутся при выключенном экране; один файл на сессию, переживает START_STICKY-перезапуск ОС; полный до-импорт нативных точек по timestamp при финализации — без потерь, даже если экран был выключен всю запись, а при пробуждении экрана точки видимо нарастают на карте по мере импорта). Оффлайн карты (Leaflet + IndexedDB кэш). Импорт GPX/FTB (FitoTrack). Экспорт GPX. Фильтр altitude outlier'ов и GPS-залипаний. Отбор точек по точности (жёсткий порог по настройке «Мин. точность» — рубит грубые сотовые фиксы ~800м, оставляет слабый но точный GPS; где сигнала нет — честный разрыв). Направление (bearing) каждой точки пишется в БД. Вращение карты по компасу (leaflet-rotate, EMA-сглаживание, gimbal lock компенсация). Ландшафтный режим (CSS rotation + панель справа, 3 режима: off/auto/lock). Dead reckoning при потере GPS (гироскоп + акселерометр). Placeholder до GPS (пиксельарт). Карта проезженных дорог (дедупликация 2м, вело/пешие). Импорт тайлов по регионам (SQLite, роутер LAN).
|
||||
- **GPS трекинг** — запись маршрутов на велосипеде/пешком в фоне (foreground service, точки пишутся при выключенном экране; один файл на сессию, переживает START_STICKY-перезапуск ОС; полный до-импорт нативных точек по timestamp при финализации — без потерь, даже если экран был выключен всю запись, а при пробуждении экрана точки видимо нарастают на карте по мере импорта). Оффлайн карты (Leaflet + IndexedDB кэш). Импорт GPX/FTB (FitoTrack). Экспорт GPX. Фильтр altitude outlier'ов и GPS-залипаний. Отбор точек по точности (жёсткий порог по настройке «Мин. точность» — рубит грубые сотовые фиксы ~800м, оставляет слабый но точный GPS; где сигнала нет — честный разрыв). Направление (bearing) каждой точки пишется в БД. Вращение карты по компасу (leaflet-rotate, EMA-сглаживание, gimbal lock компенсация) и course-up по GPS-курсу (в ландшафте карта доворачивается вместе с экраном — курс к физическому верху). Ландшафтный режим (CSS rotation + панель справа, 3 режима: off/auto/lock). Dead reckoning при потере GPS (гироскоп + акселерометр). Placeholder до GPS (пиксельарт). Карта проезженных дорог (дедупликация 2м, вело/пешие). Импорт тайлов по регионам (SQLite, роутер LAN).
|
||||
- **BLE пульсометр** — подключение к FitPro браслетам и стандартным HR датчикам (0x180D). ЧСС, SpO2, давление в реальном времени при записи трека.
|
||||
- **Расчёт ккал** — физическая модель для велосипеда (мощность из gradient + Crr + CdA). MET-таблица для ходьбы/бега и тренировок (приседания 6.0, скакалка 12.3, растяжки 2.3, бассейн 8.0). Профили велосипедов. BMR по Mifflin-St Jeor с коэффициентом активности.
|
||||
- **Тренировки** — приседания (подходы x повторения), скакалка, растяжки, бассейн (дистанция). Автоматический расчёт ккал по MET и весу.
|
||||
|
|
|
|||
|
|
@ -706,16 +706,19 @@ GPS.recoverSession = async function() {
|
|||
try {
|
||||
const s = JSON.parse(saved);
|
||||
if (!s.sessionId) { GPS._clearPersistedState(); return; }
|
||||
// Import remaining native points not yet in DB (idempotent by timestamp)
|
||||
// Import EVERY native point missing from the DB, deduped by the SET of existing
|
||||
// timestamps — NOT "ts > maxDbTs". The live poll is screen-gated and leaves
|
||||
// sparse points spread across the whole session, so the max DB ts can be the
|
||||
// LAST point; "ts > maxDbTs" would then import nothing and the gaps stay.
|
||||
const existingPts = await DB.getGpsPoints(s.sessionId);
|
||||
const maxDbTs = existingPts.length ? Math.max(...existingPts.map(p => new Date(p.ts).getTime())) : 0;
|
||||
const dbTs = new Set(existingPts.map(p => new Date(p.ts).getTime()));
|
||||
const nativePoints = await GPS._readNativePoints();
|
||||
let imported = 0;
|
||||
let seq = existingPts.length;
|
||||
for (const p of nativePoints) {
|
||||
if (new Date(p.ts).getTime() <= maxDbTs) continue;
|
||||
if (dbTs.has(new Date(p.ts).getTime())) continue;
|
||||
seq++;
|
||||
await DB.addGpsPoint(s.sessionId, p.ts, p.lat, p.lon, p.altitude, p.speed_ms, seq);
|
||||
await DB.addGpsPoint(s.sessionId, p.ts, p.lat, p.lon, p.altitude, p.speed_ms, seq, null, p);
|
||||
imported++;
|
||||
}
|
||||
// Finalize the session
|
||||
|
|
@ -801,7 +804,10 @@ GPS.startRecording = async function(activityType, weightKg) {
|
|||
GPS.recording._lastProcessedTs = pTs;
|
||||
newCount++;
|
||||
if (GPS.recording.onImportPoint) GPS.recording.onImportPoint(p);
|
||||
if (newCount % 20 === 0) await new Promise(r => requestAnimationFrame(r));
|
||||
// Yield a frame so the growing track repaints — but ONLY when visible.
|
||||
// requestAnimationFrame never fires while the screen is off (page hidden),
|
||||
// so awaiting it there would hang this loop forever and freeze the import.
|
||||
if (newCount % 20 === 0 && !document.hidden) await new Promise(r => requestAnimationFrame(r));
|
||||
}
|
||||
GPS.recording.onPoint = origOnPoint;
|
||||
// Final sync — marker, pan, course and stats from the full point set.
|
||||
|
|
|
|||
|
|
@ -2948,7 +2948,13 @@ function renderRecordingInline(container) {
|
|||
|
||||
function applyBearing() {
|
||||
let h = compassFrozen ? frozenHeading : baseHeading();
|
||||
let bearing = h - rotOffset;
|
||||
// Do NOT subtract rotOffset. The whole .rec-screen (map + panel) is CSS-rotated
|
||||
// by rotOffset in landscape; setBearing adds another CSS rotate on the map's
|
||||
// rotatePane. Subtracting rotOffset cancelled the screen rotation FOR THE MAP,
|
||||
// so the map stayed portrait-oriented (course glued to the narrow top edge)
|
||||
// while the panel rotated. Using h lets the map turn with the screen like the
|
||||
// panel → course points to the physical top. Portrait (rotOffset=0): h-0=h.
|
||||
let bearing = h;
|
||||
map.setBearing(bearing);
|
||||
updateNorthArrow(bearing);
|
||||
}
|
||||
|
|
|
|||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
|
|
@ -139,7 +139,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|||
|
||||
[[package]]
|
||||
name = "baldav"
|
||||
version = "0.1.363"
|
||||
version = "0.1.366"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"btleplug",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "baldav"
|
||||
version = "0.1.363"
|
||||
version = "0.1.366"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/nicegui/tauri-v2/refs/heads/main/tooling/cli/schema.json",
|
||||
"productName": "BalDav",
|
||||
"version": "0.1.363",
|
||||
"version": "0.1.366",
|
||||
"identifier": "dev.baldav.app",
|
||||
"build": {
|
||||
"frontendDist": "../frontend"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue