import matplotlib.pyplot as plt
import geopandas as gpd
from shapely.geometry import LineString
from geopy.geocoders import Nominatim
import time
# — Données —
stages_bike = [
« Virieu-le-Grand », « Gex », « Pontarlier », « Seppois-le-Bas », « Ribeauvillé »,
« Saverne », « Metz », « Mouzon 08210 », « Sémeries », « Boeschepe », « Escalles »,
« Groffliers », « Martigny », « Yport », « Honfleur », « Osmanville », « Cherbourg »,
« Saint Pair-sur-Mer », « Mont-Saint-Michel », « Saint Malo »,
« Nantes », « Loireauxence », « Chouzé-sur-Loire », « Muides-sur-Loire »,
« Gien », « Nevers »
]
stages_train = [
(« Saint Etienne », « Virieu-le-Grand »), # segment initial
(« Saint Malo », « Nantes »),
(« Nevers », « Saint Etienne »)
]
# — Géocodage —
geolocator = Nominatim(user_agent= »velo_carte »)
locations = {}
for place in set(stages_bike + [p for seg in stages_train for p in seg]):
loc = geolocator.geocode(f »{place}, France »)
if loc:
locations[place] = (loc.longitude, loc.latitude)
else:
print(f »Non trouvé : {place} »)
time.sleep(1)
# — Segments vélo —
bike_segment1 = [p for p in stages_bike if p not in [« Nantes », « Loireauxence », « Chouzé-sur-Loire », « Muides-sur-Loire », « Gien », « Nevers »]]
bike_segment2 = [« Nantes », « Loireauxence », « Chouzé-sur-Loire », « Muides-sur-Loire », « Gien », « Nevers »]
# — Carte France Métropole —
shapefile_path = « /media/damien/DATA/DOCUMENT/vélo/2025/27 – TDF/Python/ne_110m_admin_0_countries/ne_110m_admin_0_countries.shp »
world = gpd.read_file(shapefile_path)
france = world[world.NAME == « France »].copy()
france = france.to_crs(epsg=2154)
france[‘area’] = france.geometry.area
france_metropole = france.loc[france[‘area’].idxmax()].geometry
france_metropole_gs = gpd.GeoSeries([france_metropole], crs= »EPSG:2154″).to_crs(epsg=4326)
# — Figure 5000×5000 px —
fig, ax_map = plt.subplots(figsize=(50, 50))
france_metropole_gs.plot(ax=ax_map, color= »#FFD433″, edgecolor= »black »)
# — Tracés vélo —
if bike_segment1:
coords1 = [locations[p] for p in bike_segment1 if p in locations]
gpd.GeoSeries(LineString(coords1), crs= »EPSG:4326″).plot(ax=ax_map, color= »black », linewidth=2)
if bike_segment2:
coords2 = [locations[p] for p in bike_segment2 if p in locations]
gpd.GeoSeries(LineString(coords2), crs= »EPSG:4326″).plot(ax=ax_map, color= »blue », linewidth=2)
# — Tracés train pointillé rouge —
for start, end in stages_train:
if start in locations and end in locations:
gpd.GeoSeries(LineString([locations[start], locations[end]]), crs= »EPSG:4326″).plot(
ax=ax_map, color= »red », linewidth=2, linestyle=’–‘
)
# — Numéros sur points —
# Premier segment vélo (noir)
for idx, place in enumerate(bike_segment1):
if place in locations:
lon, lat = locations[place]
ax_map.plot(lon, lat, ‘o’, markersize=15, markeredgecolor=’black’, markerfacecolor=’white’, zorder=5)
ax_map.text(lon, lat, str(idx), fontsize=12, ha=’center’, va=’center’, zorder=6)
# Deuxième segment vélo (bleu) avec numéros spécifiques
etapes_numero = [(« Loireauxence », 20), (« Chouzé-sur-Loire », 21),
(« Muides-sur-Loire », 22), (« Gien », 23), (« Nevers », 24)]
for place, numero in etapes_numero:
if place in locations:
lon, lat = locations[place]
ax_map.plot(lon, lat, ‘o’, markersize=15, markeredgecolor=’blue’, markerfacecolor=’white’, zorder=5)
ax_map.text(lon, lat, str(numero), fontsize=12, ha=’center’, va=’center’, zorder=6)
# — Dates départ/arrivée —
if « Virieu-le-Grand » in locations:
ax_map.text(locations[« Virieu-le-Grand »][0], locations[« Virieu-le-Grand »][1] – 0.5,
« 22 juillet 2025″, fontsize=14, color= »black »)
if « Nevers » in locations:
ax_map.text(locations[« Nevers »][0], locations[« Nevers »][1] – 0.5,
« 14 août 2025″, fontsize=14, color= »black »)
# — Labels supplémentaires —
if « Saint Etienne » in locations:
ax_map.text(locations[« Saint Etienne »][0], locations[« Saint Etienne »][1] + 0.2,
« Saint Etienne », fontsize=14, color= »black », ha=’center’)
if « Saint Malo » in locations:
ax_map.text(locations[« Saint Malo »][0], locations[« Saint Malo »][1] + 0.2,
« Saint Malo », fontsize=14, color= »black », ha=’center’)
# — Kilométrage total —
ax_map.text(0.02, 0.95, « 3000 km », transform=ax_map.transAxes,
fontsize=24, fontweight=’bold’, color= »black », ha=’left’, va=’top’)
# — Légende des villes —
legend_text = [f »{i}: {p} » for i, p in enumerate(stages_bike)]
n = len(legend_text)
margin_top = 0.95
margin_bottom = 0.05
for i, text in enumerate(legend_text):
y_pos = margin_top – i * (margin_top – margin_bottom) / max(n-1, 1)
ax_map.text(1.02, y_pos, text, transform=ax_map.transAxes,
fontsize=12, color= »red », va=’top’, ha=’left’)
ax_map.set_axis_off()
# — Titre avec drapeau pirate —
ax_map.set_title(« Saint Malo, par les routes du Tour de France ☠️ »,
fontsize=36, fontweight=’bold’, color= »black », pad=50)
# — Sauvegarde SVG —
plt.savefig(« carte_voyage_velo_metropole_legende_5000px.svg », format= »svg », bbox_inches= »tight »)
plt.close()
print(« ✅ Carte finale mise à jour : segment bleu vélo Nantes → Loireauxence inclus, tout le reste conservé. »)