"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 RoutingService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.RoutingService = void 0; const common_1 = require("@nestjs/common"); const config_1 = require("@nestjs/config"); const axios_1 = require("axios"); let RoutingService = RoutingService_1 = class RoutingService { constructor(configService) { this.configService = configService; this.logger = new common_1.Logger(RoutingService_1.name); this.valhallaUrl = this.configService.get('VALHALLA_URL') || 'http://valhalla:8002'; this.valhallaClient = axios_1.default.create({ baseURL: this.valhallaUrl, timeout: 30000, headers: { 'Content-Type': 'application/json', }, }); } async calculateRoute(dto) { try { this.logger.debug(`Calculating route from ${dto.originLat},${dto.originLng} to ${dto.destinationLat},${dto.destinationLng}`); const requestId = this.generateRequestId(); const valhallaRequest = this.buildValhallaRequest(dto); const response = await this.valhallaClient.post('/route', valhallaRequest); if (!response.data || !response.data.trip) { throw new Error('Invalid response from Valhalla routing engine'); } const routes = this.parseValhallaResponse(response.data); return { routes, origin: { lat: dto.originLat, lng: dto.originLng }, destination: { lat: dto.destinationLat, lng: dto.destinationLng }, requestId, }; } catch (error) { this.logger.error('Failed to calculate route', error); if (error.response?.status === 400) { throw new common_1.HttpException('Invalid route request parameters', common_1.HttpStatus.BAD_REQUEST); } if (error.response?.status === 404) { throw new common_1.HttpException('No route found between the specified locations', common_1.HttpStatus.NOT_FOUND); } throw new common_1.HttpException('Route calculation service unavailable', common_1.HttpStatus.SERVICE_UNAVAILABLE); } } buildValhallaRequest(dto) { const locations = [ { lat: dto.originLat, lon: dto.originLng }, { lat: dto.destinationLat, lon: dto.destinationLng }, ]; const costingOptions = this.getCostingOptions(dto); return { locations, costing: dto.costing, costing_options: costingOptions, directions_options: { units: 'kilometers', language: 'en-US', narrative: true, alternates: dto.alternatives || 1, }, format: 'json', shape_match: 'edge_walk', encoded_polyline: true, }; } getCostingOptions(dto) { const options = {}; if (dto.costing === 'auto') { options.auto = { maneuver_penalty: 5, gate_cost: 30, gate_penalty: 300, private_access_penalty: 450, toll_booth_cost: 15, toll_booth_penalty: 0, ferry_cost: 300, use_ferry: dto.avoidTolls ? 0 : 1, use_highways: dto.avoidHighways ? 0 : 1, use_tolls: dto.avoidTolls ? 0 : 1, }; } else if (dto.costing === 'bicycle') { options.bicycle = { maneuver_penalty: 5, gate_penalty: 300, use_roads: 0.5, use_hills: 0.5, use_ferry: 1, avoid_bad_surfaces: 0.25, }; } else if (dto.costing === 'pedestrian') { options.pedestrian = { walking_speed: 5.1, walkway_factor: 1, sidewalk_factor: 1, alley_factor: 2, driveway_factor: 5, step_penalty: 0, use_ferry: 1, use_living_streets: 0.6, }; } return options; } parseValhallaResponse(data) { const trip = data.trip; if (!trip || !trip.legs || trip.legs.length === 0) { return []; } const route = { summary: { distance: Math.round(trip.summary.length * 100) / 100, time: Math.round(trip.summary.time / 60 * 100) / 100, cost: this.estimateFuelCost(trip.summary.length, 'auto'), }, geometry: this.decodePolyline(trip.shape), steps: this.parseManeuvers(trip.legs[0].maneuvers), confidence: 0.95, }; return [route]; } parseManeuvers(maneuvers) { return maneuvers.map(maneuver => ({ instruction: maneuver.instruction, distance: Math.round(maneuver.length * 1000), time: maneuver.time, type: maneuver.type?.toString() || 'unknown', geometry: [], })); } decodePolyline(encoded) { const points = []; let index = 0; let lat = 0; let lng = 0; while (index < encoded.length) { let result = 1; let shift = 0; let b; do { b = encoded.charCodeAt(index++) - 63 - 1; result += b << shift; shift += 5; } while (b >= 0x1f); lat += (result & 1) !== 0 ? ~(result >> 1) : (result >> 1); result = 1; shift = 0; do { b = encoded.charCodeAt(index++) - 63 - 1; result += b << shift; shift += 5; } while (b >= 0x1f); lng += (result & 1) !== 0 ? ~(result >> 1) : (result >> 1); points.push({ lat: lat / 1e5, lng: lng / 1e5, }); } return points; } estimateFuelCost(distanceKm, costing) { if (costing !== 'auto') return 0; const fuelEfficiency = 10; const fuelPricePerLiter = 1.5; return Math.round((distanceKm / fuelEfficiency) * fuelPricePerLiter * 100) / 100; } generateRequestId() { return `route_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } async getServiceStatus() { try { const response = await this.valhallaClient.get('/status'); return { status: 'healthy', version: response.data?.version, }; } catch (error) { this.logger.error('Valhalla service health check failed', error); return { status: 'unhealthy', }; } } }; exports.RoutingService = RoutingService; exports.RoutingService = RoutingService = RoutingService_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [config_1.ConfigService]) ], RoutingService); //# sourceMappingURL=routing.service.js.map