2026-04-13-2
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Traffic Grid Service - In-Memory GeoHash Grid
|
||||
* نظام شبكة التنبؤ المروري الذكي: يحول بيانات قاعدة البيانات إلى خارطة سرعة في الذاكرة
|
||||
*/
|
||||
@Injectable()
|
||||
export class TrafficGridService implements OnModuleInit {
|
||||
private readonly logger = new Logger(TrafficGridService.name);
|
||||
|
||||
// Key: "geohash:hr:dow", Value: trafficFactor
|
||||
private grid: Map<string, number> = new Map();
|
||||
private readonly GEOHASH_PRECISION = 6; // ~100m accuracy
|
||||
|
||||
constructor(private dataSource: DataSource) {}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.refreshGrid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the in-memory lookup table from PostGIS
|
||||
* إعادة بناء الجدول المرجعي في الذاكرة من قاعدة البيانات
|
||||
*/
|
||||
async refreshGrid() {
|
||||
this.logger.log('📡 Rebuilding Traffic Grid v3 (In-Memory Lookup)...');
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
|
||||
const data = await this.dataSource.query(`
|
||||
SELECT
|
||||
ST_X(ST_Centroid(geometry::geometry)) as lng,
|
||||
ST_Y(ST_Centroid(geometry::geometry)) as lat,
|
||||
p."hourOfDay", p."dayOfWeek", p."averageSpeed",
|
||||
s."averageSpeed" as base_speed
|
||||
FROM road_speed_profiles p
|
||||
JOIN road_segment_stats s ON p."segmentId" = s."segmentId"
|
||||
WHERE s.geometry IS NOT NULL
|
||||
`);
|
||||
|
||||
this.grid.clear();
|
||||
let count = 0;
|
||||
|
||||
for (const row of data) {
|
||||
const gh = this.encodeGeohash(row.lat, row.lng, this.GEOHASH_PRECISION);
|
||||
const factor = row.base_speed / Math.max(row.averageSpeed, 1);
|
||||
|
||||
// Key includes time buckets to handle day/night variations
|
||||
const key = `${gh}:${row.hourOfDay}:${row.dayOfWeek}`;
|
||||
this.grid.set(key, factor);
|
||||
count++;
|
||||
}
|
||||
|
||||
this.logger.log(`✅ Grid rebuilt in ${Date.now() - startTime}ms. Cached ${count} cells (${this.grid.size} unique keys).`);
|
||||
} catch (e) {
|
||||
this.logger.error('❌ Failed to refresh traffic grid:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the average traffic factor for a decoded polyline
|
||||
* جلب معامل الازدحام المتوسط لمخطط الرحلة من الذاكرة
|
||||
*/
|
||||
getTrafficFactor(coords: [number, number][], hr: number, dow: number): number {
|
||||
if (this.grid.size === 0) return 1.0;
|
||||
|
||||
let totalFactor = 0;
|
||||
let matches = 0;
|
||||
|
||||
// We sample points every ~100m to speed up (every 2nd or 3rd coord)
|
||||
for (let i = 0; i < coords.length; i += 2) {
|
||||
const [lng, lat] = coords[i];
|
||||
const gh = this.encodeGeohash(lat, lng, this.GEOHASH_PRECISION);
|
||||
const key = `${gh}:${hr}:${dow}`;
|
||||
|
||||
const factor = this.grid.get(key);
|
||||
if (factor) {
|
||||
totalFactor += factor;
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
|
||||
if (matches === 0) return 1.0;
|
||||
|
||||
// Average factor clipped to sane ranges (0.8 - 3.0)
|
||||
let finalFactor = totalFactor / matches;
|
||||
return Math.min(Math.max(finalFactor, 0.8), 3.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure Math GeoHash implementation (No outside dependencies)
|
||||
*/
|
||||
private encodeGeohash(lat: number, lng: number, precision: number): string {
|
||||
const BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";
|
||||
let minLat = -90, maxLat = 90;
|
||||
let minLng = -180, maxLng = 180;
|
||||
let result = "";
|
||||
let isEven = true;
|
||||
let bit = 0;
|
||||
let ch = 0;
|
||||
|
||||
while (result.length < precision) {
|
||||
if (isEven) {
|
||||
let mid = (minLng + maxLng) / 2;
|
||||
if (lng > mid) { ch |= (1 << (4 - bit)); minLng = mid; }
|
||||
else { maxLng = mid; }
|
||||
} else {
|
||||
let mid = (minLat + maxLat) / 2;
|
||||
if (lat > mid) { ch |= (1 << (4 - bit)); minLat = mid; }
|
||||
else { maxLat = mid; }
|
||||
}
|
||||
|
||||
isEven = !isEven;
|
||||
if (bit < 4) {
|
||||
bit++;
|
||||
} else {
|
||||
result += BASE32[ch];
|
||||
bit = 0;
|
||||
ch = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user