feat(gps): last_seen по времени приёма, фильтр джиттера (настройки), линейка, трек по скорости; fix: карта не перекрывает диалоги (isolation)
This commit is contained in:
@@ -31,6 +31,18 @@ export function pointInPolygon(lat: number, lng: number, polygon: Array<[number,
|
||||
return inside;
|
||||
}
|
||||
|
||||
/** Расстояние между двумя точками (haversine), в метрах. Без зависимостей. */
|
||||
export function haversineMeters(lat1: number, lng1: number, lat2: number, lng2: number): number {
|
||||
const R = 6371000; // радиус Земли, м
|
||||
const toRad = (deg: number) => (deg * Math.PI) / 180;
|
||||
const dLat = toRad(lat2 - lat1);
|
||||
const dLng = toRad(lng2 - lng1);
|
||||
const a =
|
||||
Math.sin(dLat / 2) ** 2 +
|
||||
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) ** 2;
|
||||
return 2 * R * Math.asin(Math.sqrt(a));
|
||||
}
|
||||
|
||||
export interface ProcessPositionInput {
|
||||
lat: number;
|
||||
lng: number;
|
||||
@@ -43,6 +55,16 @@ export interface ProcessPositionInput {
|
||||
/**
|
||||
* Основная точка входа: сохраняет позицию, обновляет last_* объекта,
|
||||
* проверяет геозоны и рассылает SSE/уведомления.
|
||||
*
|
||||
* Семантика времени:
|
||||
* - last_seen_at = ВСЕГДА время ПРИЁМА сервером (онлайн-индикатор на карте);
|
||||
* - last_recorded_at = время точки по устройству (fixTime трекера);
|
||||
* - gps_positions.recorded_at = время устройства (для треков).
|
||||
*
|
||||
* Фильтр джиттера: если в настройках jitterMeters > 0, у объекта есть
|
||||
* предыдущая точка, расстояние до неё < jitterMeters и скорость мала
|
||||
* (null или < 5 км/ч) — точка отбрасывается: не пишется в gps_positions,
|
||||
* координаты и геозоны не трогаются, но last_seen_at/is_online обновляются.
|
||||
*/
|
||||
export async function processPosition(
|
||||
assetId: number,
|
||||
@@ -51,7 +73,26 @@ export async function processPosition(
|
||||
): Promise<void> {
|
||||
const recordedAt = input.recordedAt ?? new Date();
|
||||
|
||||
// 1. Сохраняем точку в историю
|
||||
const asset = await gpsStorage.getAsset(assetId, organizationId);
|
||||
if (!asset) return;
|
||||
|
||||
// Фильтр джиттера (округление мелких перемещений)
|
||||
const settings = await gpsStorage.getSettings(organizationId);
|
||||
if (
|
||||
settings.jitterMeters > 0 &&
|
||||
asset.lastLat !== null &&
|
||||
asset.lastLng !== null
|
||||
) {
|
||||
const distance = haversineMeters(asset.lastLat, asset.lastLng, input.lat, input.lng);
|
||||
const speed = input.speed ?? null;
|
||||
if (distance < settings.jitterMeters && (speed === null || speed < 5)) {
|
||||
// Устройство живо — фиксируем факт приёма, точку не сохраняем
|
||||
await gpsStorage.touchAssetSeen(assetId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Сохраняем точку в историю (recorded_at — время устройства, для треков)
|
||||
await gpsStorage.insertPosition({
|
||||
assetId,
|
||||
organizationId,
|
||||
@@ -63,7 +104,7 @@ export async function processPosition(
|
||||
recordedAt,
|
||||
});
|
||||
|
||||
// 2. Обновляем последнюю позицию объекта (и переводим в online)
|
||||
// 2. Обновляем последнюю позицию объекта (last_seen_at = время приёма, online)
|
||||
await gpsStorage.updateAssetLastPosition(assetId, {
|
||||
lat: input.lat,
|
||||
lng: input.lng,
|
||||
@@ -88,21 +129,19 @@ export async function processPosition(
|
||||
});
|
||||
|
||||
// 4. Проверка геозон
|
||||
await checkGeozones(assetId, organizationId, input, recordedAt);
|
||||
await checkGeozones(asset, input, recordedAt);
|
||||
}
|
||||
|
||||
async function checkGeozones(
|
||||
assetId: number,
|
||||
organizationId: number,
|
||||
asset: GpsAsset,
|
||||
input: ProcessPositionInput,
|
||||
recordedAt: Date
|
||||
): Promise<void> {
|
||||
const { id: assetId, organizationId } = asset;
|
||||
const geozones = await gpsStorage.listActiveGeozones(organizationId);
|
||||
if (geozones.length === 0) return;
|
||||
|
||||
const states = await gpsStorage.getGeozoneStatesForAsset(assetId);
|
||||
const asset = await gpsStorage.getAsset(assetId, organizationId);
|
||||
if (!asset) return;
|
||||
|
||||
for (const geozone of geozones) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user