fix(gps): отсев устаревших копий и LBS-телепортов (stale/jump фильтры), настройка maxJumpSpeedKmh, фильтрация трека при выдаче
This commit is contained in:
@@ -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 } from "./geozone.service";
|
||||
import { processPosition, classifyPoint } from "./geozone.service";
|
||||
import { ensureDevice, deleteDevice } from "./traccar.client";
|
||||
import { storage } from "../storage";
|
||||
|
||||
@@ -173,16 +173,28 @@ export function registerGpsRoutes(app: Express) {
|
||||
|
||||
router.get("/settings", async (req: AuthenticatedRequest, res: Response) => {
|
||||
const settings = await gpsStorage.getSettings(req.user!.organizationId);
|
||||
res.json({ success: true, jitterMeters: settings.jitterMeters });
|
||||
res.json({ success: true, ...settings });
|
||||
});
|
||||
|
||||
router.put("/settings", async (req: AuthenticatedRequest, res: Response) => {
|
||||
const jitterMeters = Number(req.body?.jitterMeters);
|
||||
// Обновляем только переданные поля (обратная совместимость с клиентом,
|
||||
// который шлёт только jitterMeters)
|
||||
const current = await gpsStorage.getSettings(req.user!.organizationId);
|
||||
|
||||
const jitterMeters =
|
||||
req.body?.jitterMeters !== undefined ? Number(req.body.jitterMeters) : current.jitterMeters;
|
||||
const maxJumpSpeedKmh =
|
||||
req.body?.maxJumpSpeedKmh !== undefined ? Number(req.body.maxJumpSpeedKmh) : current.maxJumpSpeedKmh;
|
||||
|
||||
if (!Number.isInteger(jitterMeters) || jitterMeters < 0 || jitterMeters > 1000) {
|
||||
return badRequest(res, "jitterMeters: целое число от 0 до 1000");
|
||||
}
|
||||
const row = await gpsStorage.upsertSettings(req.user!.organizationId, jitterMeters);
|
||||
res.json({ success: true, jitterMeters: row.jitterMeters });
|
||||
if (!Number.isInteger(maxJumpSpeedKmh) || maxJumpSpeedKmh < 0 || maxJumpSpeedKmh > 5000) {
|
||||
return badRequest(res, "maxJumpSpeedKmh: целое число от 0 до 5000 (0 = фильтр выключен)");
|
||||
}
|
||||
|
||||
const row = await gpsStorage.upsertSettings(req.user!.organizationId, { jitterMeters, maxJumpSpeedKmh });
|
||||
res.json({ success: true, jitterMeters: row.jitterMeters, maxJumpSpeedKmh: row.maxJumpSpeedKmh });
|
||||
});
|
||||
|
||||
// =====================
|
||||
@@ -306,19 +318,36 @@ export function registerGpsRoutes(app: Express) {
|
||||
const all = await gpsStorage.getTrack(id, orgId, from ?? undefined, to ?? undefined);
|
||||
const total = all.length;
|
||||
|
||||
let points = all;
|
||||
// Фильтрация сохранённых данных при выдаче (без удаления из БД):
|
||||
// пропускаем stale (устаревшие копии) и jump (LBS-скачки) — те же правила,
|
||||
// что при приёме, сравнение с предыдущей принятой точкой
|
||||
const settings = await gpsStorage.getSettings(orgId);
|
||||
const filtered: typeof all = [];
|
||||
let filteredCount = 0;
|
||||
let prevAccepted: { lat: number; lng: number; recordedAt: Date } | null = null;
|
||||
for (const point of all) {
|
||||
const current = { lat: point.lat, lng: point.lng, recordedAt: point.recordedAt };
|
||||
if (classifyPoint(prevAccepted, current, settings) === "accept") {
|
||||
filtered.push(point);
|
||||
prevAccepted = current;
|
||||
} else {
|
||||
filteredCount++;
|
||||
}
|
||||
}
|
||||
|
||||
let points = filtered;
|
||||
let sampled = false;
|
||||
if (total > MAX_TRACK_POINTS) {
|
||||
const step = Math.ceil(total / MAX_TRACK_POINTS);
|
||||
points = all.filter((_, idx) => idx % step === 0);
|
||||
if (points.length > MAX_TRACK_POINTS) {
|
||||
const step = Math.ceil(points.length / MAX_TRACK_POINTS);
|
||||
points = points.filter((_, idx) => idx % step === 0);
|
||||
// Всегда включаем последнюю точку трека
|
||||
if (points[points.length - 1] !== all[total - 1]) {
|
||||
points.push(all[total - 1]);
|
||||
if (points[points.length - 1] !== filtered[filtered.length - 1]) {
|
||||
points.push(filtered[filtered.length - 1]);
|
||||
}
|
||||
sampled = true;
|
||||
}
|
||||
|
||||
res.json({ success: true, points, total, sampled });
|
||||
res.json({ success: true, points, total, filteredCount, sampled });
|
||||
});
|
||||
|
||||
// =====================
|
||||
|
||||
Reference in New Issue
Block a user