Files
AutoMood/trajet.py
jerem 73d1653225 Calcul de trajet auto après scrape + recalcul local intelligent
- trajet.py : fonction couts() pure (calcul des coûts sans appel réseau)
- app.py : mémorisation distance/durée/péage + adresses dans le CSV ;
  auto-calcul best-effort après ajout (scrape simple et en lot) ;
  api_distance recalcule en local si adresses inchangées, complet sinon
- index.html : affichage auto du trajet en cache à l'ouverture du prospect
2026-06-13 17:43:24 +02:00

120 lines
4.7 KiB
Python
Raw Permalink 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.
"""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 <adresse-depart> <adresse-arrivee>")
sys.exit(1)
print(json.dumps(calculer(sys.argv[1], sys.argv[2], 6.5, 1.90), ensure_ascii=False, indent=2))