feat(gps): скорость в км/ч (конверсия узлов Traccar + backfill), деление маршрута на поездки, проигрывание от любой точки, скорости x100-x1000
This commit is contained in:
@@ -58,11 +58,13 @@ function normalizePoint(raw: any): NormalizedPoint | null {
|
||||
const lat = parseNum(p.latitude);
|
||||
const lng = parseNum(p.longitude);
|
||||
if (lat === null || lng === null) return null;
|
||||
// Traccar отдаёт скорость в узлах (knots) — в БД храним км/ч
|
||||
const speedKnots = parseNum(p.speed);
|
||||
return {
|
||||
externalId: String(raw.device.uniqueId),
|
||||
lat,
|
||||
lng,
|
||||
speed: parseNum(p.speed),
|
||||
speed: speedKnots !== null ? Math.round(speedKnots * 1.852 * 10) / 10 : null,
|
||||
course: parseNum(p.course),
|
||||
accuracy: parseNum(p.accuracy),
|
||||
recordedAt: parseDateParam(p.fixTime ?? p.deviceTime),
|
||||
@@ -178,13 +180,15 @@ export function registerGpsRoutes(app: Express) {
|
||||
|
||||
router.put("/settings", async (req: AuthenticatedRequest, res: Response) => {
|
||||
// Обновляем только переданные поля (обратная совместимость с клиентом,
|
||||
// который шлёт только 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;
|
||||
const tripStopMinutes =
|
||||
req.body?.tripStopMinutes !== undefined ? Number(req.body.tripStopMinutes) : current.tripStopMinutes;
|
||||
|
||||
if (!Number.isInteger(jitterMeters) || jitterMeters < 0 || jitterMeters > 1000) {
|
||||
return badRequest(res, "jitterMeters: целое число от 0 до 1000");
|
||||
@@ -192,9 +196,17 @@ export function registerGpsRoutes(app: Express) {
|
||||
if (!Number.isInteger(maxJumpSpeedKmh) || maxJumpSpeedKmh < 0 || maxJumpSpeedKmh > 5000) {
|
||||
return badRequest(res, "maxJumpSpeedKmh: целое число от 0 до 5000 (0 = фильтр выключен)");
|
||||
}
|
||||
if (!Number.isInteger(tripStopMinutes) || tripStopMinutes < 0 || tripStopMinutes > 120) {
|
||||
return badRequest(res, "tripStopMinutes: целое число от 0 до 120 (0 = не делить на поездки)");
|
||||
}
|
||||
|
||||
const row = await gpsStorage.upsertSettings(req.user!.organizationId, { jitterMeters, maxJumpSpeedKmh });
|
||||
res.json({ success: true, jitterMeters: row.jitterMeters, maxJumpSpeedKmh: row.maxJumpSpeedKmh });
|
||||
const row = await gpsStorage.upsertSettings(req.user!.organizationId, { jitterMeters, maxJumpSpeedKmh, tripStopMinutes });
|
||||
res.json({
|
||||
success: true,
|
||||
jitterMeters: row.jitterMeters,
|
||||
maxJumpSpeedKmh: row.maxJumpSpeedKmh,
|
||||
tripStopMinutes: row.tripStopMinutes,
|
||||
});
|
||||
});
|
||||
|
||||
// =====================
|
||||
|
||||
@@ -462,7 +462,7 @@ export class GpsStorage {
|
||||
// =====================
|
||||
|
||||
/** Настройки GPS организации; если строки нет — дефолты */
|
||||
async getSettings(organizationId: number): Promise<{ jitterMeters: number; maxJumpSpeedKmh: number }> {
|
||||
async getSettings(organizationId: number): Promise<{ jitterMeters: number; maxJumpSpeedKmh: number; tripStopMinutes: number }> {
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(gpsSettings)
|
||||
@@ -470,12 +470,13 @@ export class GpsStorage {
|
||||
return {
|
||||
jitterMeters: row?.jitterMeters ?? 25,
|
||||
maxJumpSpeedKmh: row?.maxJumpSpeedKmh ?? 250,
|
||||
tripStopMinutes: row?.tripStopMinutes ?? 10,
|
||||
};
|
||||
}
|
||||
|
||||
async upsertSettings(
|
||||
organizationId: number,
|
||||
settings: { jitterMeters: number; maxJumpSpeedKmh: number }
|
||||
settings: { jitterMeters: number; maxJumpSpeedKmh: number; tripStopMinutes: number }
|
||||
): Promise<GpsSettings> {
|
||||
const [row] = await db
|
||||
.insert(gpsSettings)
|
||||
|
||||
Reference in New Issue
Block a user