Files
CARIA-AUTOMOTIVE/ui/geo.js
T

45 lines
3.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export function distance(a, b) {
const rad = Math.PI / 180, lat = (b.lat - a.lat) * rad, lon = (b.lon - a.lon) * rad;
const h = Math.sin(lat / 2) ** 2 + Math.cos(a.lat * rad) * Math.cos(b.lat * rad) * Math.sin(lon / 2) ** 2;
return 6371000 * 2 * Math.asin(Math.min(1, Math.sqrt(h)));
}
export function validateZones(data) {
if (data?.type !== 'FeatureCollection' || !Array.isArray(data.features) || data.features.length > 1000) throw new Error('Fichier GeoJSON attendu, 1 000 zones maximum.');
const ids = new Set();
return data.features.map((f, i) => {
const p = f.properties || {}, c = f.geometry?.coordinates;
if (f.geometry?.type !== 'Point' || !Array.isArray(c) || c.length !== 2 || !c.every(Number.isFinite) || Math.abs(c[0]) > 180 || Math.abs(c[1]) > 85 || !Number.isFinite(p.radiusM) || p.radiusM < 300 || p.radiusM > 10000 || !/^[A-Z]{2}$/.test(p.country || '') || typeof p.source !== 'string' || !p.source.trim() || !Number.isFinite(Date.parse(p.updatedAt)) || !Number.isFinite(Date.parse(p.expiresAt)) || Date.parse(p.expiresAt) <= Date.parse(p.updatedAt)) throw new Error(`Zone ${i + 1} invalide : coordonnées, pays, rayon (300–10 000 m), source et dates requis.`);
const id = String(p.id ?? i).slice(0, 100);
if (ids.has(id)) throw new Error('Identifiants de zones en double.'); ids.add(id);
return { id, lat: c[1], lon: c[0], radiusM: p.radiusM, country: p.country, name: String(p.name || 'Zone de vigilance').slice(0, 120), source: p.source.slice(0, 150), updatedAt: p.updatedAt, expiresAt: p.expiresAt };
});
}
export function nearbyZones(zones, position, country, now = Date.now()) {
if (!position || position.source !== 'gps' || !Number.isFinite(position.accuracy) || position.accuracy > 100 || !Number.isFinite(position.timestamp) || Math.abs(now - position.timestamp) > 15000) return [];
return zones.filter(z => z.country === country && Date.parse(z.updatedAt) <= now && Date.parse(z.expiresAt) > now)
.map(z => ({ ...z, remaining: Math.max(0, distance(position, z) - z.radiusM) }))
.filter(z => z.remaining <= 400).sort((a, b) => a.remaining - b.remaining);
}
export function instruction(step) {
const m = step.maneuver || {}, ways = { left: 'à gauche', right: 'à droite', 'slight left': 'légèrement à gauche', 'slight right': 'légèrement à droite', 'sharp left': 'franchement à gauche', 'sharp right': 'franchement à droite', straight: 'tout droit', uturn: 'en faisant demi-tour' };
if (m.type === 'arrive') return 'Vous arrivez à destination';
if (m.type === 'depart') return `Départ${step.name ? ` sur ${step.name}` : ''}`;
if (['roundabout', 'rotary'].includes(m.type)) return `Au rond-point, ${m.exit ? `prenez la sortie ${m.exit}` : 'suivez l’itinéraire'}`;
return `${m.type === 'merge' ? 'Insérez-vous' : m.type === 'fork' ? 'Restez' : 'Continuez'} ${ways[m.modifier] || 'sur l’itinéraire'}${step.name ? ` sur ${step.name}` : ''}`;
}
// Nearest segment projection gives progress even on long rural road segments.
export function routeProgress(coords, position) {
let best = { away: Infinity, travelled: 0 }, total = 0;
const scale = Math.cos(position.lat * Math.PI / 180);
for (let i = 1; i < coords.length; i++) {
const a = { lon: coords[i - 1][0], lat: coords[i - 1][1] }, b = { lon: coords[i][0], lat: coords[i][1] };
const dx = (b.lon - a.lon) * scale, dy = b.lat - a.lat;
const fraction = Math.max(0, Math.min(1, (((position.lon - a.lon) * scale) * dx + (position.lat - a.lat) * dy) / (dx * dx + dy * dy || 1)));
const point = { lon: a.lon + fraction * (b.lon - a.lon), lat: a.lat + fraction * (b.lat - a.lat) };
const length = distance(a, b), away = distance(position, point);
if (away < best.away) best = { away, travelled: total + fraction * length };
total += length;
}
return { ...best, total, fraction: total ? best.travelled / total : 0 };
}