Spaces:
Sleeping
Sleeping
| import sql from './db'; | |
| // Open-Meteo Elevation API | |
| // https://open-meteo.com/en/docs/elevation-api | |
| const API_URL = 'https://api.open-meteo.com/v1/elevation'; | |
| async function updateElevations() { | |
| try { | |
| console.log('Fetching routes without elevation profile...'); | |
| // Debug: Check total routes | |
| const count = await sql`SELECT count(*) FROM routes`; | |
| console.log(`Total routes in DB: ${count[0].count}`); | |
| // Debug: Check one route | |
| // const sample = await sql`SELECT id, path, elevation_profile FROM routes LIMIT 1`; | |
| // if (sample.length > 0) { | |
| // console.log('Sample route:', JSON.stringify(sample[0], null, 2)); | |
| // console.log('Type of elevation_profile:', typeof sample[0].elevation_profile); | |
| // console.log('Value of elevation_profile:', sample[0].elevation_profile); | |
| // } | |
| // Fetch a batch of routes to update | |
| // We order by ID to be deterministic | |
| const routes = await sql` | |
| SELECT id, name, path | |
| FROM routes | |
| ORDER BY id ASC | |
| LIMIT 50 | |
| `; | |
| console.log(`Found ${routes.length} routes to update.`); | |
| for (const route of routes) { | |
| console.log(`Processing route: ${route.name} (ID: ${route.id})`); | |
| try { | |
| let geojson = route.path; | |
| if (typeof geojson === 'string') { | |
| geojson = JSON.parse(geojson); | |
| } | |
| const coordinates = geojson.coordinates; // Array of [lon, lat] | |
| if (!coordinates || coordinates.length === 0) { | |
| console.log('No coordinates found, skipping.'); | |
| continue; | |
| } | |
| // Open-Meteo accepts max 100 locations per request in free tier usually, | |
| // but let's check documentation. It says "Multiple locations can be specified". | |
| // To be safe and avoid URL length limits, we might need to chunk if the path is very long. | |
| // For now, let's sample or chunk. | |
| // Let's limit to 100 points for the profile to keep it simple and fast | |
| // If more than 100 points, we sample. | |
| const sampledCoordinates = sampleCoordinates(coordinates, 100); | |
| const lats = sampledCoordinates.map(c => c[1]); | |
| const lngs = sampledCoordinates.map(c => c[0]); | |
| const url = `${API_URL}?latitude=${lats.join(',')}&longitude=${lngs.join(',')}`; | |
| const response = await fetch(url); | |
| if (!response.ok) { | |
| throw new Error(`API Error: ${response.statusText} (${response.status})`); | |
| } | |
| const data = await response.json(); | |
| const elevations = data.elevation; | |
| if (!elevations || elevations.length !== sampledCoordinates.length) { | |
| console.warn('Mismatch in elevation data length'); | |
| continue; | |
| } | |
| // Smooth elevations to avoid noisy data causing huge elevation gains | |
| const smoothedElevations = smoothElevations(elevations, 5); | |
| // Create elevation profile | |
| // We can calculate distance along the path to create a proper profile { distance, elevation } | |
| const profile = []; | |
| let totalDistance = 0; | |
| for (let i = 0; i < sampledCoordinates.length; i++) { | |
| if (i > 0) { | |
| const prev = sampledCoordinates[i - 1]; | |
| const curr = sampledCoordinates[i]; | |
| const distKm = getDistanceFromLatLonInKm(prev[1], prev[0], curr[1], curr[0]); | |
| totalDistance += distKm * 1000; // Convert to meters | |
| } | |
| profile.push({ | |
| distance: Math.round(totalDistance * 10) / 10, // in meters, rounded | |
| elevation: smoothedElevations[i] // in meters | |
| }); | |
| } | |
| // Update the route in the database | |
| // We are NOT updating the main 'path' geometry with 3D coordinates here to preserve the original detailed geometry | |
| // We are just saving the profile. | |
| // postgres.js handles JSON serialization automatically if we pass the object | |
| const result = await sql` | |
| UPDATE routes | |
| SET elevation_profile = ${sql.json(profile)} | |
| WHERE id = ${route.id} | |
| `; | |
| console.log(`Updated elevation profile for route ${route.id}. Points: ${profile.length}. Result: ${result.count}`); | |
| // Log the full profile for verification | |
| console.log(`Elevation Profile Data for Route ${route.id}:`); | |
| console.log(JSON.stringify(profile, null, 2)); | |
| // Respect API rate limits | |
| // Open-Meteo free tier: < 600 calls/minute (10/sec). | |
| // We'll be conservative with 2 second delay. | |
| await new Promise(resolve => setTimeout(resolve, 2000)); | |
| } catch (err: any) { | |
| if (err.message && err.message.includes('429')) { | |
| console.warn('Rate limit hit, waiting 10 seconds...'); | |
| await new Promise(resolve => setTimeout(resolve, 10000)); | |
| } else { | |
| console.error(`Error updating route ${route.id}:`, err); | |
| } | |
| } | |
| } | |
| console.log('Elevation update completed.'); | |
| } catch (err) { | |
| console.error('Error in updateElevations:', err); | |
| } | |
| } | |
| function sampleCoordinates(coordinates: number[][], maxPoints: number): number[][] { | |
| if (coordinates.length <= maxPoints) return coordinates; | |
| const result = []; | |
| const step = (coordinates.length - 1) / (maxPoints - 1); | |
| for (let i = 0; i < maxPoints; i++) { | |
| const index = Math.round(i * step); | |
| result.push(coordinates[index]); | |
| } | |
| return result; | |
| } | |
| // Haversine formula for distance | |
| function getDistanceFromLatLonInKm(lat1: number, lon1: number, lat2: number, lon2: number) { | |
| const R = 6371; // Radius of the earth in km | |
| const dLat = deg2rad(lat2 - lat1); | |
| const dLon = deg2rad(lon2 - lon1); | |
| const a = | |
| Math.sin(dLat / 2) * Math.sin(dLat / 2) + | |
| Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * | |
| Math.sin(dLon / 2) * Math.sin(dLon / 2); | |
| const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | |
| const d = R * c; // Distance in km | |
| return d; | |
| } | |
| function deg2rad(deg: number) { | |
| return deg * (Math.PI / 180); | |
| } | |
| function smoothElevations(elevations: number[], windowSize: number = 3): number[] { | |
| const smoothed = []; | |
| for (let i = 0; i < elevations.length; i++) { | |
| let sum = 0; | |
| let count = 0; | |
| for (let j = Math.max(0, i - Math.floor(windowSize / 2)); j <= Math.min(elevations.length - 1, i + Math.floor(windowSize / 2)); j++) { | |
| sum += elevations[j]; | |
| count++; | |
| } | |
| smoothed.push(sum / count); | |
| } | |
| return smoothed; | |
| } | |
| updateElevations(); | |