"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