feat: (broken) implement route map and address autocomplete widgets with associated infrastructure testing scripts

This commit is contained in:
ElPoyo
2026-06-05 11:10:32 +02:00
parent 8c01a21728
commit 21d7bc8b87
40 changed files with 21078 additions and 30 deletions
+147
View File
@@ -0,0 +1,147 @@
const axios = require('axios');
const fs = require('fs');
const csv = require('csv-parser');
const polylineLib = require('@mapbox/polyline');
require('dotenv').config({ path: '.env' });
function loadTollStations() {
return new Promise((resolve) => {
const csvPath = './travel/gares_peage_export.csv';
const results = [];
fs.createReadStream(csvPath)
.pipe(csv())
.on('data', (row) => {
if (row.id_gare && row.lat && row.lon) {
results.push({
id: row.id_gare,
operatorId: row.id_gare.substring(0, 2),
tollId: row.id_gare.substring(2, 5),
name: row.nom || '',
lat: parseFloat(row.lat),
lon: parseFloat(row.lon),
});
}
})
.on('end', () => resolve(results));
});
}
function _distKm(lat1, lng1, lat2, lng2) {
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLng = (lng2 - lng1) * Math.PI / 180;
const a = Math.sin(dLat/2)**2 + Math.cos(lat1*Math.PI/180) * Math.cos(lat2*Math.PI/180) * Math.sin(dLng/2)**2;
return 6371 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
}
async function getUlysRate(vehicleCategory, passages) {
try {
const payload = {
vehicleCategory: String(vehicleCategory),
paymentOption: 2,
tollPassages: passages.map((p) => ({
toll: { operatorId: p.operatorId, tollId: p.tollId },
passageDate: new Date().toISOString(),
})),
};
const res = await axios.post('https://api-ulys.azure-api.net/tollstation/v1/rate', payload);
const data = res.data;
if (Array.isArray(data) && data.length > 0) {
if (passages.length === 2) {
if (data.length !== 1 || !data[0].exitToll) return null;
return data[0].price > 0 ? data[0].price : null;
} else {
if (data.length === 1 && data[0].price > 0) return data[0].price;
const total = data.reduce((sum, d) => sum + (d.price || 0), 0);
return total > 0 ? total : null;
}
}
return null;
} catch (e) { return null; }
}
async function test() {
const origin = "25 Impasse du Puits du Suc, Saint-Martin-en-Haut, France";
const destination = "Nice, France";
const apiKey = process.env.API_MAPS;
const routesUrl = 'https://routes.googleapis.com/directions/v2:computeRoutes';
const res = await axios.post(routesUrl, {
travelMode: 'DRIVE', routingPreference: 'TRAFFIC_UNAWARE', origin: { address: origin }, destination: { address: destination },
}, { headers: { 'Content-Type': 'application/json', 'X-Goog-Api-Key': apiKey, 'X-Goog-FieldMask': 'routes.polyline.encodedPolyline' }});
const poly = res.data.routes[0].polyline.encodedPolyline;
const coords = polylineLib.decode(poly, 5);
const safePolyline = poly;
const url = 'https://api-ulys.azure-api.net/placemark/v2/legs?precision=5&includeLayersIds=GaresPeage';
const ulysRes = await axios.post(url, JSON.stringify(safePolyline), { headers: { 'Content-Type': 'application/json' } });
const items = Array.isArray(ulysRes.data) ? ulysRes.data : (ulysRes.data.features || []);
const stations = await loadTollStations();
const gates = [];
for (const item of items) {
const pm = item.Placemark || item.placemark || {};
const tags = pm.Tags || pm.tags || {};
let idStr = tags.ID_PEAGE;
if (!idStr && pm.Code) idStr = pm.Code.split('_')[0];
const s = stations.find(s => s.id === idStr);
if (s && !gates.find(g => g.id === idStr)) gates.push(s);
}
// Fallback for missing first system
let missingSystemPrice = 0;
if (gates.length > 0) {
const originLat = coords[0][0];
const originLng = coords[0][1];
const firstGate = gates[0];
const distToFirstGate = _distKm(originLat, originLng, firstGate.lat, firstGate.lon);
if (distToFirstGate > 50) {
console.log(`First gate ${firstGate.name} is ${Math.round(distToFirstGate)}km from origin. Checking for missing system...`);
// Find all geometric gates within 2km of the route, UP TO the firstGate
let firstGateIndex = 0;
for (let i = 0; i < coords.length; i++) {
if (_distKm(coords[i][0], coords[i][1], firstGate.lat, firstGate.lon) < 1) {
firstGateIndex = i; break;
}
}
const candidates = [];
stations.forEach(s => {
let minDist = Infinity;
let minIndex = -1;
for (let i = 0; i < firstGateIndex; i++) {
const d = _distKm(s.lat, s.lon, coords[i][0], coords[i][1]);
if (d < minDist) { minDist = d; minIndex = i; }
}
if (minDist < 2) {
candidates.push({ ...s, polyIndex: minIndex });
}
});
candidates.sort((a, b) => a.polyIndex - b.polyIndex);
if (candidates.length >= 2) {
// Try combinations from furthest to closest to find the longest closed system
let found = false;
for (let i = 0; i < Math.min(10, candidates.length); i++) {
for (let j = candidates.length - 1; j > i && j > candidates.length - 10; j--) {
if (candidates[i].operatorId !== candidates[j].operatorId) continue;
const price = await getUlysRate(2, [candidates[i], candidates[j]]);
if (price) {
console.log(`Found missing system: ${candidates[i].name} -> ${candidates[j].name} = ${price}`);
missingSystemPrice += price;
found = true;
break;
}
}
if (found) break;
}
}
}
}
console.log(`Missing system price: ${missingSystemPrice}`);
}
test();