Spaces:
Configuration error
Configuration error
| 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<string> { | |
| 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<string, any[]> = { | |
| '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<string, [number, number]> = { | |
| '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(); | |