File size: 6,361 Bytes
ff39e1a
 
 
 
6f1635e
ff39e1a
 
6c7d496
ff39e1a
 
6c7d496
ff39e1a
 
7c640f7
 
6c7d496
 
7c640f7
ff39e1a
 
6c7d496
ff39e1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
029672d
ff39e1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6f1635e
ff39e1a
 
 
 
 
 
 
 
 
ad68f89
ff39e1a
 
ad68f89
ff39e1a
 
 
 
 
 
 
 
 
 
 
ad68f89
 
 
ff39e1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223e856
 
 
 
 
 
6c7d496
223e856
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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()