File size: 6,784 Bytes
ff39e1a
 
 
 
6f1635e
ff39e1a
 
6c7d496
ff39e1a
6c7d496
ff39e1a
 
7c640f7
 
6c7d496
 
7c640f7
ff39e1a
4afd3e0
6c7d496
ff39e1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa99f2c
 
ff39e1a
fa99f2c
ff39e1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4afd3e0
ff39e1a
 
 
 
 
 
 
8ba070b
ff39e1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa99f2c
ff39e1a
 
 
 
 
 
6f1635e
ff39e1a
 
 
 
 
4afd3e0
 
 
 
 
be99ee8
4afd3e0
 
 
 
 
 
be99ee8
4afd3e0
 
ff39e1a
 
 
 
ad68f89
ff39e1a
 
ad68f89
ff39e1a
 
 
 
 
 
 
 
 
 
 
fa99f2c
 
 
 
 
 
ff39e1a
 
 
4afd3e0
 
ff39e1a
 
 
 
 
 
 
 
be99ee8
ff39e1a
223e856
 
 
 
 
 
4afd3e0
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
159
160
161
162
163
164
165
166
167
168
169
170
171
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", '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
    dfsol["événement"]=dfsol.type.map({"pickup": "prise en charge", "delivery": "dépôt"})
    goodcols=['num_vehicule', 'événement', 'distance', 'durée (minutes)',
              "temps d'attente (minutes)", 'indice', 'lieu', 'geometry', 'heure']
    dffinal=dfsol[goodcols].dropna()
    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.1)
        if r.ok:
            dres[igp]=r.json()

    return dres


def carte(dffinal, dres, dfadr):
    tiles='GeoportailFrance.plan'
    
    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['événement']=='prise en charge' 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 tableau_resultats(dffinal):
    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)
    tab_final.style.map(lambda v: f'background-color:{pal[v]};color:white', subset="num_vehicule")
    
    fichier_exp, bouton_exp = tab_final.download_menu(
        text_kwargs={'label': 'Nom csv résultats', 'value': 'Résultats.csv'},
        button_kwargs={'label': 'Télécharger'}
    )
    return pn.Column(
        pn.Row(fichier_exp, bouton_exp), tab_final
    )


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)

    unass=sol.to_dict()["unassigned"]
    if len(unass):
        gpu=dfp.loc[list({u["id"] for u in unass})][['suggestion', 'indice école', 'ECOLE', 'arrivée_max']].copy()
        gpu["Distance (km)"]=gpu.apply(lambda row: mat_dist[ladr.index(row["suggestion"])][ladr.index( row["ECOLE"])], axis=1)
        gpu["Durée (min)"]=gpu.apply(lambda row: mat_dur[ladr.index( row["suggestion"])][ladr.index( row["ECOLE"])]//60, axis=1)
        tabs.append(('Non assignés', pn.widgets.Tabulator(gpu, disabled=True,  height=1000)))
    

    dffinal=mise_en_forme_dfinal(sol, gdfadr)

    tabs.append(('Tableau des résultats',  tableau_resultats(dffinal)))

    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.3.0")
template.servable()