| | |
| | | let THRESHOLD_MOVING = parseInt(await this.baseSysParamService.dataByKey('vehicle.speed.threshold.moving')); //'2.0 km/h'; |
| | | let THRESHOLD_SPEEDING = parseInt(await this.baseSysParamService.dataByKey('vehicle.speed.threshold.speeding')); //'30 km/h'; |
| | | let THRESHOLD_DISPLACEMENT = parseInt(await this.baseSysParamService.dataByKey('vehicle.threshold.displacement')); //'30 km/h'; |
| | | let VEHICLE_GPS_MAX_INTERVAL_SEC = parseInt(await this.baseSysParamService.dataByKey('vehicle.gps.max.interval.sec')); //'900'; |
| | | HOUR_STARTWORK = Number.isNaN(HOUR_STARTWORK) ? 6 : HOUR_STARTWORK; |
| | | HOUR_OFFWORK = Number.isNaN(HOUR_OFFWORK) ? 22 : HOUR_OFFWORK; |
| | | THRESHOLD_MOVING = Number.isNaN(THRESHOLD_MOVING) ? 2 : THRESHOLD_MOVING; |
| | | THRESHOLD_SPEEDING = Number.isNaN(THRESHOLD_SPEEDING) ? 30 : THRESHOLD_SPEEDING; |
| | | THRESHOLD_DISPLACEMENT = Number.isNaN(THRESHOLD_DISPLACEMENT) ? 10 : THRESHOLD_DISPLACEMENT; |
| | | |
| | | VEHICLE_GPS_MAX_INTERVAL_SEC = Number.isNaN(VEHICLE_GPS_MAX_INTERVAL_SEC) ? 900 : VEHICLE_GPS_MAX_INTERVAL_SEC; |
| | | |
| | | |
| | | const rawGpsList = await this.pushVehicleGpsEntity.createQueryBuilder('gps') |
| | |
| | | HOUR_OFFWORK, |
| | | THRESHOLD_MOVING, |
| | | THRESHOLD_SPEEDING, |
| | | THRESHOLD_DISPLACEMENT |
| | | THRESHOLD_DISPLACEMENT, |
| | | VEHICLE_GPS_MAX_INTERVAL_SEC |
| | | ); |
| | | summariesToUpsert.push(summary); |
| | | } |
| | |
| | | HOUR_OFFWORK: number, |
| | | HHRESHOLD_MOVING: number, |
| | | THRESHOLD_SPEEDING: number, |
| | | THRESHOLD_DISPLACEMENT: number |
| | | THRESHOLD_DISPLACEMENT: number, |
| | | VEHICLE_GPS_MAX_INTERVAL_SEC: number |
| | | ): Partial<ReportDailyVehicleEntity> { |
| | | const departmentId = points[0]?.departmentId ?? null; |
| | | |
| | |
| | | let offHoursMoveCount = 0; // 非工作时段异动次数 |
| | | |
| | | let isOverspeeding = false; // 超速状态标记 |
| | | // 物理量只在「上一有效坐标点」与当前有效点之间计算,空坐标不参与 |
| | | let lastValidIdx = -1; |
| | | |
| | | for (let i = 0; i < points.length; i++) { |
| | | const curr = points[i]; |
| | | const speed = Number(curr.speed) || 0; |
| | | const currDate = new Date(curr.gpsTime); |
| | | const currValid = this.isValidGpsPoint(curr); |
| | | |
| | | // ==================================================== |
| | | // 顺序 1:单点状态判断(只依赖当前点的数据) |
| | |
| | | } |
| | | |
| | | // ==================================================== |
| | | // 顺序 2:相邻点微分与物理量计算(必须依赖前一点 i-1) |
| | | // 顺序 2:相邻有效点微分与物理量计算 |
| | | // ==================================================== |
| | | if (i > 0) { |
| | | const prev = points[i - 1]; |
| | | if (!currValid) { |
| | | continue; |
| | | } |
| | | if (lastValidIdx >= 0) { |
| | | const prev = points[lastValidIdx]; |
| | | const prevDate = new Date(prev.gpsTime); |
| | | |
| | | // 计算相邻两点时间差(秒) |
| | | const timeDiffSec = Math.floor((currDate.getTime() - prevDate.getTime()) / 1000); |
| | | |
| | | // 过滤网络断连/关机(时间差 > 120 秒 视为掉线,不参与物理微分计算) |
| | | if (timeDiffSec > 0 && timeDiffSec <= 120) { |
| | | |
| | | // 3.4 静止停靠达到 15 分钟(900秒)算作异常怠速 |
| | | if (timeDiffSec >= 900) { |
| | | secondsAbnormalIdle += timeDiffSec; |
| | | } |
| | | // 超过配置间隔视为掉线,不参与物理微分计算 |
| | | if (timeDiffSec > 0 && timeDiffSec <= VEHICLE_GPS_MAX_INTERVAL_SEC) { |
| | | // ================================================ |
| | | // 顺序 3:基于坐标位移划分为【行驶】或【静止停靠】 |
| | | // ================================================ |
| | |
| | | secondsMoving += timeDiffSec; |
| | | |
| | | // 3.2 累加球面真实位移距离 |
| | | totalMeter += this.getHaversineDistance( |
| | | Number(prev.latitude), |
| | | Number(prev.longitude), |
| | | Number(curr.latitude), |
| | | Number(curr.longitude), |
| | | ); |
| | | totalMeter += distance; |
| | | } else { |
| | | // 3.3 累加静止停靠时长 |
| | | secondsParked += timeDiffSec; |
| | | |
| | | // 3.4 静止停靠超过 15 分钟(900秒)算作异常怠速 |
| | | if (timeDiffSec > 900) { |
| | | secondsAbnormalIdle += timeDiffSec; |
| | | } |
| | | } |
| | | |
| | | // ================================================ |
| | |
| | | } |
| | | } |
| | | } |
| | | lastValidIdx = i; |
| | | } |
| | | |
| | | // ==================================================== |
| | |
| | | |
| | | } |
| | | |
| | | /** 经纬度缺失、非数字或 (0,0) 不参与里程/时长 */ |
| | | private isValidGpsPoint(point: { latitude?: any; longitude?: any }): boolean { |
| | | const lat = Number(point?.latitude); |
| | | const lon = Number(point?.longitude); |
| | | if (!Number.isFinite(lat) || !Number.isFinite(lon)) return false; |
| | | if (lat === 0 && lon === 0) return false; |
| | | if (Math.abs(lat) > 90 || Math.abs(lon) > 180) return false; |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 纯 JS 原生计算两点之间的球面距离 (单位: 米) |
| | | */ |