feat(gps): мосты-предположения по дорогам между GPS-якорями на LBS-участках (OSRM route, пунктир)

This commit is contained in:
2026-08-31 21:30:17 +03:00
parent 08d1fa1af3
commit 67dbf90d65
3 changed files with 66 additions and 0 deletions

View File

@@ -149,6 +149,52 @@ function badRequest(res: Response, error: string) {
return res.status(400).json({ success: false, error });
}
/**
* Мосты-предположения: если между двумя соседними по времени точными
* GPS-точками (якорями) есть хотя бы одна LBS-точка, строим между якорями
* маршрут по дорогам через OSRM Route API — целый трек без мусорных
* вышечных линий. Максимум 50 мостов на трек; OSRM недоступен — без мостов.
*/
const MAX_BRIDGES = 50;
const OSRM_ROUTE_TIMEOUT_MS = 3000;
interface TrackBridge {
points: [number, number][]; // [lat, lng]
distance: number;
}
async function buildBridges(points: Array<{ lat: number; lng: number; recordedAt: Date; isLbs: boolean }>): Promise<TrackBridge[]> {
// Якоря — точные точки (не LBS) по времени
const anchors = points.filter((p) => !p.isLbs);
const bridges: TrackBridge[] = [];
for (let i = 1; i < anchors.length && bridges.length < MAX_BRIDGES; i++) {
const a = anchors[i - 1];
const b = anchors[i];
const aTs = a.recordedAt.getTime();
const bTs = b.recordedAt.getTime();
// Мост нужен, только если между якорями есть хотя бы одна LBS-точка
const hasLbsBetween = points.some(
(p) => p.isLbs && p.recordedAt.getTime() > aTs && p.recordedAt.getTime() < bTs
);
if (!hasLbsBetween) continue;
try {
const url = `${OSRM_URL}/route/v1/driving/${a.lng},${a.lat};${b.lng},${b.lat}?overview=full&geometries=geojson`;
const res = await fetch(url, { signal: AbortSignal.timeout(OSRM_ROUTE_TIMEOUT_MS) });
if (!res.ok) continue;
const data = (await res.json()) as { code: string; routes?: Array<{ distance: number; geometry: { coordinates: [number, number][] } }> };
const route = data.routes?.[0];
if (data.code !== "Ok" || !route) continue;
bridges.push({
points: route.geometry.coordinates.map(([lng, lat]) => [lat, lng] as [number, number]),
distance: Math.round(route.distance),
});
} catch {
// OSRM недоступен для этого моста — просто пропускаем
}
}
return bridges;
}
// Максимум LBS-точек, проверяемых offroad-фильтром при выдаче трека
// (бюджет ~5 сек при ~1-2 мс на OSRM nearest и конкуренции 10)
const MAX_LBS_OFFROAD_CHECK = 2000;
@@ -694,6 +740,9 @@ export function registerGpsRoutes(app: Express) {
matched = gpsOnly.length >= 2 ? await matchTrackCached(id, from, to, gpsOnly) : null;
}
// Мосты-предположения по дорогам между GPS-якорями на LBS-участках
const bridges = await buildBridges(points);
res.json({
success: true,
points,
@@ -702,6 +751,7 @@ export function registerGpsRoutes(app: Express) {
offroadFiltered,
sampled,
...(matched !== undefined ? { matched } : {}),
...(bridges.length > 0 ? { bridges } : {}),
});
});