357 lines
14 KiB
JavaScript
357 lines
14 KiB
JavaScript
/**
|
|
* Script one-shot : ajoute latitude/longitude aux structures depuis Nominatim
|
|
* Usage : node scripts/geocode-structures.js
|
|
* IMPORTANT : respecter le rate limit Nominatim (1 req/sec max)
|
|
*
|
|
* Stratégie à 3 niveaux :
|
|
* 1. Lookup statique (villes les plus fréquentes - instantané)
|
|
* 2. Nominatim API (geocodage réel - 1 req/sec)
|
|
* 3. Fallback pays (centroïde national si ville inconnue)
|
|
*/
|
|
|
|
import * as fs from 'fs'
|
|
import * as path from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = path.dirname(__filename)
|
|
|
|
const DATA_PATH = path.join(__dirname, '..', 'public', 'data', 'reseaux-bifurcation.json')
|
|
const data = JSON.parse(fs.readFileSync(DATA_PATH, 'utf-8'))
|
|
|
|
// Lookup table statique pour villes connues (évite appels API pour les fréquentes)
|
|
const CITY_COORDS = {
|
|
'Paris': { lat: 48.8566, lng: 2.3522 },
|
|
'Bruxelles': { lat: 50.8503, lng: 4.3517 },
|
|
'Brussels': { lat: 50.8503, lng: 4.3517 },
|
|
'Lyon': { lat: 45.7640, lng: 4.8357 },
|
|
'Marseille': { lat: 43.2965, lng: 5.3698 },
|
|
'Toulouse': { lat: 43.6047, lng: 1.4442 },
|
|
'Bordeaux': { lat: 44.8378, lng: -0.5792 },
|
|
'Nantes': { lat: 47.2184, lng: -1.5536 },
|
|
'Strasbourg': { lat: 48.5734, lng: 7.7521 },
|
|
'Lille': { lat: 50.6292, lng: 3.0573 },
|
|
'Grenoble': { lat: 45.1885, lng: 5.7245 },
|
|
'Montpellier': { lat: 43.6108, lng: 3.8767 },
|
|
'Rennes': { lat: 48.1173, lng: -1.6778 },
|
|
'Berlin': { lat: 52.5200, lng: 13.4050 },
|
|
'Amsterdam': { lat: 52.3676, lng: 4.9041 },
|
|
'London': { lat: 51.5074, lng: -0.1278 },
|
|
'Madrid': { lat: 40.4168, lng: -3.7038 },
|
|
'Barcelona': { lat: 41.3851, lng: 2.1734 },
|
|
'Rome': { lat: 41.9028, lng: 12.4964 },
|
|
'Vienna': { lat: 48.2082, lng: 16.3738 },
|
|
'Warsaw': { lat: 52.2297, lng: 21.0122 },
|
|
'Copenhagen': { lat: 55.6761, lng: 12.5683 },
|
|
'Zurich': { lat: 47.3769, lng: 8.5417 },
|
|
'Helsinki': { lat: 60.1699, lng: 24.9384 },
|
|
'Porto': { lat: 41.1579, lng: -8.6291 },
|
|
'Lisbonne': { lat: 38.7169, lng: -9.1399 },
|
|
'Lisbon': { lat: 38.7169, lng: -9.1399 },
|
|
'Gent': { lat: 51.0543, lng: 3.7174 },
|
|
'Ghent': { lat: 51.0543, lng: 3.7174 },
|
|
'Liège': { lat: 50.6326, lng: 5.5797 },
|
|
'Krakow': { lat: 50.0647, lng: 19.9450 },
|
|
'Wroclaw': { lat: 51.1079, lng: 17.0385 },
|
|
'Lausanne': { lat: 46.5197, lng: 6.6323 },
|
|
'Genève': { lat: 46.2044, lng: 6.1432 },
|
|
'Geneva': { lat: 46.2044, lng: 6.1432 },
|
|
'Namur': { lat: 50.4669, lng: 4.8674 },
|
|
'Tallinn': { lat: 59.4370, lng: 24.7536 },
|
|
'Brest': { lat: 48.3904, lng: -4.4861 },
|
|
'Tours': { lat: 47.3941, lng: 0.6848 },
|
|
'Caen': { lat: 49.1829, lng: -0.3707 },
|
|
'Montauban': { lat: 44.0174, lng: 1.3518 },
|
|
'Bayonne': { lat: 43.4929, lng: -1.4748 },
|
|
'Pau': { lat: 43.2951, lng: -0.3708 },
|
|
'Aurillac': { lat: 44.9282, lng: 2.4480 },
|
|
'Aubenas': { lat: 44.6208, lng: 4.3908 },
|
|
'Hyères': { lat: 43.1200, lng: 6.1283 },
|
|
'Aubagne': { lat: 43.2940, lng: 5.5706 },
|
|
'Auch': { lat: 43.6462, lng: 0.5851 },
|
|
'Clermont-Ferrand': { lat: 45.7797, lng: 3.0863 },
|
|
'Dijon': { lat: 47.3220, lng: 5.0415 },
|
|
'Nice': { lat: 43.7102, lng: 7.2620 },
|
|
'Metz': { lat: 49.1193, lng: 6.1757 },
|
|
'Nancy': { lat: 48.6921, lng: 6.1844 },
|
|
'Rouen': { lat: 49.4432, lng: 1.0993 },
|
|
'Angers': { lat: 47.4784, lng: -0.5632 },
|
|
'Reims': { lat: 49.2583, lng: 4.0317 },
|
|
'Le Mans': { lat: 47.9960, lng: 0.1966 },
|
|
'Amiens': { lat: 49.8941, lng: 2.2958 },
|
|
'Perpignan': { lat: 42.6886, lng: 2.8948 },
|
|
'Orléans': { lat: 47.9029, lng: 1.9039 },
|
|
'Limoges': { lat: 45.8315, lng: 1.2578 },
|
|
'Mulhouse': { lat: 47.7508, lng: 7.3359 },
|
|
'Besançon': { lat: 47.2378, lng: 6.0241 },
|
|
'Poitiers': { lat: 46.5802, lng: 0.3404 },
|
|
'Villeurbanne': { lat: 45.7676, lng: 4.8800 },
|
|
'Aix-en-Provence': { lat: 43.5297, lng: 5.4474 },
|
|
'Boulogne-Billancourt': { lat: 48.8353, lng: 2.2400 },
|
|
'Nîmes': { lat: 43.8367, lng: 4.3601 },
|
|
'Argenteuil': { lat: 48.9478, lng: 2.2476 },
|
|
'Montreuil': { lat: 48.8638, lng: 2.4440 },
|
|
'Roubaix': { lat: 50.6942, lng: 3.1746 },
|
|
'Tourcoing': { lat: 50.7238, lng: 3.1612 },
|
|
'Dunkerque': { lat: 51.0343, lng: 2.3752 },
|
|
'Calais': { lat: 50.9513, lng: 1.8587 },
|
|
'Valenciennes': { lat: 50.3573, lng: 3.5239 },
|
|
'Chartres': { lat: 48.4469, lng: 1.4877 },
|
|
'Colmar': { lat: 48.0778, lng: 7.3585 },
|
|
'Lorient': { lat: 47.7482, lng: -3.3714 },
|
|
'Quimper': { lat: 47.9983, lng: -4.0975 },
|
|
'Vannes': { lat: 47.6559, lng: -2.7602 },
|
|
'Saint-Nazaire': { lat: 47.2736, lng: -2.2137 },
|
|
'La Rochelle': { lat: 46.1603, lng: -1.1511 },
|
|
'Angoulême': { lat: 45.6500, lng: 0.1561 },
|
|
'Périgueux': { lat: 45.1866, lng: 0.7213 },
|
|
'Tulle': { lat: 45.2672, lng: 1.7726 },
|
|
'Guéret': { lat: 46.1714, lng: 1.8714 },
|
|
'Moulins': { lat: 46.5647, lng: 3.3325 },
|
|
'Brive-la-Gaillarde': { lat: 45.1597, lng: 1.5317 },
|
|
'Saint-Étienne': { lat: 45.4397, lng: 4.3872 },
|
|
'Grenoble': { lat: 45.1885, lng: 5.7245 },
|
|
'Valence': { lat: 44.9334, lng: 4.8924 },
|
|
'Chambéry': { lat: 45.5646, lng: 5.9178 },
|
|
'Annecy': { lat: 45.8992, lng: 6.1294 },
|
|
'Mâcon': { lat: 46.3066, lng: 4.8281 },
|
|
'Chalon-sur-Saône': { lat: 46.7806, lng: 4.8534 },
|
|
'Auxerre': { lat: 47.7977, lng: 3.5740 },
|
|
'Troyes': { lat: 48.2973, lng: 4.0744 },
|
|
'Châlons-en-Champagne': { lat: 48.9571, lng: 4.3665 },
|
|
'Épinal': { lat: 48.1741, lng: 6.4490 },
|
|
'Belfort': { lat: 47.6396, lng: 6.8633 },
|
|
'Montbéliard': { lat: 47.5076, lng: 6.7987 },
|
|
'Vilvorde': { lat: 50.9267, lng: 4.4145 },
|
|
'Liège': { lat: 50.6326, lng: 5.5797 },
|
|
'Louvain-la-Neuve': { lat: 50.6683, lng: 4.6118 },
|
|
'Leuven': { lat: 50.8798, lng: 4.7005 },
|
|
'Antwerp': { lat: 51.2194, lng: 4.4025 },
|
|
'Anvers': { lat: 51.2194, lng: 4.4025 },
|
|
'Rotterdam': { lat: 51.9244, lng: 4.4777 },
|
|
'The Hague': { lat: 52.0705, lng: 4.3007 },
|
|
'Utrecht': { lat: 52.0907, lng: 5.1214 },
|
|
'Eindhoven': { lat: 51.4416, lng: 5.4697 },
|
|
'Tilburg': { lat: 51.5555, lng: 5.0913 },
|
|
'Hamburg': { lat: 53.5753, lng: 10.0153 },
|
|
'Munich': { lat: 48.1351, lng: 11.5820 },
|
|
'Cologne': { lat: 50.9333, lng: 6.9500 },
|
|
'Frankfurt': { lat: 50.1109, lng: 8.6821 },
|
|
'Stuttgart': { lat: 48.7758, lng: 9.1829 },
|
|
'Dusseldorf': { lat: 51.2217, lng: 6.7762 },
|
|
'Dortmund': { lat: 51.5136, lng: 7.4653 },
|
|
'Essen': { lat: 51.4556, lng: 7.0116 },
|
|
'Leipzig': { lat: 51.3397, lng: 12.3731 },
|
|
'Dresden': { lat: 51.0504, lng: 13.7373 },
|
|
'Hanover': { lat: 52.3759, lng: 9.7320 },
|
|
'Nuremberg': { lat: 49.4521, lng: 11.0767 },
|
|
'Freiburg': { lat: 47.9990, lng: 7.8421 },
|
|
'Kassel': { lat: 51.3127, lng: 9.4797 },
|
|
'Mannheim': { lat: 49.4875, lng: 8.4660 },
|
|
'Heidelberg': { lat: 49.3988, lng: 8.6724 },
|
|
'Tübingen': { lat: 48.5217, lng: 9.0576 },
|
|
'Weimar': { lat: 50.9795, lng: 11.3235 },
|
|
'Bristol': { lat: 51.4545, lng: -2.5879 },
|
|
'Manchester': { lat: 53.4808, lng: -2.2426 },
|
|
'Birmingham': { lat: 52.4862, lng: -1.8904 },
|
|
'Edinburgh': { lat: 55.9533, lng: -3.1883 },
|
|
'Glasgow': { lat: 55.8642, lng: -4.2518 },
|
|
'Leeds': { lat: 53.7997, lng: -1.5492 },
|
|
'Malmö': { lat: 55.6050, lng: 13.0038 },
|
|
'Stockholm': { lat: 59.3293, lng: 18.0686 },
|
|
'Göteborg': { lat: 57.7089, lng: 11.9746 },
|
|
'Oslo': { lat: 59.9139, lng: 10.7522 },
|
|
'Bergen': { lat: 60.3929, lng: 5.3241 },
|
|
'Lausanne': { lat: 46.5197, lng: 6.6323 },
|
|
'Basel': { lat: 47.5596, lng: 7.5886 },
|
|
'Bern': { lat: 46.9480, lng: 7.4474 },
|
|
'Milan': { lat: 45.4654, lng: 9.1859 },
|
|
'Turin': { lat: 45.0703, lng: 7.6869 },
|
|
'Florence': { lat: 43.7696, lng: 11.2558 },
|
|
'Naples': { lat: 40.8518, lng: 14.2681 },
|
|
'Bologna': { lat: 44.4949, lng: 11.3426 },
|
|
'Venice': { lat: 45.4408, lng: 12.3155 },
|
|
'Venise': { lat: 45.4408, lng: 12.3155 },
|
|
'Séville': { lat: 37.3891, lng: -5.9845 },
|
|
'Seville': { lat: 37.3891, lng: -5.9845 },
|
|
'Valence': { lat: 39.4699, lng: -0.3763 },
|
|
'Bilbao': { lat: 43.2630, lng: -2.9350 },
|
|
'Casablanca': { lat: 33.5731, lng: -7.5898 },
|
|
'Rabat': { lat: 34.0209, lng: -6.8417 },
|
|
'Dakar': { lat: 14.7167, lng: -17.4677 },
|
|
'Abidjan': { lat: 5.3600, lng: -4.0083 },
|
|
'Nairobi': { lat: -1.2921, lng: 36.8219 },
|
|
'Tunis': { lat: 36.8065, lng: 10.1815 },
|
|
'Alger': { lat: 36.7372, lng: 3.0864 },
|
|
'New York': { lat: 40.7128, lng: -74.0060 },
|
|
'San Francisco': { lat: 37.7749, lng: -122.4194 },
|
|
'Los Angeles': { lat: 34.0522, lng: -118.2437 },
|
|
'Chicago': { lat: 41.8781, lng: -87.6298 },
|
|
'Boston': { lat: 42.3601, lng: -71.0589 },
|
|
'Seattle': { lat: 47.6062, lng: -122.3321 },
|
|
'Montreal': { lat: 45.5017, lng: -73.5673 },
|
|
'Toronto': { lat: 43.6532, lng: -79.3832 },
|
|
'Vancouver': { lat: 49.2827, lng: -123.1207 },
|
|
'Melbourne': { lat: -37.8136, lng: 144.9631 },
|
|
'Sydney': { lat: -33.8688, lng: 151.2093 },
|
|
'Tokyo': { lat: 35.6762, lng: 139.6503 },
|
|
'Seoul': { lat: 37.5665, lng: 126.9780 },
|
|
'Shanghai': { lat: 31.2304, lng: 121.4737 },
|
|
'Beijing': { lat: 39.9042, lng: 116.4074 },
|
|
'Mexico City': { lat: 19.4326, lng: -99.1332 },
|
|
'Buenos Aires': { lat: -34.6118, lng: -58.3960 },
|
|
'São Paulo': { lat: -23.5505, lng: -46.6333 },
|
|
'Rio de Janeiro': { lat: -22.9068, lng: -43.1729 },
|
|
}
|
|
|
|
// Fallback par pays si ville inconnue
|
|
const COUNTRY_FALLBACK = {
|
|
'FR': { lat: 46.6034, lng: 1.8883 },
|
|
'BE': { lat: 50.5039, lng: 4.4699 },
|
|
'NL': { lat: 52.3702, lng: 4.8952 },
|
|
'DE': { lat: 51.1657, lng: 10.4515 },
|
|
'GB': { lat: 55.3781, lng: -3.4360 },
|
|
'UK': { lat: 55.3781, lng: -3.4360 },
|
|
'IT': { lat: 41.8719, lng: 12.5674 },
|
|
'ES': { lat: 40.4637, lng: -3.7492 },
|
|
'CH': { lat: 46.8182, lng: 8.2275 },
|
|
'PL': { lat: 51.9194, lng: 19.1451 },
|
|
'DK': { lat: 56.2639, lng: 9.5018 },
|
|
'AT': { lat: 47.5162, lng: 14.5501 },
|
|
'PT': { lat: 39.3999, lng: -8.2245 },
|
|
'FI': { lat: 61.9241, lng: 25.7482 },
|
|
'SE': { lat: 60.1282, lng: 18.6435 },
|
|
'NO': { lat: 60.4720, lng: 8.4689 },
|
|
'US': { lat: 37.0902, lng: -95.7129 },
|
|
'MA': { lat: 31.7917, lng: -7.0926 },
|
|
'SN': { lat: 14.4974, lng: -14.4524 },
|
|
'MG': { lat: -18.7669, lng: 46.8691 },
|
|
'EE': { lat: 58.5953, lng: 25.0136 },
|
|
'LT': { lat: 55.1694, lng: 23.8813 },
|
|
'LV': { lat: 56.8796, lng: 24.6032 },
|
|
'HU': { lat: 47.1625, lng: 19.5033 },
|
|
'CZ': { lat: 49.8175, lng: 15.4730 },
|
|
'SK': { lat: 48.6690, lng: 19.6990 },
|
|
'RO': { lat: 45.9432, lng: 24.9668 },
|
|
'GR': { lat: 39.0742, lng: 21.8243 },
|
|
'HR': { lat: 45.1000, lng: 15.2000 },
|
|
'SI': { lat: 46.1512, lng: 14.9955 },
|
|
'RS': { lat: 44.0165, lng: 21.0059 },
|
|
'CA': { lat: 56.1304, lng: -106.3468 },
|
|
'AU': { lat: -25.2744, lng: 133.7751 },
|
|
'NZ': { lat: -40.9006, lng: 174.8860 },
|
|
'JP': { lat: 36.2048, lng: 138.2529 },
|
|
'KR': { lat: 35.9078, lng: 127.7669 },
|
|
'CN': { lat: 35.8617, lng: 104.1954 },
|
|
'BR': { lat: -14.2350, lng: -51.9253 },
|
|
'AR': { lat: -38.4161, lng: -63.6167 },
|
|
'MX': { lat: 23.6345, lng: -102.5528 },
|
|
'CL': { lat: -35.6751, lng: -71.5430 },
|
|
'CO': { lat: 4.5709, lng: -74.2973 },
|
|
'PE': { lat: -9.1900, lng: -75.0152 },
|
|
'TN': { lat: 33.8869, lng: 9.5375 },
|
|
'DZ': { lat: 28.0339, lng: 1.6596 },
|
|
'CI': { lat: 7.5399, lng: -5.5471 },
|
|
'KE': { lat: -0.0236, lng: 37.9062 },
|
|
'ZA': { lat: -30.5595, lng: 22.9375 },
|
|
'NG': { lat: 9.0820, lng: 8.6753 },
|
|
'GH': { lat: 7.9465, lng: -1.0232 },
|
|
'CM': { lat: 3.8480, lng: 11.5021 },
|
|
'ET': { lat: 9.1450, lng: 40.4897 },
|
|
'TZ': { lat: -6.3690, lng: 34.8888 },
|
|
'UG': { lat: 1.3733, lng: 32.2903 },
|
|
'RW': { lat: -1.9403, lng: 29.8739 },
|
|
'IN': { lat: 20.5937, lng: 78.9629 },
|
|
'BD': { lat: 23.6850, lng: 90.3563 },
|
|
'PK': { lat: 30.3753, lng: 69.3451 },
|
|
'ID': { lat: -0.7893, lng: 113.9213 },
|
|
'TH': { lat: 15.8700, lng: 100.9925 },
|
|
'VN': { lat: 14.0583, lng: 108.2772 },
|
|
'MY': { lat: 4.2105, lng: 101.9758 },
|
|
'PH': { lat: 12.8797, lng: 121.7740 },
|
|
}
|
|
|
|
async function geocodeNominatim(ville, pays) {
|
|
await new Promise(r => setTimeout(r, 1100)) // rate limit 1 req/sec
|
|
try {
|
|
const query = encodeURIComponent(`${ville}, ${pays}`)
|
|
const url = `https://nominatim.openstreetmap.org/search?q=${query}&format=json&limit=1`
|
|
const res = await fetch(url, { headers: { 'User-Agent': 'AEP-Bifurcation-Map/1.0' } })
|
|
const result = await res.json()
|
|
if (result.length > 0) {
|
|
return { lat: parseFloat(result[0].lat), lng: parseFloat(result[0].lon) }
|
|
}
|
|
} catch (e) {
|
|
console.warn(`Geocoding failed for ${ville}, ${pays}: ${e.message}`)
|
|
}
|
|
return null
|
|
}
|
|
|
|
async function main() {
|
|
let enriched = 0
|
|
let fromCache = 0
|
|
let fromNominatim = 0
|
|
let fromCountryFallback = 0
|
|
let failed = 0
|
|
|
|
console.log(`Starting geocoding of ${data.structures.length} structures...`)
|
|
|
|
for (const structure of data.structures) {
|
|
// Déjà geocodé
|
|
if (structure.latitude != null && structure.longitude != null) {
|
|
enriched++
|
|
continue
|
|
}
|
|
|
|
const villeKey = structure.ville?.trim()
|
|
|
|
// 1. Lookup statique
|
|
if (villeKey && CITY_COORDS[villeKey]) {
|
|
structure.latitude = CITY_COORDS[villeKey].lat
|
|
structure.longitude = CITY_COORDS[villeKey].lng
|
|
enriched++
|
|
fromCache++
|
|
process.stdout.write('.')
|
|
continue
|
|
}
|
|
|
|
// 2. Nominatim
|
|
if (villeKey && structure.pays) {
|
|
const coords = await geocodeNominatim(villeKey, structure.pays)
|
|
if (coords) {
|
|
structure.latitude = coords.lat
|
|
structure.longitude = coords.lng
|
|
enriched++
|
|
fromNominatim++
|
|
console.log(`\n Geocoded: ${structure.nom} -> ${villeKey} (${coords.lat.toFixed(3)}, ${coords.lng.toFixed(3)})`)
|
|
continue
|
|
}
|
|
}
|
|
|
|
// 3. Fallback pays
|
|
if (structure.pays && COUNTRY_FALLBACK[structure.pays]) {
|
|
structure.latitude = COUNTRY_FALLBACK[structure.pays].lat
|
|
structure.longitude = COUNTRY_FALLBACK[structure.pays].lng
|
|
enriched++
|
|
fromCountryFallback++
|
|
process.stdout.write('~')
|
|
continue
|
|
}
|
|
|
|
console.warn(`\n No coords for: ${structure.nom} (${structure.ville}, ${structure.pays})`)
|
|
structure.latitude = null
|
|
structure.longitude = null
|
|
failed++
|
|
}
|
|
|
|
console.log('\n')
|
|
fs.writeFileSync(DATA_PATH, JSON.stringify(data, null, 2), 'utf-8')
|
|
console.log(`Done: ${enriched}/${data.structures.length} structures geocoded`)
|
|
console.log(` - Cache statique : ${fromCache}`)
|
|
console.log(` - Nominatim API : ${fromNominatim}`)
|
|
console.log(` - Fallback pays : ${fromCountryFallback}`)
|
|
console.log(` - Echecs : ${failed}`)
|
|
}
|
|
|
|
main().catch(console.error)
|