feat(gps): фильтр «далеко от дороги» в LBS-режиме (offRoadMeters, OSRM nearest); пробег «точно + примерно (по вышкам)» в Маршрутах
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { gpsStorage } from "./storage";
|
||||
import { OSRM_URL } from "./osrm";
|
||||
import { storage } from "../storage";
|
||||
import { webPushService } from "../services/web-push.service";
|
||||
import { eventBus, publishNotificationSSE } from "../routes/shared";
|
||||
@@ -309,8 +310,10 @@ export function filterTrackPoints<T extends { lat: number; lng: number; recorded
|
||||
* Режим «потерян GPS»: идущие stale re-send'ы — сигнал, что трекер не видит
|
||||
* спутники. Пока последний stale моложе 10 минут, свежие точки помечаются
|
||||
* is_lbs (координаты сотовой вышки): сохраняются и отдаются клиенту
|
||||
* (пунктирное отображение), но геозоны по ним не проверяются. Первый
|
||||
* настоящий GPS-фикс после окна stale выключает режим (gps_lost = false).
|
||||
* (пунктирное отображение), но геозоны по ним не проверяются. Дополнительно
|
||||
* LBS-точки дальше off_road_meters от ближайшей дороги (OSRM nearest)
|
||||
* отбрасываются совсем. Первый настоящий GPS-фикс после окна stale
|
||||
* выключает режим (gps_lost = false).
|
||||
*/
|
||||
|
||||
// In-memory фильтры по объектам (процесс один; при рестарте состояние
|
||||
@@ -326,6 +329,49 @@ const assetLastStaleAt = new Map<number, number>();
|
||||
// Окно после последнего stale re-send'а, в котором свежие точки считаются LBS
|
||||
const STALE_LBS_WINDOW_MS = 10 * 60 * 1000;
|
||||
|
||||
// Таймаут запроса к OSRM Nearest API (фильтр «далеко от дороги»)
|
||||
const OSRM_NEAREST_TIMEOUT_MS = 2000;
|
||||
// Warn про недоступность OSRM для nearest пишем один раз до следующего успешного ответа
|
||||
let osrmNearestWarned = false;
|
||||
|
||||
/**
|
||||
* Проверка «далеко от дороги» через OSRM Nearest API (только для LBS-режима).
|
||||
* Возвращает расстояние до ближайшей дороги (м), если оно БОЛЬШЕ порога
|
||||
* (точку надо отбросить), иначе null.
|
||||
* При недоступности OSRM/таймауте — null: приём точки не блокируем.
|
||||
* code=NoSegment — ближайшая дорога вне радиуса поиска OSRM (порядка км),
|
||||
* надёжный сигнал LBS: возвращаем Infinity (точно offroad).
|
||||
*/
|
||||
async function offRoadDistance(lat: number, lng: number, thresholdMeters: number): Promise<number | null> {
|
||||
let data: { code?: string; waypoints?: Array<{ distance?: number }> };
|
||||
try {
|
||||
// ВНИМАНИЕ: OSRM ждёт координаты в порядке lng,lat
|
||||
const res = await fetch(`${OSRM_URL}/nearest/v1/driving/${lng},${lat}`, {
|
||||
signal: AbortSignal.timeout(OSRM_NEAREST_TIMEOUT_MS),
|
||||
});
|
||||
data = (await res.json()) as typeof data;
|
||||
if (osrmNearestWarned) {
|
||||
console.log("[GPS] OSRM снова доступен (nearest)");
|
||||
osrmNearestWarned = false;
|
||||
}
|
||||
} catch (err) {
|
||||
// OSRM недоступен — пропускаем проверку, точка идёт обычным путём
|
||||
if (!osrmNearestWarned) {
|
||||
console.warn(`[GPS] OSRM недоступен для nearest (${OSRM_URL}):`, err instanceof Error ? err.message : err);
|
||||
osrmNearestWarned = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Дорога вне радиуса поиска — точно далеко от дороги
|
||||
if (data.code === "NoSegment") return Infinity;
|
||||
if (data.code !== "Ok") return null;
|
||||
|
||||
const dist = data.waypoints?.[0]?.distance;
|
||||
if (typeof dist !== "number") return null;
|
||||
return dist > thresholdMeters ? dist : null;
|
||||
}
|
||||
|
||||
function getAssetWalker(assetId: number, prev: PrevPoint | null, settings: GpsFilterSettings): JumpFilterWalker {
|
||||
let walker = assetWalkers.get(assetId);
|
||||
if (!walker) {
|
||||
@@ -408,6 +454,22 @@ export async function processPosition(
|
||||
const lastStaleAt = assetLastStaleAt.get(assetId);
|
||||
const isLbs = lastStaleAt !== undefined && Date.now() - lastStaleAt < STALE_LBS_WINDOW_MS;
|
||||
|
||||
// Фильтр «далеко от дороги» — ТОЛЬКО в режиме потери GPS (LBS-точки):
|
||||
// координаты сотовых вышек обычно >100 м от ближайшей дороги, настоящий
|
||||
// GPS — почти никогда (p90 ≈ 7 м). Вне LBS-режима проверку не выполняем:
|
||||
// при живом GPS заезды в поля/дворы легальны.
|
||||
if (isLbs && settings.offRoadMeters > 0) {
|
||||
const distToRoad = await offRoadDistance(input.lat, input.lng, settings.offRoadMeters);
|
||||
if (distToRoad !== null) {
|
||||
console.log(
|
||||
`[GPS] skipped offroad: asset=${assetId} dist_to_road=${distToRoad === Infinity ? "> радиуса поиска OSRM" : `${Math.round(distToRoad)}m`} > порога ${settings.offRoadMeters}m`
|
||||
);
|
||||
// Точку не сохраняем, координаты и геозоны не трогаем — только «устройство живо»
|
||||
await gpsStorage.touchAssetSeen(assetId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Фильтр джиттера (округление мелких перемещений)
|
||||
if (settings.jitterMeters > 0 && prev) {
|
||||
const distance = haversineMeters(prev.lat, prev.lng, input.lat, input.lng);
|
||||
|
||||
Reference in New Issue
Block a user