2026-04-14-8 auth and commercial
This commit is contained in:
Vendored
+101
-72
@@ -20,100 +20,129 @@ const common_1 = require("@nestjs/common");
|
||||
const config_1 = require("@nestjs/config");
|
||||
const typeorm_1 = require("@nestjs/typeorm");
|
||||
const typeorm_2 = require("typeorm");
|
||||
const road_stat_entity_1 = require("./road-stat.entity");
|
||||
const redis_service_1 = require("../common/redis.service");
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
const road_stat_entity_1 = require("./road-stat.entity");
|
||||
const traffic_grid_service_1 = require("./traffic-grid.service");
|
||||
const geocoding_service_1 = require("../geocoding/geocoding.service");
|
||||
let MapsService = class MapsService {
|
||||
configService;
|
||||
roadStatRepo;
|
||||
dataSource;
|
||||
redisService;
|
||||
trafficGrid;
|
||||
geocodingService;
|
||||
graphHopperUrl;
|
||||
TRAFFIC_CACHE_KEY = 'traffic_snapshot';
|
||||
constructor(configService, roadStatRepo, dataSource, redisService) {
|
||||
constructor(configService, roadStatRepo, trafficGrid, geocodingService) {
|
||||
this.configService = configService;
|
||||
this.roadStatRepo = roadStatRepo;
|
||||
this.dataSource = dataSource;
|
||||
this.redisService = redisService;
|
||||
this.graphHopperUrl = this.configService.get('GRAPH_HOPPER_URL', 'http://map-routing:8080');
|
||||
this.trafficGrid = trafficGrid;
|
||||
this.geocodingService = geocodingService;
|
||||
this.graphHopperUrl = this.configService.get('GRAPH_HOPPER_URL', 'http://routing:8080');
|
||||
}
|
||||
async getRoute(from, to) {
|
||||
const congestedSegments = await this.redisService.get(this.TRAFFIC_CACHE_KEY) || [];
|
||||
const buffer = 0.05;
|
||||
const minLat = Math.min(from[0], to[0]) - buffer;
|
||||
const maxLat = Math.max(from[0], to[0]) + buffer;
|
||||
const minLng = Math.min(from[1], to[1]) - buffer;
|
||||
const maxLng = Math.max(from[1], to[1]) + buffer;
|
||||
const nearbyCongestion = congestedSegments.filter(seg => {
|
||||
const geom = JSON.parse(typeof seg.geojson === 'string' ? seg.geojson : JSON.stringify(seg.geojson));
|
||||
return geom.coordinates.some(([lng, lat]) => lat >= minLat && lat <= maxLat && lng >= minLng && lng <= maxLng);
|
||||
}).slice(0, 50);
|
||||
const customModel = {
|
||||
speed: [],
|
||||
areas: {},
|
||||
};
|
||||
nearbyCongestion.forEach((seg, index) => {
|
||||
const areaId = `cong_${index}`;
|
||||
const geojson = typeof seg.geojson === 'string' ? JSON.parse(seg.geojson) : seg.geojson;
|
||||
customModel.areas[areaId] = {
|
||||
type: 'Feature',
|
||||
geometry: geojson,
|
||||
properties: {}
|
||||
};
|
||||
customModel.speed.push({
|
||||
if: `in_${areaId}`,
|
||||
multiply_by: Math.max(0.1, 1 / seg.congestionFactor)
|
||||
});
|
||||
});
|
||||
async getRoute(waypoints, profile = 'car') {
|
||||
if (waypoints.length < 2) {
|
||||
throw new common_1.HttpException('At least two waypoints are required', common_1.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
try {
|
||||
const params = {
|
||||
point: [`${from[0]},${from[1]}`, `${to[0]},${to[1]}`],
|
||||
profile: 'car',
|
||||
const ghPoints = waypoints.map(wp => [wp[1], wp[0]]);
|
||||
let startName = 'Unknown Location';
|
||||
let endName = 'Unknown Location';
|
||||
try {
|
||||
const startWp = waypoints[0];
|
||||
const endWp = waypoints[waypoints.length - 1];
|
||||
const [startRes, endRes] = await Promise.all([
|
||||
this.geocodingService.reverseGeocode(startWp[0], startWp[1]),
|
||||
this.geocodingService.reverseGeocode(endWp[0], endWp[1])
|
||||
]);
|
||||
const formatName = (r) => {
|
||||
const parts = [r.name_ar || r.name, r.neighbourhood, r.district, r.governorate].filter(Boolean);
|
||||
const uniqueParts = [...new Set(parts)];
|
||||
return uniqueParts.length > 0 ? uniqueParts.join('، ') : 'Unknown Location';
|
||||
};
|
||||
if (startRes && startRes.length > 0)
|
||||
startName = formatName(startRes[0]);
|
||||
if (endRes && endRes.length > 0)
|
||||
endName = formatName(endRes[0]);
|
||||
}
|
||||
catch (e) {
|
||||
console.warn('Geocoding internal error during routing:', e);
|
||||
}
|
||||
const payload = {
|
||||
points: ghPoints,
|
||||
profile: profile,
|
||||
locale: 'en',
|
||||
calc_points: true,
|
||||
points_encoded: true,
|
||||
elevation: false,
|
||||
type: 'json',
|
||||
};
|
||||
if (customModel.speed.length > 0) {
|
||||
params.custom_model = JSON.stringify(customModel);
|
||||
if (waypoints.length === 2) {
|
||||
payload.algorithm = 'alternative_route';
|
||||
payload['ch.disable'] = true;
|
||||
payload['alternative_route.max_paths'] = 2;
|
||||
payload['alternative_route.max_weight_factor'] = 1.6;
|
||||
payload['alternative_route.max_share_factor'] = 0.6;
|
||||
}
|
||||
const response = await axios_1.default.get(`${this.graphHopperUrl}/route`, {
|
||||
params,
|
||||
paramsSerializer: (params) => {
|
||||
const searchParams = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(v => searchParams.append(key, v));
|
||||
}
|
||||
else {
|
||||
searchParams.append(key, value);
|
||||
}
|
||||
});
|
||||
return searchParams.toString();
|
||||
},
|
||||
});
|
||||
console.log('Routing Successful with congestion:', customModel.speed.length, 'segments');
|
||||
const route = response.data.paths[0];
|
||||
if (!route) {
|
||||
console.log(`Routing Request: ${waypoints.length} points via ${profile} on ${this.graphHopperUrl}`);
|
||||
const response = await axios_1.default.post(`${this.graphHopperUrl}/route`, payload, { timeout: 10000 });
|
||||
console.log('Routing SUCCESS');
|
||||
const paths = response.data.paths;
|
||||
if (!paths || paths.length === 0)
|
||||
throw new common_1.HttpException('No route found', common_1.HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const route = paths[0];
|
||||
const now = new Date();
|
||||
const hr = now.getHours();
|
||||
const dow = now.getDay();
|
||||
const coords = this.decodePolyline(route.points);
|
||||
const trafficFactor = this.trafficGrid.getTrafficFactor(coords, hr, dow);
|
||||
const baseDuration = route.time / 1000;
|
||||
const trafficAwareDuration = baseDuration * trafficFactor;
|
||||
const alternatives = paths.slice(1).map(alt => ({
|
||||
distance: alt.distance,
|
||||
duration: Math.round(alt.time / 1000),
|
||||
points: alt.points,
|
||||
bbox: alt.bbox
|
||||
}));
|
||||
return {
|
||||
distance: route.distance,
|
||||
duration: route.time / 1000,
|
||||
duration: Math.round(baseDuration),
|
||||
trafficAwareDuration: Math.round(trafficAwareDuration),
|
||||
trafficFactor: Math.round(trafficFactor * 100) / 100,
|
||||
startName,
|
||||
endName,
|
||||
points: route.points,
|
||||
bbox: route.bbox,
|
||||
congestionAware: customModel.speed.length > 0,
|
||||
alternatives: alternatives
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Routing Error:', error.message);
|
||||
if (error.response) {
|
||||
console.error('GraphHopper Response:', error.response.data);
|
||||
}
|
||||
throw new common_1.HttpException('Error calculating route', common_1.HttpStatus.BAD_GATEWAY);
|
||||
const msg = error.response ? `GH Error: ${JSON.stringify(error.response.data)}` : `DNS/Connection Error: ${error.message}`;
|
||||
console.error('CRITICAL ROUTING FAILURE:', msg);
|
||||
throw new common_1.HttpException(`Routing Failure: ${msg}`, common_1.HttpStatus.BAD_GATEWAY);
|
||||
}
|
||||
}
|
||||
decodePolyline(encoded) {
|
||||
const points = [];
|
||||
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]);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
async getMapConfig() {
|
||||
return {
|
||||
center: [31.95, 35.91],
|
||||
@@ -128,7 +157,7 @@ exports.MapsService = MapsService = __decorate([
|
||||
__param(1, (0, typeorm_1.InjectRepository)(road_stat_entity_1.RoadSegmentStat)),
|
||||
__metadata("design:paramtypes", [config_1.ConfigService,
|
||||
typeorm_2.Repository,
|
||||
typeorm_2.DataSource,
|
||||
redis_service_1.RedisService])
|
||||
traffic_grid_service_1.TrafficGridService,
|
||||
geocoding_service_1.GeocodingService])
|
||||
], MapsService);
|
||||
//# sourceMappingURL=maps.service.js.map
|
||||
Reference in New Issue
Block a user