feat: (broken) implement route map and address autocomplete widgets with associated infrastructure testing scripts
This commit is contained in:
@@ -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();
|
||||
Reference in New Issue
Block a user