Files

263 lines
12 KiB
JavaScript

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a, _b, _c;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MapsService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
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");
const redis_service_1 = require("../common/redis.service");
let MapsService = class MapsService {
configService;
roadStatRepo;
trafficGrid;
geocodingService;
dataSource;
redisService;
graphHopperUrl;
constructor(configService, roadStatRepo, trafficGrid, geocodingService, dataSource, redisService) {
this.configService = configService;
this.roadStatRepo = roadStatRepo;
this.trafficGrid = trafficGrid;
this.geocodingService = geocodingService;
this.dataSource = dataSource;
this.redisService = redisService;
this.graphHopperUrl = this.configService.get('GRAPH_HOPPER_URL', 'http://routing:8080');
}
async requestRoutingSync() {
await this.redisService.set('routing_sync_requested', '1');
return { success: true, message: 'Routing sync requested and scheduled for the next minute.' };
}
async getRoute(waypoints, profile = 'car', steps = false, locale = 'en', alternatives = false) {
if (waypoints.length < 2) {
throw new common_1.HttpException('At least two waypoints are required', common_1.HttpStatus.BAD_REQUEST);
}
try {
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: locale === 'en' ? 'ar' : locale,
calc_points: true,
points_encoded: true,
instructions: steps || true,
};
let closureCount = 0;
try {
const closedSegments = await this.dataSource.query(`
SELECT ST_AsGeoJSON(
ST_Transform(ST_Buffer(ST_Transform(geometry::geometry, 3857), 15), 4326), 6
) AS geojson
FROM road_segment_stats
WHERE "isClosed" = true AND geometry IS NOT NULL
LIMIT 50
`);
const features = closedSegments
.map((s, i) => {
try {
return { type: 'Feature', id: `closed_${i}`, geometry: JSON.parse(s.geojson), properties: {} };
}
catch {
return null;
}
})
.filter(Boolean);
if (features.length > 0) {
payload['custom_model'] = {
priority: features.map((f) => ({ if: `in_${f.id}`, multiply_by: '0' })),
areas: { type: 'FeatureCollection', features },
};
payload['ch.disable'] = true;
closureCount = features.length;
console.log(`🚧 Routing: avoiding ${closureCount} closed segment(s).`);
}
}
catch (closureError) {
console.warn('⚠️ Could not load road closures for routing:', closureError.message);
}
if (alternatives && 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;
}
console.log(`Routing Request: ${waypoints.length} points via ${profile} on ${this.graphHopperUrl} | Steps: ${steps} | Locale: ${locale}`);
let response;
try {
response = await axios_1.default.post(`${this.graphHopperUrl}/route`, payload, { timeout: 10000 });
}
catch (routeErr) {
if (closureCount > 0 && payload['custom_model']) {
const detail = routeErr.response ? JSON.stringify(routeErr.response.data) : routeErr.message;
console.warn(`⚠️ Closure-aware routing failed (${detail}). Retrying without closures...`);
delete payload['custom_model'];
if (!(alternatives && waypoints.length === 2))
delete payload['ch.disable'];
response = await axios_1.default.post(`${this.graphHopperUrl}/route`, payload, { timeout: 10000 });
}
else {
throw routeErr;
}
}
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 processedPaths = paths.map((p, index) => {
const pCoords = this.decodePolyline(p.points);
const pTrafficFactor = this.trafficGrid.getTrafficFactor(pCoords, hr, dow);
const pDuration = Math.round((p.time / 1000) * pTrafficFactor);
const routeName = this.getRouteName(p.instructions);
const tags = [];
if (index === 0)
tags.push('FASTEST');
if (paths.length > 1) {
const isShortest = paths.every((other) => p.distance <= other.distance);
if (isShortest)
tags.push('SHORTEST');
if (index > 0 && !isShortest)
tags.push('ALTERNATIVE');
}
return {
routeName: routeName ? `عبر ${routeName}` : `المسار ${index + 1}`,
tags,
distance: p.distance,
duration: pDuration,
points: p.points,
bbox: p.bbox,
instructions: steps ? p.instructions : undefined
};
});
const mainRoute = processedPaths[0];
const altRoutes = processedPaths.slice(1);
return {
routeName: mainRoute.routeName,
tags: mainRoute.tags,
distance: mainRoute.distance,
duration: mainRoute.duration,
trafficFactor: Math.round(trafficFactor * 100) / 100,
startName,
endName,
points: mainRoute.points,
bbox: mainRoute.bbox,
instructions: mainRoute.instructions,
alternatives: altRoutes
};
}
catch (error) {
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);
}
}
getRouteName(instructions) {
if (!instructions || instructions.length === 0)
return null;
const streetDistances = {};
for (const inst of instructions) {
if (inst.street_name && inst.street_name.trim() !== '') {
streetDistances[inst.street_name] = (streetDistances[inst.street_name] || 0) + (inst.distance || 0);
}
}
let longestStreet = null;
let maxDist = 0;
for (const [street, dist] of Object.entries(streetDistances)) {
if (dist > maxDist) {
maxDist = dist;
longestStreet = street;
}
}
return longestStreet;
}
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],
zoom: 12,
tileServerUrl: this.configService.get('TILE_SERVER_URL', 'http://localhost:3001'),
};
}
};
exports.MapsService = MapsService;
exports.MapsService = MapsService = __decorate([
(0, common_1.Injectable)(),
__param(1, (0, typeorm_1.InjectRepository)(road_stat_entity_1.RoadSegmentStat)),
__metadata("design:paramtypes", [typeof (_a = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _a : Object, typeof (_b = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _b : Object, traffic_grid_service_1.TrafficGridService,
geocoding_service_1.GeocodingService, typeof (_c = typeof typeorm_2.DataSource !== "undefined" && typeorm_2.DataSource) === "function" ? _c : Object, redis_service_1.RedisService])
], MapsService);
//# sourceMappingURL=maps.service.js.map