fix(gps): zIndex точек трека над полилиниями + capture-клики; offroad-фильтр LBS при выдаче трека (чистит старый «мяч»)
This commit is contained in:
@@ -342,7 +342,7 @@ let osrmNearestWarned = false;
|
||||
* code=NoSegment — ближайшая дорога вне радиуса поиска OSRM (порядка км),
|
||||
* надёжный сигнал LBS: возвращаем Infinity (точно offroad).
|
||||
*/
|
||||
async function offRoadDistance(lat: number, lng: number, thresholdMeters: number): Promise<number | null> {
|
||||
export async function offRoadDistance(lat: number, lng: number, thresholdMeters: number): Promise<number | null> {
|
||||
let data: { code?: string; waypoints?: Array<{ distance?: number }> };
|
||||
try {
|
||||
// ВНИМАНИЕ: OSRM ждёт координаты в порядке lng,lat
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Router } from "express";
|
||||
import type { Express, Response } from "express";
|
||||
import { authenticateToken, type AuthenticatedRequest } from "../middleware/auth.middleware";
|
||||
import { gpsStorage } from "./storage";
|
||||
import { processPosition, filterTrackPoints } from "./geozone.service";
|
||||
import { processPosition, filterTrackPoints, offRoadDistance } from "./geozone.service";
|
||||
import { hasGpsAccess } from "./access";
|
||||
import { ensureDevice, deleteDevice } from "./traccar.client";
|
||||
import { OSRM_URL } from "./osrm";
|
||||
@@ -149,6 +149,27 @@ function badRequest(res: Response, error: string) {
|
||||
return res.status(400).json({ success: false, error });
|
||||
}
|
||||
|
||||
// Максимум LBS-точек, проверяемых offroad-фильтром при выдаче трека
|
||||
// (бюджет ~5 сек при ~1-2 мс на OSRM nearest и конкуренции 10)
|
||||
const MAX_LBS_OFFROAD_CHECK = 2000;
|
||||
// Конкуренция запросов к OSRM nearest при выдаче трека
|
||||
const OFFROAD_CHECK_CONCURRENCY = 10;
|
||||
|
||||
/**
|
||||
* Простой p-limit без зависимостей: выполняет fn над элементами
|
||||
* с ограничением числа одновременных вызовов.
|
||||
*/
|
||||
async function runWithConcurrency<T>(items: T[], limit: number, fn: (item: T) => Promise<void>): Promise<void> {
|
||||
let index = 0;
|
||||
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
||||
while (index < items.length) {
|
||||
const item = items[index++];
|
||||
await fn(item);
|
||||
}
|
||||
});
|
||||
await Promise.all(workers);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Приём позиций от Traccar (публичный, по секрету)
|
||||
// =====================
|
||||
@@ -624,14 +645,46 @@ export function registerGpsRoutes(app: Express) {
|
||||
const settings = await gpsStorage.getSettings(orgId);
|
||||
const { points: filtered, filteredCount } = filterTrackPoints(all, settings);
|
||||
|
||||
// Offroad-фильтр при выдаче: СТАРЫЕ LBS-точки (помеченные до внедрения
|
||||
// фильтра при приёме) далеко от дорог рисуют «мяч» зигзагов — выкидываем
|
||||
// их из выдачи через OSRM nearest. Проверяются только isLbs-точки,
|
||||
// настоящий GPS не трогаем (поля/дворы легальны). 0 в настройке = выкл.
|
||||
let points = filtered;
|
||||
let offroadFiltered = 0;
|
||||
if (settings.offRoadMeters > 0) {
|
||||
let lbsPoints = points.filter((p) => p.isLbs);
|
||||
if (lbsPoints.length > MAX_LBS_OFFROAD_CHECK) {
|
||||
// Бюджет времени ~5 сек: прореживаем LBS-точки равномерно до лимита;
|
||||
// не попавшие в проверку остаются в выдаче как есть
|
||||
const step = Math.ceil(lbsPoints.length / MAX_LBS_OFFROAD_CHECK);
|
||||
const thinned = lbsPoints.filter((_, idx) => idx % step === 0);
|
||||
console.log(
|
||||
`[GPS] track offroad: asset=${id} LBS-точек ${lbsPoints.length} > ${MAX_LBS_OFFROAD_CHECK}, проверяем ${thinned.length} (прорежено)`
|
||||
);
|
||||
lbsPoints = thinned;
|
||||
}
|
||||
if (lbsPoints.length > 0) {
|
||||
const dropped = new Set<(typeof lbsPoints)[number]>();
|
||||
await runWithConcurrency(lbsPoints, OFFROAD_CHECK_CONCURRENCY, async (p) => {
|
||||
const dist = await offRoadDistance(p.lat, p.lng, settings.offRoadMeters);
|
||||
if (dist !== null) dropped.add(p);
|
||||
});
|
||||
if (dropped.size > 0) {
|
||||
offroadFiltered = dropped.size;
|
||||
points = points.filter((p) => !dropped.has(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sampled = false;
|
||||
if (points.length > MAX_TRACK_POINTS) {
|
||||
// Точки ДО прореживания — для гарантии включения последней точки трека
|
||||
const beforeSample = points;
|
||||
const step = Math.ceil(points.length / MAX_TRACK_POINTS);
|
||||
points = points.filter((_, idx) => idx % step === 0);
|
||||
// Всегда включаем последнюю точку трека
|
||||
if (points[points.length - 1] !== filtered[filtered.length - 1]) {
|
||||
points.push(filtered[filtered.length - 1]);
|
||||
if (points[points.length - 1] !== beforeSample[beforeSample.length - 1]) {
|
||||
points.push(beforeSample[beforeSample.length - 1]);
|
||||
}
|
||||
sampled = true;
|
||||
}
|
||||
@@ -650,6 +703,7 @@ export function registerGpsRoutes(app: Express) {
|
||||
points,
|
||||
total,
|
||||
filteredCount,
|
||||
offroadFiltered,
|
||||
sampled,
|
||||
...(matched !== undefined ? { matched } : {}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user