import express from 'express'; import cors from 'cors'; import { createServer as createViteServer } from 'vite'; import axios from 'axios'; import dotenv from 'dotenv'; import path from 'path'; dotenv.config(); const GEMINI_API_KEY = 'AIzaSyDnT-o1Lxw_NcEFA5f2yxI5qnrjEPWzHRQ'; async function generateGeminiResponse(prompt: string, context: string): Promise { try { const url = `https://generativelanguage.googleapis.com/v1/models/gemini-2.0-flash:generateContent?key=${GEMINI_API_KEY}`; const response = await axios.post( url, { contents: [{ parts: [{ text: `You are a helpful SafeRoute assistant. Keep responses short and friendly. ${context}\n\nUser: ${prompt}\n\nResponse:` }] }], generationConfig: { temperature: 0.7, maxOutputTokens: 300, } }, { headers: { 'Content-Type': 'application/json', }, timeout: 15000 } ); if (response.data.candidates?.[0]?.content?.parts?.[0]?.text) { return response.data.candidates[0].content.parts[0].text; } if (response.data.promptFeedback?.blockReason) { console.error('Content blocked:', response.data.promptFeedback.blockReason); } return ''; } catch (error: any) { console.error('Gemini API Error:', error.response?.data || error.message); return ''; } } const FALLBACK_GEOCODE_DATA: Record = { 'bhopal': [ { place_name: 'Bhopal, Madhya Pradesh, India', center: [77.4126, 23.2599] }, { place_name: 'Bhopal Junction Railway Station', center: [77.4349, 23.2326] }, ], 'delhi': [ { place_name: 'New Delhi, Delhi, India', center: [77.2090, 28.6139] }, { place_name: 'Connaught Place, New Delhi', center: [77.2167, 28.6315] }, ], 'mumbai': [ { place_name: 'Mumbai, Maharashtra, India', center: [72.8777, 19.0760] }, { place_name: 'Bandra, Mumbai', center: [72.8403, 19.0596] }, ], 'bangalore': [ { place_name: 'Bangalore, Karnataka, India', center: [77.5946, 12.9716] }, { place_name: 'MG Road, Bangalore', center: [77.6088, 12.9753] }, ], }; function geocodeFallback(query: string) { const lower = query.toLowerCase(); const results: any[] = []; const cities: Record = { 'new delhi': [77.2090, 28.6139], 'delhi': [77.2090, 28.6139], 'mumbai': [72.8777, 19.0760], 'bombay': [72.8777, 19.0760], 'bangalore': [77.5946, 12.9716], 'bengaluru': [77.5946, 12.9716], 'chennai': [80.2707, 13.0827], 'kolkata': [88.3639, 22.5726], 'calcutta': [88.3639, 22.5726], 'hyderabad': [78.4867, 17.3850], 'pune': [73.8567, 18.5204], 'ahmedabad': [72.5714, 23.0225], 'jaipur': [75.7873, 26.9124], 'lucknow': [80.9462, 26.8467], 'indore': [75.7873, 22.7196], 'bhopal': [77.4126, 23.2599], 'patna': [85.3131, 25.5941], 'kochi': [76.3061, 9.9312], 'goa': [74.1240, 15.2993], 'chandigarh': [76.7794, 30.7333], 'raipur': [81.6290, 21.2514], 'jabalpur': [79.9550, 23.1815], 'nagpur': [79.0882, 21.1458], 'kanpur': [80.3199, 26.4499], 'vadodara': [73.1812, 22.3106], 'ranchi': [85.3096, 23.3441], 'dehradun': [78.0438, 30.3165], 'rajasthan': [75.7873, 26.9124], 'madhya pradesh': [77.4126, 23.2599], 'maharashtra': [72.8777, 19.0760], 'karnataka': [77.5946, 12.9716], 'tamil nadu': [80.2707, 13.0827], 'gujarat': [72.5714, 23.0225], 'india': [78.9629, 20.5937], }; for (const [city, coords] of Object.entries(cities)) { if (city.includes(lower) || lower.includes(city) || lower.includes(city.split(' ')[0])) { results.push({ place_id: results.length + 1, display_name: `${city.charAt(0).toUpperCase() + city.slice(1)}, India`, lon: coords[0], lat: coords[1] }); if (results.length >= 5) break; } } return results; } async function startServer() { const app = express(); const PORT = 3000; app.use(cors()); app.use(express.json({ limit: '50mb' })); app.use(express.urlencoded({ limit: '50mb', extended: true })); // --- API Endpoints --- // 0. Geocoding API (with POI support) app.get('/api/geocode', async (req, res) => { try { const { q, lat, lon } = req.query; if (!q || typeof q !== 'string') { return res.json([]); } // POI keywords that need location context const poiKeywords = ['atm', 'hospital', 'petrol', 'parking', 'police', 'restaurant', 'hotel', 'school', 'bank']; const isPOI = poiKeywords.some(kw => q.toLowerCase().includes(kw)); // Try Nominatim first with location context for POIs try { let params: any = { q, format: 'json', limit: 8, addressdetails: 1 }; if (isPOI && lat && lon) { // Add location context for POI searches params.viewbox = `${Number(lon) - 0.5},${Number(lat) - 0.5},${Number(lon) + 0.5},${Number(lat) + 0.5}`; params.bounded = 1; } const response = await axios.get('https://nominatim.openstreetmap.org/search', { params, headers: { 'User-Agent': 'SafeRoute-Server/1.0' }, timeout: 8000 }); if (response.data && response.data.length > 0) { return res.json(response.data); } } catch (nomError) { console.error('Nominatim error:', nomError); } // Fallback to local database const fallbackResults = geocodeFallback(q); return res.json(fallbackResults); } catch (error: any) { console.error('Geocode Error:', error.message); const fallbackResults = geocodeFallback(String(req.query.q || '')); res.json(fallbackResults); } }); // 1. Weather API app.get('/api/weather', async (req, res) => { try { const { lat, lon } = req.query; const apiKey = process.env.OPENWEATHER_API_KEY; if (!apiKey) { return res.json({ weather: [{ main: 'Clear' }], main: { temp: 298 } }); } const response = await axios.get(`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}`); res.json(response.data); } catch (error: any) { console.error('Weather API Error:', error.response?.data || error.message); res.json({ weather: [{ main: 'Clear' }], main: { temp: 298 } }); } }); // 2. Predict API app.post('/api/predict', (req, res) => { const { weather_condition, time_of_day, traffic_level } = req.body; let risk = 0.15; // Weather factor if (weather_condition === 'Rain' || weather_condition === 'Drizzle') risk += 0.15; else if (weather_condition === 'Snow') risk += 0.25; else if (weather_condition === 'Thunderstorm') risk += 0.3; else if (weather_condition === 'Fog' || weather_condition === 'Mist') risk += 0.2; // Time of day factor if (time_of_day >= 22 || time_of_day <= 4) risk += 0.1; else if ((time_of_day >= 7 && time_of_day <= 9) || (time_of_day >= 16 && time_of_day <= 18)) risk += 0.08; // Traffic level factor risk += traffic_level * 0.15; risk = Math.min(Math.max(risk, 0.1), 0.95); res.json({ risk_probability: risk }); }); // 3. AI Chat API app.post('/api/ai-chat', async (req, res) => { try { const { message, context } = req.body; const response = await generateGeminiResponse(message, context || ''); if (response) { res.json({ response }); } else { res.json({ response: null, fallback: true }); } } catch (error: any) { console.error('AI Chat Error:', error.response?.data || error.message); res.json({ response: null, fallback: true }); } }); // 4. Route Analysis API app.post('/api/route-analysis', (req, res) => { const { coordinates, weather_condition, time_of_day, vehicle_type } = req.body; function pseudoRandom(seed: number) { let x = Math.sin(seed) * 10000; return x - Math.floor(x); } const segments = []; for (let i = 0; i < coordinates.length - 1; i++) { const seed = coordinates[i][0] * 1000 + coordinates[i][1]; const baseTraffic = pseudoRandom(seed); const traffic_level = Math.max(0, Math.min(1, baseTraffic + (pseudoRandom(seed + 1) * 0.1 - 0.05))); let risk = 0.2; let explanation: string[] = []; let routeType = 'street'; let riskLevel = 'low'; // Determine route type based on traffic patterns const highwayChance = pseudoRandom(seed + 5); const smallRoadChance = pseudoRandom(seed + 6); if (traffic_level > 0.75 && highwayChance > 0.6) { routeType = 'highway'; risk += 0.2; riskLevel = 'high'; explanation.push('Highway route'); } else if (traffic_level < 0.35 && smallRoadChance > 0.5) { routeType = 'small_road'; risk += 0.1; riskLevel = 'moderate'; explanation.push('Local/small road'); } else { routeType = 'street'; riskLevel = 'low'; explanation.push('City streets'); } // Weather impact if (weather_condition === 'Rain' || weather_condition === 'Drizzle') { risk += 0.15; explanation.push('Rain'); if (routeType === 'highway') risk += 0.1; } else if (weather_condition === 'Snow') { risk += 0.3; explanation.push('Snow'); if (routeType === 'highway') risk += 0.15; } else if (weather_condition === 'Thunderstorm') { risk += 0.35; explanation.push('Thunderstorm'); } else if (weather_condition === 'Fog' || weather_condition === 'Mist') { risk += 0.2; explanation.push('Fog'); } // Time of day if (time_of_day >= 22 || time_of_day <= 4) { risk += 0.1; explanation.push('Night'); } else if ((time_of_day >= 7 && time_of_day <= 9) || (time_of_day >= 16 && time_of_day <= 18)) { risk += 0.05; explanation.push('Rush hour'); } // Vehicle specific if (vehicle_type === 'cycling') { risk += 0.1; explanation.push('Cycling'); } // Traffic impact risk += traffic_level * 0.1; if (traffic_level > 0.8) { explanation.push('Heavy traffic'); if (routeType === 'highway') risk += 0.1; } // Accident hotspots const accidentChance = pseudoRandom(seed + 2); if (accidentChance > 0.9) { if (routeType === 'highway') { risk += 0.25; riskLevel = 'high'; explanation.push('High accident zone'); } else if (routeType === 'small_road') { risk += 0.12; riskLevel = 'moderate'; explanation.push('Accident history'); } else { risk += 0.08; } } // Update risk level based on final risk if (risk >= 0.6) riskLevel = 'high'; else if (risk >= 0.35) riskLevel = 'moderate'; else riskLevel = 'low'; risk = Math.min(Math.max(risk, 0.1), 0.95); segments.push({ start: coordinates[i], end: coordinates[i+1], risk_probability: risk, traffic_level, explanation: explanation.slice(0, 3).join(', '), routeType, riskLevel }); } // Calculate overall route risk const avgRisk = segments.reduce((sum, seg) => sum + seg.risk_probability, 0) / segments.length; // Determine overall risk level for the route const highwayCount = segments.filter(s => s.routeType === 'highway').length; const smallRoadCount = segments.filter(s => s.routeType === 'small_road').length; const highRiskCount = segments.filter(s => s.riskLevel === 'high').length; let overallRiskLevel = 'low'; let overallRiskMessage = 'This route is safe and suitable for travel.'; if (avgRisk >= 0.6 || highRiskCount > segments.length * 0.3) { overallRiskLevel = 'high'; overallRiskMessage = 'This route has high risk. Proceed with caution.'; } else if (avgRisk >= 0.35 || smallRoadCount > segments.length * 0.4) { overallRiskLevel = 'moderate'; overallRiskMessage = 'This route has moderate risk. Stay alert while traveling.'; } // Generate incidents based on route characteristics const incidents = []; const routeSeed = Math.abs(coordinates[0][0] + coordinates[coordinates.length - 1][1]); const incidentChance = pseudoRandom(routeSeed); if (incidentChance > 0.85 && coordinates.length > 10) { const numIncidents = overallRiskLevel === 'high' ? 3 : (overallRiskLevel === 'moderate' ? 2 : 1); for(let i = 0; i < numIncidents; i++) { const idx = Math.floor(pseudoRandom(routeSeed + i + 1) * (coordinates.length - 1)); const isAccident = pseudoRandom(routeSeed + i + 2) > 0.6; incidents.push({ location: coordinates[idx], type: isAccident ? 'Accident' : 'Roadwork', severity: overallRiskLevel === 'high' ? 'High' : (overallRiskLevel === 'moderate' ? 'Medium' : 'Low') }); } } res.json({ segments, incidents, overallRisk: avgRisk, riskLevel: overallRiskLevel, riskMessage: overallRiskMessage, routeStats: { highwaySegments: highwayCount, smallRoadSegments: smallRoadCount, totalSegments: segments.length } }); }); // Vite middleware if (process.env.NODE_ENV !== 'production') { const vite = await createViteServer({ server: { middlewareMode: true }, appType: 'spa', }); app.use(vite.middlewares); } else { const distPath = path.join(process.cwd(), 'dist'); app.use(express.static(distPath)); app.get('*', (req, res) => { res.sendFile(path.join(distPath, 'index.html')); }); } app.listen(PORT, '0.0.0.0', () => { console.log(`Server running on http://localhost:${PORT}`); }); } startServer();