Tiles: heal blank tiles online (periodic OSM-reachability check + backfill)
The on-demand loader never retries a tile whose fetch failed, so holes from a bad signal stayed blank even after the network recovered. Add a healer: every 30s, when OSM is reachable, re-fetch MISSING tiles (current zoom) for the visible area and along the ridden route, cache them, and redraw. Gentle to OSM (one-tile probe, 300ms throttle, <=40 fetches/cycle); respects offline mode. Wired on record/detail/compare/roads maps, stopped on screen change.
This commit is contained in:
parent
d4dda3da0c
commit
3f092cced0
5 changed files with 148 additions and 6 deletions
|
|
@ -26,7 +26,7 @@
|
|||
| `js/gps.js` | ~1650 | GPS запись, ккал (нетто-модель + MET-нетто floor, накат=базовый), altitude filter + гистерезис высоты, BLE HR polling, dead reckoning. Источник истины БД при живой записи — нативный файл (`_processIncomingPoint(p, fromNative)`: watchPosition только рисует, поллинг пишет). `recoverFromFiles` — восстановление точек с устройства (матч по id/дате + `_s<id>` в имени, дедуп ±2.5с, `recalculateSession(noDelete)`, чистка импортированных файлов). `recomputeAllStats` — пересчёт статы всех треков без изменения точек. `filterByAccuracy` — отбор по точности (minAccuracyM) внутри `computeStats`+`filterNoise`: неточные фиксы исключаются из расчёта/линии (обратимо, non-destructive; null/≥999 keep), bearing_deg. `smoothTrackSG(pts,W,it)` — де-джиттер Савицкого-Голея (квадратичный МНК по окну, итеративно, концы закреплены; lat/lon→метры→обратно, все поля точки сохраняются). `parseRouteGPX(xml)` — парсер маршрутов (`rtept`/`trkpt`/`wpt`, читает `<ele>`, DOMParser). `routeElevStats(pts)` — профиль маршрута (накопл. дистанция, грейд к след. точке, набор/сброс с гистерезисом, макс-уклон; высоты сглаживаются). `fillGaps(points)` → `{points, gaps}` — дорожная достройка GPS-дропаутов (BRouter shortest) с интерполяцией ts и флагом `estimated`; `gaps[].status` = filled/skipped-long/skipped-noroute для UI-предпросмотра. `config.gpxzKey` — ключ GPXZ (localStorage) |
|
||||
| `js/sync.js` | ~300 | Синхронизация между устройствами (LAN, PHP сервер на роутере) |
|
||||
| `js/chzn.js` | ~330 | Честный знак (CRPT). Скан DataMatrix → карточка Нацкаталога → префилл КБЖУ. `ChZ`: сессия в localStorage (`chzn_session`), `importFromText`, `refresh`/`ensureSid` (sid 48ч, refreshToken ~год), `authedPost/Get` (userId+sid в query, авто-refresh на 401), `check`, `nutritionInfo` (витамины, часто 204), `normalizeCode` (реконструкция GS 0x1D перед AI 93), `parseCard` (группа «Энергетическая ценность…», имена атрибутов разнятся у производителей), `resolve`. `/authorize/anonymous` закрыт Play Integrity — свою сессию не завести, только импорт из приложения (`tools/chzn_export.sh`) |
|
||||
| `js/tilecache.js` | 178 | Оффлайн кэш тайлов карты (IndexedDB) |
|
||||
| `js/tilecache.js` | ~300 | Оффлайн кэш тайлов карты (IndexedDB). `createLayer` — Leaflet-слой, тянет из IndexedDB, при промахе (если не offline) фетчит с OSM и кэширует; НЕ ретраит упавший тайл (Leaflet метит его loaded-with-error). `startHealer/stopHealer/probeOsm` — онлайн-долечивание: раз в 30с, если OSM доступен, дофетчивает НЕДОСТАЮЩИЕ тайлы (текущий зум) по видимой области и вдоль проеханного маршрута (rotating cursor, трот 300мс, ≤40 фетчей/цикл — бережно к OSM), кэширует и `layer.redraw()`. Гейтится offline-режимом. Запускается на картах записи/детали/сравнения/дорог, глушится в `renderScreen` |
|
||||
| `js/router.js` | ~80 | Оффлайн-роутинг BRouter. `BRouter.route(points,[profile])`: Android → встроенный движок (`invoke("brouter_route")`), иначе/при ошибке → PC-RouteServer по HTTP (`http_request`, мимо CSP; LAN 192.168.1.18:17777 / WAN mirror.invalid:17777). Профили `trekking` (планирование) / `shortest` (гэпы). `BRouter.route(points, profile, nogos)` — nogos `[{lat,lon,radius}]` → `&nogos=lon,lat,r` в RouteServer и в запрос встроенного движка (Kotlin `rc.nogopoints`). Докачка сегментов `.rd5`: `downloadTile/downloadStatus/listTiles/downloadTileAndWait` |
|
||||
| `js/poi.js` | ~200 | POI-оверлей на картах: круглые контрастные бейджи (эмодзи в круге, не пины), фильтр категорий (`POI.attach(map)`, данные `data/pois.json`, localStorage `baldav_poi_cats/shown`) |
|
||||
| `js/bmr.js` | ~70 | BMR (Mifflin-St Jeor), РСП из БД (33 нутриента), полураспад |
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
## Возможности
|
||||
|
||||
- **Дневник питания** — ~13000 продуктов с меткой источника (Магнит, 5ка, ИОН, KDV, Гриднев, свои). Поиск порядко-независимый (слова в любом порядке). Каталог чистится от неиспользованного импорта без метки (health-diet и пр.) кнопкой в настройках — съеденные и в рецептах сохраняются. 37 нутриентов (витамины, минералы, omega-3/6, вода, клетчатка). Рецепты с термообработкой. Формулы порций (4*30, 300+50).
|
||||
- **GPS трекинг** — запись маршрутов на велосипеде/пешком в фоне (foreground service, точки пишутся при выключенном экране; один файл на сессию с id сессии в имени `gps_points_<ms>_s<id>.jsonl` для однозначной привязки, переживает START_STICKY-перезапуск ОС; полный до-импорт нативных точек по timestamp при финализации — без потерь, даже если экран был выключен всю запись, а при пробуждении экрана точки видимо нарастают на карте по мере импорта). При живой записи нативный файл — источник истины для БД, watchPosition только рисует маркер (без дублей одного фикса с разными ts). Восстановление точек из файлов на устройстве — матч файла к сессии по id/дате, дозаполнение разрывов с дедупом по близости ±2.5с, безопасный пересчёт без удаления точек, чистка уже импортированных файлов. Оффлайн карты (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м, вело/пешие). Настраиваемый цвет линии трека (чтобы отличать от цветных дорог). Навигационный маркер позиции с треугольником-направленцем (по компасу или GPS-курсу); порядок слоёв тайлы<дороги<маршрут<трек<маркер. Импорт тайлов по регионам (SQLite, роутер LAN).
|
||||
- **GPS трекинг** — запись маршрутов на велосипеде/пешком в фоне (foreground service, точки пишутся при выключенном экране; один файл на сессию с id сессии в имени `gps_points_<ms>_s<id>.jsonl` для однозначной привязки, переживает START_STICKY-перезапуск ОС; полный до-импорт нативных точек по timestamp при финализации — без потерь, даже если экран был выключен всю запись, а при пробуждении экрана точки видимо нарастают на карте по мере импорта). При живой записи нативный файл — источник истины для БД, watchPosition только рисует маркер (без дублей одного фикса с разными ts). Восстановление точек из файлов на устройстве — матч файла к сессии по id/дате, дозаполнение разрывов с дедупом по близости ±2.5с, безопасный пересчёт без удаления точек, чистка уже импортированных файлов. Оффлайн карты (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м, вело/пешие). Настраиваемый цвет линии трека (чтобы отличать от цветных дорог). Навигационный маркер позиции с треугольником-направленцем (по компасу или GPS-курсу); порядок слоёв тайлы<дороги<маршрут<трек<маркер. Импорт тайлов по регионам (SQLite, роутер LAN). Онлайн-долечивание пустых тайлов: раз в 30с, когда OSM доступен, БалДав дофетчивает недостающие тайлы (текущий зум) по видимой области и вдоль проеханного маршрута, кэширует и перерисовывает - дырки, оставшиеся от плохого сигнала в поездке, закрываются сами, когда связь есть (бережно к OSM: троттлинг, лимит на цикл; в режиме «офлайн карты» не фетчит).
|
||||
- **BLE пульсометр** — подключение к FitPro браслетам и стандартным HR датчикам (0x180D). ЧСС, SpO2, давление в реальном времени при записи трека. Подключение **только вручную** (Настройки -> «Подключить»), в т.ч. во время записи - пульс пишется в трек с момента подключения; автопоиска при старте записи нет (экономия батареи). BT выключен -> кнопка просит включить (системный диалог). Браслет не найден в скане -> сервис не висит в коннекте.
|
||||
- **Зарядка (пульс)** — режим записи БЕЗ GPS для домашней тренировки: геолокация не включается и не пишется, только пульс с датчика + таймер. Экран без карты (крупный пульс, время, живые ккал, средний/макс). Нагрузка (ккал) считается по пульсу формулой Keytel (нетто, как и остальная активность) и идёт в дневной баланс. Деталь - без карты, график пульса по HR-зонам. Хранится в `gps_sessions` (`activity_type="workout"`, без точек, ряд пульса в `hr_samples`).
|
||||
- **Расчёт ккал** — велосипед: НЕТТО-модель (суточный баланс уже учитывает BMR, поэтому активность считается сверх покоя). Кручение = max(физика мощности из gradient+Crr+CdA / КПД, эмпирический MET-нетто по скорости); накат (freewheel, `power≤0` — «велик катился бы сам») = только базовый обмен. Crr на грунтах уточняется по вибрации акселерометра (до 0.020 на траве/колее). Ходьба с велосипедом в горку (только односкоростной) отделяется автоматически по скорости ниже каденса 30 об/мин и не засчитывается как езда. MET-таблица для ходьбы/бега и тренировок (приседания 6.0, скакалка 12.3, растяжки 2.3, бассейн 8.0). Набор высоты — кумулятивный гистерезис (устойчив на плотных треках, где per-point разница <1м); абсолютная точность набора ограничена сырым GPS-альтиметром (порядка десятков процентов, без модели рельефа не разрешается). Известное ограничение: `max(физика, MET)` не складывает накладные расходы, поэтому подъёмы по ккал слегка недосчитываются. Пункт меню «Пересчитать высоту/калории» пересчитывает статистику всех треков без изменения точек. Профили велосипедов. BMR по Mifflin-St Jeor с коэффициентом активности. Вес водителя для ккал берётся из **последнего взвешивания** (настройки - запас); каждая новая поездка **хранит вес на момент записи** (`gps_sessions.rider_weight_kg`), поэтому пересчёт старого трека считает по тогдашнему весу, а не текущему (старые треки без сохранённого веса - по последнему взвешиванию). Поле веса в настройках авто-синкается с последним взвешиванием. Баланс ккал вверху, тренд «Среднее за 7 дн.» и графики (вес/баланс, окна 30/90) считаются от **выбранного дня** (окно кончается на нём), а не всегда от «сегодня».
|
||||
|
|
|
|||
27
docs/session-logs/2026-10-19_tile-healer.md
Normal file
27
docs/session-logs/2026-10-19_tile-healer.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Сессия 2026-10-19 (доб.) - онлайн-долечивание пустых тайлов
|
||||
|
||||
## Причина (из диагностики ранее)
|
||||
`createTile` (tilecache.js) при падении фетча не ретраит: Leaflet метит тайл
|
||||
loaded-with-error и не перезапрашивает, пока тайл не пересоздан (панорама/зум/redraw/
|
||||
новая карта). Поэтому дырки от плохого сигнала в поездке висели даже после возврата связи
|
||||
(подтверждено: офлайн ВЫКЛ, OSM через VPN отдавал 200, но пустые тайлы не оживали).
|
||||
|
||||
## Сделано (фича по запросу пользователя)
|
||||
tilecache.js - хилер:
|
||||
- `probeOsm()` - navigator.onLine + один тайл 0/0/0.png (таймаут 6с) = OSM доступен.
|
||||
- `startHealer(map, layer, pointsFn)` / `stopHealer()` - раз в 30с, если онлайн и не
|
||||
offline-режим: собирает недостающие тайлы на ТЕКУЩЕМ зуме по видимой области + (если
|
||||
pointsFn есть) вдоль маршрута (rotating cursor по длинному треку, чтобы не бурстить),
|
||||
фетчит с OSM, кэширует, по заполнению `layer.redraw()`. Лимиты: троттл 300мс, ≤40
|
||||
фетчей/цикл, ≤500 lookups/цикл - бережно к политике OSM (без массовой скачки).
|
||||
Также немедленный проход по событию `online` и через 3с после открытия карты.
|
||||
- Решения пользователя: зум - только текущий; охват - запись + просмотр.
|
||||
|
||||
main.js - подключение: хилер стартует на картах записи (pointsFn = точки текущей записи),
|
||||
детали трека (точки трека), сравнения треков и экрана "Дороги" (оба - по видимой области,
|
||||
pointsFn=null); глушится в `renderScreen` при уходе с экрана (и на map 'unload').
|
||||
|
||||
## Проверка
|
||||
Синтаксис JS ок (tilecache.js, main.js). На телефоне: открыть трек с дырявыми тайлами при
|
||||
живой сети - в течение ~30с дырки на текущем зуме заполняются; при офлайн-режиме докачки нет;
|
||||
нагрузка на OSM ограничена троттлингом. Живую проверку делает пользователь.
|
||||
|
|
@ -250,6 +250,7 @@ function renderScreen(name, fromPopstate) {
|
|||
// Release screen wake lock and compass listener when leaving recording screen
|
||||
if (window._screenLock) { try { window._screenLock.release(); } catch {} window._screenLock = null; }
|
||||
if (GPS.recording._compassCleanup) { GPS.recording._compassCleanup(); GPS.recording._compassCleanup = null; }
|
||||
try { TileCache.stopHealer(); } catch {} // leaving a map screen stops online tile-healing; map screens restart it
|
||||
|
||||
let prevScreen = state.screen;
|
||||
state.screen = name;
|
||||
|
|
@ -4632,7 +4633,9 @@ async function renderRecordingInline(container) {
|
|||
}, { passive: false });
|
||||
addZoomDisplay(map);
|
||||
let CachedLayer = TileCache.createLayer();
|
||||
new CachedLayer({ maxZoom: 19 }).addTo(map);
|
||||
let tileLayer = new CachedLayer({ maxZoom: 19 }).addTo(map);
|
||||
// Heal blank tiles along the ridden route whenever OSM is reachable.
|
||||
TileCache.startHealer(map, tileLayer, () => (GPS.recording.points || []).map(p => [p.lat, p.lon]));
|
||||
// Force recalc after flex layout settles — fixes black screen on init
|
||||
setTimeout(() => map.invalidateSize(), 200);
|
||||
|
||||
|
|
@ -5620,7 +5623,9 @@ async function showSessionDetail(sessionId) {
|
|||
}, { passive: false });
|
||||
addZoomDisplay(map);
|
||||
let CachedLayer = TileCache.createLayer();
|
||||
new CachedLayer({ maxZoom: 19 }).addTo(map);
|
||||
let tileLayer = new CachedLayer({ maxZoom: 19 }).addTo(map);
|
||||
// Heal blank tiles along this track's route when OSM is reachable.
|
||||
TileCache.startHealer(map, tileLayer, () => points.map(p => [p.lat, p.lon]));
|
||||
addDarkMapControl(map); // dark-tiles toggle on the track view
|
||||
POI.attach(map); // POI overlay + category filter
|
||||
{ let nl = null; let rn = async () => { nl = await drawNogoAreas(map, nl, rn); }; rn(); } // impassable-spot circles
|
||||
|
|
@ -6669,7 +6674,8 @@ async function showComparisonView(currentId, compareId, currentSession, currentP
|
|||
mapEl._leafletMap = map;
|
||||
addZoomDisplay(map);
|
||||
let CachedLayer = TileCache.createLayer();
|
||||
new CachedLayer({ maxZoom: 19 }).addTo(map);
|
||||
let tileLayer = new CachedLayer({ maxZoom: 19 }).addTo(map);
|
||||
TileCache.startHealer(map, tileLayer, null); // heal visible tiles when OSM is reachable
|
||||
|
||||
// Build speed lookup for older track
|
||||
let olderCoords = olderPoints.map(p => [p.lat, p.lon]);
|
||||
|
|
@ -8674,7 +8680,8 @@ async function showRoadsMap() {
|
|||
let map = L.map(mapEl, { preferCanvas: true, attributionControl: false }).setView([47.42, 40.08], 13); // canvas: many road polylines vs SVG compositor tile-memory limit
|
||||
addZoomDisplay(map);
|
||||
let CachedLayer = TileCache.createLayer();
|
||||
new CachedLayer({ maxZoom: 19 }).addTo(map);
|
||||
let tileLayer = new CachedLayer({ maxZoom: 19 }).addTo(map);
|
||||
TileCache.startHealer(map, tileLayer, null); // heal visible tiles when OSM is reachable
|
||||
addDarkMapControl(map); // dark-tiles toggle on the roads map
|
||||
POI.attach(map); // POI overlay + category filter
|
||||
|
||||
|
|
|
|||
|
|
@ -216,5 +216,113 @@ TileCache.importFromSqlite = async function(path, onProgress) {
|
|||
return imported;
|
||||
};
|
||||
|
||||
// ---- Online tile healer ---------------------------------------------------
|
||||
// The on-demand loader (createTile) never retries a tile whose fetch failed:
|
||||
// Leaflet marks it loaded-with-error and won't re-request until the tile is
|
||||
// re-created (pan out+back / zoom / redraw). So blanks from a bad moment stay
|
||||
// blank even after the network recovers. The healer fixes that: periodically,
|
||||
// when OSM is reachable, it re-fetches MISSING tiles (current zoom only) for the
|
||||
// visible area and — during recording / track review — along the ridden route,
|
||||
// caches them, and redraws so the holes fill. Respects offline mode. Gentle to
|
||||
// OSM (one-tile probe, throttled fetches, capped per cycle) per their tile usage
|
||||
// policy — we never bulk-download.
|
||||
let _healTimer = null, _healMap = null, _healLayer = null, _healPointsFn = null;
|
||||
let _healing = false, _healCursor = 0, _onlineHandler = null;
|
||||
const HEAL_INTERVAL_MS = 30000, HEAL_THROTTLE_MS = 300;
|
||||
const HEAL_MAX_FETCH = 40; // tiles fetched per cycle (≈1.3/s — modest)
|
||||
const HEAL_MAX_SCAN = 500; // IndexedDB lookups per cycle (bounds CPU on long routes)
|
||||
|
||||
function _sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
||||
|
||||
// One small tile tells us OSM is reachable (navigator.onLine gates first — cheap).
|
||||
TileCache.probeOsm = async function() {
|
||||
if (!navigator.onLine) return false;
|
||||
try {
|
||||
const c = new AbortController();
|
||||
const t = setTimeout(() => c.abort(), 6000);
|
||||
const r = await fetch("https://a.tile.openstreetmap.org/0/0/0.png", { cache: "no-store", signal: c.signal });
|
||||
clearTimeout(t);
|
||||
return r.ok;
|
||||
} catch { return false; }
|
||||
};
|
||||
|
||||
function _lonLatToTile(lat, lon, z) {
|
||||
const n = Math.pow(2, z);
|
||||
return {
|
||||
x: Math.floor((lon + 180) / 360 * n),
|
||||
y: Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * n),
|
||||
};
|
||||
}
|
||||
|
||||
// Tiles covering a polyline at zoom z (+1 ring around each point so the road's
|
||||
// tiles, not just the exact centre, are covered), de-duplicated.
|
||||
function _tileKeysForLatLngs(latlngs, z) {
|
||||
const seen = new Set();
|
||||
for (const ll of latlngs) {
|
||||
const lat = ll[0], lon = ll[1];
|
||||
if (lat == null || lon == null) continue;
|
||||
const t = _lonLatToTile(lat, lon, z);
|
||||
for (let dx = -1; dx <= 1; dx++) for (let dy = -1; dy <= 1; dy++) seen.add(`${z}/${t.x + dx}/${t.y + dy}`);
|
||||
}
|
||||
return [...seen];
|
||||
}
|
||||
|
||||
function _tileKeysForBounds(bounds, z) {
|
||||
const r = getTileRange(bounds, z);
|
||||
const keys = [];
|
||||
for (let x = r.xMin; x <= r.xMax; x++) for (let y = r.yMin; y <= r.yMax; y++) keys.push(`${z}/${x}/${y}`);
|
||||
return keys;
|
||||
}
|
||||
|
||||
async function _healCycle() {
|
||||
if (_healing || TileCache.offlineMode || !_healMap) return;
|
||||
_healing = true;
|
||||
try {
|
||||
if (!(await TileCache.probeOsm())) return;
|
||||
const z = _healMap.getZoom();
|
||||
// Visible tiles first (fills what you actually see), then the ridden route
|
||||
// from a rotating cursor so a long track trickles across cycles, not in a burst.
|
||||
let keys = _tileKeysForBounds(_healMap.getBounds(), z);
|
||||
const pts = _healPointsFn && _healPointsFn();
|
||||
if (pts && pts.length) {
|
||||
const routeKeys = _tileKeysForLatLngs(pts, z);
|
||||
if (_healCursor >= routeKeys.length) _healCursor = 0;
|
||||
keys = keys.concat(routeKeys.slice(_healCursor), routeKeys.slice(0, _healCursor));
|
||||
}
|
||||
let fetched = 0, scanned = 0, advanced = 0;
|
||||
for (const key of keys) {
|
||||
if (fetched >= HEAL_MAX_FETCH || scanned >= HEAL_MAX_SCAN) break;
|
||||
scanned++; advanced++;
|
||||
if (await getTile(key)) continue; // already cached
|
||||
try {
|
||||
const r = await fetch(`https://a.tile.openstreetmap.org/${key}.png`, { cache: "no-store" });
|
||||
if (r.ok) { await putTile(key, await r.blob()); fetched++; }
|
||||
} catch { /* leave for a later cycle */ }
|
||||
await _sleep(HEAL_THROTTLE_MS);
|
||||
}
|
||||
if (pts && pts.length) _healCursor += advanced; // continue the route next cycle
|
||||
if (fetched && _healLayer && _healLayer.redraw) _healLayer.redraw();
|
||||
} finally { _healing = false; }
|
||||
}
|
||||
|
||||
// Start healing for a map. layer = the tile layer to redraw once holes fill.
|
||||
// pointsFn() → [[lat,lon],…] of the ridden route (recording / track review), or
|
||||
// null to heal only the visible area (e.g. the roads screen).
|
||||
TileCache.startHealer = function(map, layer, pointsFn) {
|
||||
TileCache.stopHealer();
|
||||
_healMap = map; _healLayer = layer; _healPointsFn = pointsFn || null; _healCursor = 0;
|
||||
_healTimer = setInterval(_healCycle, HEAL_INTERVAL_MS);
|
||||
setTimeout(_healCycle, 3000); // first pass soon after the map opens
|
||||
_onlineHandler = () => _healCycle(); // and immediately when the link returns
|
||||
window.addEventListener("online", _onlineHandler);
|
||||
if (map && map.on) map.on("unload", TileCache.stopHealer);
|
||||
};
|
||||
|
||||
TileCache.stopHealer = function() {
|
||||
if (_healTimer) { clearInterval(_healTimer); _healTimer = null; }
|
||||
if (_onlineHandler) { window.removeEventListener("online", _onlineHandler); _onlineHandler = null; }
|
||||
_healMap = _healLayer = _healPointsFn = null; _healCursor = 0;
|
||||
};
|
||||
|
||||
window.TileCache = TileCache;
|
||||
console.log("[tilecache.js] window.TileCache ready, offline:", TileCache.offlineMode);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue