wip: snapshot V2 cascade onglet 2 (sauvegarde avant chirurgie git-hygiene)

This commit is contained in:
Jules Neny
2026-05-06 15:37:13 +02:00
parent 5878c56888
commit e63d02a351
101 changed files with 188900 additions and 3959 deletions

View File

@@ -0,0 +1,356 @@
/**
* 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)