import requests import folium import colorcet as cc import plotly.express as px import panel as pn import pandas as pd import geopandas as gpd from time import sleep from bokeh.models.widgets.tables import DateFormatter from vrp import calcul_routes from tableur import Tableur from routingmat import fetch_matrices pn.extension("tabulator", 'filedropper', 'plotly') wgs84="EPSG:4326" l93="EPSG:9794" pal=cc.glasbey_dark def mat_figs(dfadr, mat_dist, mat_dur): labels=[ f'{row["oid"]}:{row["lieu"]}' for _, row in dfadr.iterrows()] fig_dist = px.imshow(mat_dur, height=1500, x=labels, y=labels, title = "Distance (m) entre deux adresses") fig_dur = px.imshow(mat_dist, height=1500, x=labels, y=labels, title = "Temps (minutes) entre deux adresses") return fig_dist, fig_dur duree_taxi=pn.widgets.IntInput(name='Durée max taxi', value=45, step=1, start=0, end=300) duree_ecole=pn.widgets.IntInput(name='Durée max devant école', value=10, step=1, start=0, end=60) capacité=pn.widgets.IntInput(name='Capacité taxi', value=3, step=1, start=0, end=10) def mise_en_forme_dfinal(sol, dfadr): dfsol=sol.routes dfsol["num_vehicule"]=dfsol.groupby("vehicle_id").ngroup() dfsol["durée (minutes)"]=dfsol["duration"]//60 dfsol["temps d'attente (minutes)"]=dfsol["waiting_time"]//60 dfsol["indice"]=dfsol.location_index.map(dfadr["oid"].to_dict()) dfsol["lieu"] = dfsol.location_index.map(dfadr["lieu"].to_dict()) dfsol["geometry"] = dfsol.location_index.map(dfadr["geometry"].to_dict()) dfsol["heure"]=dfsol.arrival.astype("datetime64[s]").dt.time goodcols=['num_vehicule', 'type', 'distance', 'durée (minutes)', "temps d'attente (minutes)", 'indice', 'lieu', 'geometry', 'heure'] dffinal=dfsol[goodcols].query("type in ['pickup', 'delivery']") return dffinal def fetch_itinéraires(dffinal): dres={} for igp, gp in dffinal.groupby("num_vehicule"): coo=[f"{p.x},{p.y}" for p in gp.geometry.to_list()] params=dict(resource= "bdtopo-osrm", start=coo[0], end=coo[-1], intermediates="|".join(coo[1:-2]), geometryFormat="geojson", getSteps='false', getBbox='true', timeUnit="minute", crs=wgs84) r=requests.get(url="https://data.geopf.fr/navigation/itineraire", params=params) sleep(0.15) if r.ok: dres[igp]=r.json() return dres def carte(dffinal, dres, dfadr): tiles='Stadia.AlidadeSmooth' tiles='Cartodb Positron' bbox=dfadr.total_bounds #[0.036343, 48.41117, 0.109909, 48.465001] m = folium.Map(location=[(bbox[3]+bbox[1])/2, (bbox[2]+bbox[0])/2], tiles=tiles)#, zoom_start=10) for igp, gp in dffinal.groupby("num_vehicule"): dirs=dres[igp] color=pal[igp] folium.GeoJson( name=f'Vehicle {igp}', data={"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": dirs['geometry'], "properties": {"color": color} }]}, style_function=lambda x: {"color": x['properties']['color']} ).add_to(m) for _, step in gp.iterrows(): icon='user' if step['type']=='pickup' else 'briefcase' folium.Marker( location=[step["geometry"].y, step["geometry"].x], icon=folium.Icon(color="lightgray", icon_color=color, icon=icon), popup=f'{step["lieu"]}\n{step["heure"]}' ).add_to(m) return m tabd=Tableur(idx_offset=1) tabp=Tableur(idx_offset=1001, delivery_tab=tabd) def tous_calculs(_): dfp=tabp.df.dropna() dest_col=tabp.dest_col.value gdfadr=gpd.GeoDataFrame(pd.concat([ dfp[["suggestion", "geometry_p"]].rename(columns={"suggestion" : "lieu", "geometry_p": "geometry"}), tabd.df[[dest_col, "geometry"]][tabd.df[dest_col].isin(tabp.df.dropna()[dest_col])].rename(columns={dest_col : "lieu"})], keys=["p", "d"], names=['type', 'oid']).drop_duplicates().reset_index() ) ladr=gdfadr["lieu"].to_list() mat_dist, mat_dur=fetch_matrices(gdfadr) fig_dist, fig_dur=mat_figs(gdfadr, mat_dist, mat_dur) tabs.append(('Matrice de distances', pn.pane.Plotly(fig_dist, width=1100, height=1000 ))) tabs.append(('Matrice de durées', pn.pane.Plotly(fig_dur, width=1100, height=1000 ))) sol=calcul_routes(dfp, ladr, mat_dist, mat_dur, duree_taxi.value, duree_ecole.value, capacité.value) gpu=dfp.loc[list({u["id"] for u in sol.to_dict()["unassigned"]})][['suggestion', 'indice_destination', dest_col, 'arrivée_max']].copy() gpu["Distance (km)"]=gpu.apply(lambda row: mat_dist[ladr.index(row["suggestion"])][ladr.index( row[dest_col])], axis=1) gpu["Durée (min)"]=gpu.apply(lambda row: mat_dur[ladr.index( row["suggestion"])][ladr.index( row[dest_col])]//60, axis=1) tabs.append(('Non assignés', pn.widgets.Tabulator(gpu, disabled=True, height=1000))) dffinal=mise_en_forme_dfinal(sol, gdfadr) tab_final=pn.widgets.Tabulator(dffinal.drop("geometry", axis=1).value_counts().reset_index().sort_values(by=["num_vehicule", "heure"]).reset_index(drop=True), formatters={"heure" : DateFormatter(format="%H:%M")}, disabled=True, height=1000) tabs.append(('Tableau des résultats', tab_final.style.map(lambda v: f'background-color:{pal[v]};color:white', subset="num_vehicule"))) dres=fetch_itinéraires(dffinal) m=carte(dffinal, dres, gdfadr) tabs.append(('Carte des résultats', pn.pane.plot.Folium(m, height=1000) )) return bouton=pn.widgets.Button(name="Lancer les calculs", button_type="primary", on_click=tous_calculs) tabs=pn.Tabs( ('Destinations', tabd.get_panel()), ('Usagers', tabp.get_panel()), ('Calculs des routes', pn.Column(duree_taxi, duree_ecole, capacité, bouton) ), ) template= pn.template.MaterialTemplate(title="Problème de tournée des véhicules avec capacités et fenêtre temporelle") template.main.append(tabs) template.header.append("v0.2.3") template.servable()