2026-04-13-1

This commit is contained in:
Hamza-Ayed
2026-04-13 01:44:32 +03:00
parent 5ebd7ea3b1
commit 3b43ff983a
5 changed files with 118 additions and 12 deletions
+2 -1
View File
@@ -4,11 +4,12 @@ import { MapsService } from './maps.service';
import { MapsController } from './maps.controller'; import { MapsController } from './maps.controller';
import { RoadSegmentStat } from './road-stat.entity'; import { RoadSegmentStat } from './road-stat.entity';
import { CandidateRoad } from './candidate-road.entity'; import { CandidateRoad } from './candidate-road.entity';
import { RoadSpeedProfile } from './road-speed-profile.entity';
import { RedisModule } from '../common/redis.module'; import { RedisModule } from '../common/redis.module';
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([RoadSegmentStat, CandidateRoad]), TypeOrmModule.forFeature([RoadSegmentStat, CandidateRoad, RoadSpeedProfile]),
RedisModule, RedisModule,
], ],
controllers: [MapsController], controllers: [MapsController],
+100 -4
View File
@@ -1,12 +1,20 @@
import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { RoadSpeedProfile } from './road-speed-profile.entity';
import axios from 'axios'; import axios from 'axios';
@Injectable() @Injectable()
export class MapsService { export class MapsService {
private readonly graphHopperUrl: string; private readonly graphHopperUrl: string;
constructor(private configService: ConfigService) { constructor(
private configService: ConfigService,
@InjectRepository(RoadSpeedProfile)
private speedProfileRepo: Repository<RoadSpeedProfile>,
private dataSource: DataSource
) {
this.graphHopperUrl = this.configService.get('GRAPH_HOPPER_URL', 'http://routing:8080'); this.graphHopperUrl = this.configService.get('GRAPH_HOPPER_URL', 'http://routing:8080');
} }
@@ -25,20 +33,73 @@ export class MapsService {
locale: 'en', locale: 'en',
calc_points: true, calc_points: true,
points_encoded: true, points_encoded: true,
algorithm: 'alternative_route',
'ch.disable': true, // Required for alternative routes
'alternative_route.max_paths': 2, // Return main route + 1 alternative
'alternative_route.max_weight_factor': 1.6,
'alternative_route.max_share_factor': 0.6
}; };
console.log(`Routing Request: ${waypoints.length} points via ${profile} on ${this.graphHopperUrl}`); console.log(`Routing Request: ${waypoints.length} points via ${profile} on ${this.graphHopperUrl}`);
const response = await axios.post(`${this.graphHopperUrl}/route`, payload, { timeout: 10000 }); const response = await axios.post(`${this.graphHopperUrl}/route`, payload, { timeout: 10000 });
console.log('Routing SUCCESS'); console.log('Routing SUCCESS');
const route = response.data.paths[0]; const paths = response.data.paths;
if (!route) throw new HttpException('No route found', HttpStatus.NOT_FOUND); if (!paths || paths.length === 0) throw new HttpException('No route found', HttpStatus.NOT_FOUND);
const route = paths[0];
// --- PHASE 3: TRAFFIC-AWARE ADJUSTMENT ---
let baseDuration = route.time / 1000;
let trafficAwareDuration = baseDuration;
let trafficFactor = 1.0;
try {
const now = new Date();
const hr = now.getHours();
const dow = now.getDay();
// Decode polyline for spatial mapping (server-side only)
const coords = this.decodePolyline(route.points);
const routeGeo = {
type: 'LineString',
coordinates: coords
};
// Query historical profiles that intersect with our route
const profiles = await this.dataSource.query(`
SELECT AVG(p."averageSpeed") as profile_speed, AVG(s."averageSpeed") as base_speed
FROM road_speed_profiles p
JOIN road_segment_stats s ON p."segmentId" = s."segmentId"
WHERE p."hourOfDay" = $1 AND p."dayOfWeek" = $2
AND ST_DWithin(s.geometry, ST_GeomFromGeoJSON($3), 0.0002)
`, [hr, dow, JSON.stringify(routeGeo)]);
if (profiles[0] && profiles[0].profile_speed && profiles[0].base_speed) {
trafficFactor = profiles[0].base_speed / Math.max(profiles[0].profile_speed, 1);
trafficFactor = Math.min(Math.max(trafficFactor, 0.8), 3.0);
trafficAwareDuration = baseDuration * trafficFactor;
}
} catch (e) {
console.warn('Traffic adjustment failed, using calibrated base:', e.message);
}
// Process alternative routes if any without breaking existing frontend variables
const alternatives = paths.slice(1).map(alt => ({
distance: alt.distance,
duration: Math.round(alt.time / 1000),
points: alt.points,
bbox: alt.bbox
}));
return { return {
distance: route.distance, distance: route.distance,
duration: route.time / 1000, duration: Math.round(baseDuration),
trafficAwareDuration: Math.round(trafficAwareDuration),
trafficFactor: Math.round(trafficFactor * 100) / 100,
points: route.points, points: route.points,
bbox: route.bbox, bbox: route.bbox,
alternatives: alternatives // NEW: array of other routes
}; };
} catch (error) { } catch (error) {
const msg = error.response ? `GH Error: ${JSON.stringify(error.response.data)}` : `DNS/Connection Error: ${error.message}`; const msg = error.response ? `GH Error: ${JSON.stringify(error.response.data)}` : `DNS/Connection Error: ${error.message}`;
@@ -47,6 +108,41 @@ export class MapsService {
} }
} }
/**
* Manual decoder for Google Polyline algorithm (Server-side spatial matching)
*/
private decodePolyline(encoded: string): [number, number][] {
const points: [number, number][] = [];
let index = 0, len = encoded.length;
let lat = 0, lng = 0;
while (index < len) {
let b, shift = 0, result = 0;
do {
b = encoded.charCodeAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
let dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do {
b = encoded.charCodeAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
let dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
lng += dlng;
points.push([lng * 1e-5, lat * 1e-5]); // Note: GraphHopper polyline is [lng, lat] usually or lat, lng depending on config.
// GH polyline standard is [lat, lng] but we need [lng, lat] for PostGIS GeoJSON Coordinates.
// Let's verify: GraphHopper default polyline is Lat,Lng.
}
return points;
}
async getMapConfig() { async getMapConfig() {
// Return basic map configuration for clients // Return basic map configuration for clients
// إرجاع إعدادات الخريطة للواجهة الأمامية // إرجاع إعدادات الخريطة للواجهة الأمامية
+1 -1
View File
@@ -52,7 +52,7 @@ services:
volumes: volumes:
- ./infrastructure/osm-data:/data - ./infrastructure/osm-data:/data
- ./infrastructure/docker/graphhopper/config.yml:/graphhopper/config.yml - ./infrastructure/docker/graphhopper/config.yml:/graphhopper/config.yml
command: ["-i", "/data/mena_full.osm.pbf", "-c", "config.yml"] command: ["-i", "/data/master_map.osm.pbf", "-c", "config.yml"]
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"] test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval: 30s interval: 30s
BIN
View File
Binary file not shown.
+14 -5
View File
@@ -1,5 +1,5 @@
graphhopper: graphhopper:
datareader.file: /data/jordan-latest.osm.pbf datareader.file: /data/master_map.osm.pbf
graph.location: /data/graph-cache graph.location: /data/graph-cache
import.osm.ignored_highways: "" import.osm.ignored_highways: ""
graph.encoded_values: road_class,road_environment,max_speed,road_access,surface graph.encoded_values: road_class,road_environment,max_speed,road_access,surface
@@ -8,12 +8,21 @@ graphhopper:
- name: car - name: car
custom_model: custom_model:
distance_influence: 70 distance_influence: 70
priority:
- if: "true"
multiply_by: 1.0
speed: speed:
- if: "true" - if: "true"
limit_to: 100 limit_to: 60
- if: "road_class == MOTORWAY || road_class == TRUNK"
limit_to: 60
- if: "road_class == PRIMARY"
limit_to: 45
- if: "road_class == SECONDARY"
limit_to: 28
- if: "road_class == TERTIARY"
limit_to: 20
- if: "road_class == RESIDENTIAL || road_class == UNCLASSIFIED"
limit_to: 14
- if: "road_class == LIVING_STREET || road_class == SERVICE"
limit_to: 14
profiles_ch: profiles_ch:
- profile: car - profile: car