"""Distance routière et coût carburant entre deux adresses (OpenStreetMap : Nominatim + OSRM). Aucune dépendance ni clé d'API : on utilise les services publics gratuits d'OpenStreetMap via la bibliothèque standard. Usage personnel et ponctuel (un prospect à la fois). """ import json import re import urllib.parse import urllib.request NOMINATIM = "https://nominatim.openstreetmap.org/search" OSRM = "https://router.project-osrm.org/route/v1/driving" # Nominatim impose un User-Agent identifiant l'application ENTETES = {"User-Agent": "AutoMood/1.0 (outil local de prospection)"} # Les coordonnées d'une adresse ne changent pas : on évite de re-géocoder (et on # reste poli envers Nominatim, dont l'usage est limité à ~1 requête/seconde). _cache_geo = {} class ErreurTrajet(Exception): pass def _http_json(url): requete = urllib.request.Request(url, headers=ENTETES) try: with urllib.request.urlopen(requete, timeout=20) as reponse: return json.load(reponse) except Exception as e: raise ErreurTrajet(f"Service de cartographie injoignable : {e}") def geocoder(adresse): """Adresse texte -> (longitude, latitude). Lève ErreurTrajet si introuvable.""" adresse = (adresse or "").strip() if not adresse: raise ErreurTrajet("Adresse vide.") if adresse in _cache_geo: return _cache_geo[adresse] params = urllib.parse.urlencode({ "q": adresse, "format": "json", "limit": 1, "countrycodes": "fr", }) donnees = _http_json(f"{NOMINATIM}?{params}") if not donnees: raise ErreurTrajet(f"Adresse introuvable : « {adresse} ».") coord = (float(donnees[0]["lon"]), float(donnees[0]["lat"])) _cache_geo[adresse] = coord return coord def _est_autoroute(ref): """Vrai si le numéro de route est une autoroute française (« A 11 », « A8;E60 »...). Heuristique : la plupart des autoroutes « A » sont à péage. Certaines sont gratuites (rocades, sections urbaines) : l'estimation reste donc approximative. """ return any(re.match(r"A\s?\d", t.strip(), re.I) for t in re.split(r"[;,]", ref or "")) def itineraire(depart, arrivee): """(lon, lat) x2 -> (distance_km, duree_min, km_autoroute) du trajet le plus court.""" (lon1, lat1), (lon2, lat2) = depart, arrivee donnees = _http_json(f"{OSRM}/{lon1},{lat1};{lon2},{lat2}?overview=false&steps=true") if donnees.get("code") != "Ok" or not donnees.get("routes"): raise ErreurTrajet("Aucun itinéraire routier trouvé entre ces deux adresses.") route = donnees["routes"][0] metres_autoroute = sum( etape.get("distance", 0) for jambe in route.get("legs", []) for etape in jambe.get("steps", []) if _est_autoroute(etape.get("ref")) ) return route["distance"] / 1000.0, route["duration"] / 60.0, metres_autoroute / 1000.0 def couts(distance_km, duree_min, km_peage, conso_l_100km, prix_carburant, cout_peage_km=0.0): """Données géographiques + tarifs -> dict complet (coûts aller simple et aller-retour). Calcul purement local (aucun appel réseau) : permet de réappliquer de nouveaux prix sur une distance déjà connue sans re-géocoder ni re-router. Le péage est estimé : km d'autoroute du trajet × `cout_peage_km` (tarif moyen paramétrable, en €/km). Mettre 0 pour ne pas compter de péage. """ carburant = distance_km / 100.0 * conso_l_100km * prix_carburant peage = km_peage * cout_peage_km cout_aller = carburant + peage return { "distance_km": round(distance_km, 1), "distance_aller_retour_km": round(distance_km * 2, 1), "duree_min": round(duree_min), "km_peage": round(km_peage, 1), "carburant_aller": round(carburant, 2), "carburant_aller_retour": round(carburant * 2, 2), "peage_aller": round(peage, 2), "peage_aller_retour": round(peage * 2, 2), "cout_aller": round(cout_aller, 2), "cout_aller_retour": round(cout_aller * 2, 2), } def calculer(adresse_depart, adresse_arrivee, conso_l_100km, prix_carburant, cout_peage_km=0.0): """Estime distance, durée, coût carburant et péage (aller simple et aller-retour). Fait les appels réseau (Nominatim + OSRM) puis délègue le calcul des coûts à `couts`. """ distance_km, duree_min, km_peage = itineraire( geocoder(adresse_depart), geocoder(adresse_arrivee)) return couts(distance_km, duree_min, km_peage, conso_l_100km, prix_carburant, cout_peage_km) if __name__ == "__main__": import sys if len(sys.argv) != 3: print("Usage : python trajet.py ") sys.exit(1) print(json.dumps(calculer(sys.argv[1], sys.argv[2], 6.5, 1.90), ensure_ascii=False, indent=2))