148 lines
5.5 KiB
JavaScript
148 lines
5.5 KiB
JavaScript
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();
|