Lbasara commited on
Commit
ad68f89
·
1 Parent(s): 6c7d496

Traitement geocodage en lot

Browse files
Files changed (4) hide show
  1. CHANGELOG.md +2 -0
  2. app.py +5 -44
  3. requirements.txt +0 -1
  4. tableur.py +26 -53
CHANGELOG.md CHANGED
@@ -11,7 +11,9 @@ and this project adheres to [Semantic Versioning].
11
  ### Added
12
 
13
  ### Changed
 
14
  * Refacto : fetch_matrices dans un fichier externe
 
15
 
16
  ### Deprecated
17
 
 
11
  ### Added
12
 
13
  ### Changed
14
+ * Geocodage des adresses en lot
15
  * Refacto : fetch_matrices dans un fichier externe
16
+ * Suppression des références à "école" pour généraliser vers destinations
17
 
18
  ### Deprecated
19
 
app.py CHANGED
@@ -1,7 +1,6 @@
1
  import requests
2
  import folium
3
  import colorcet as cc
4
- import shapely
5
  import plotly.express as px
6
  import panel as pn
7
  import pandas as pd
@@ -25,43 +24,6 @@ l93="EPSG:9794"
25
  pal=cc.glasbey_dark
26
 
27
 
28
- def cree_adresse(df, cols):
29
- return df[cols].astype(str).agg(' '.join, axis=1).str.strip()
30
-
31
-
32
- def geocode(adresse, as_point=False, as_dict=False):
33
- r=requests.get(url="https://data.geopf.fr/geocodage/search",
34
- params={"q": adresse})
35
- if not r.ok:
36
- return None
37
- try:
38
- js=r.json()
39
- feature=js['features'][0]
40
- if as_point:
41
- return shapely.Point(feature['geometry']['coordinates'])
42
- if as_dict:
43
- return {js["query"] : feature}
44
- return feature
45
- except:
46
- return None
47
-
48
-
49
- def add_coo(adresse, details=False):
50
- feature=geocode(adresse)
51
- if feature is None:
52
- sleep(0.1)
53
- feature=geocode(adresse)
54
- if feature is None:
55
- return None, None, 0
56
- coo=shapely.Point(feature['geometry']['coordinates'])
57
- score=feature['properties']['score']
58
- suggestion=feature['properties']['label']
59
- return (coo, suggestion, score) if details else coo
60
-
61
-
62
-
63
-
64
-
65
  def mat_figs(dfadr, mat_dist, mat_dur):
66
  labels=[ f'{row["oid"]}:{row["lieu"]}' for _, row in dfadr.iterrows()]
67
  fig_dist = px.imshow(mat_dur, height=1500, x=labels, y=labels, title = "Distance (m) entre deux adresses")
@@ -75,8 +37,6 @@ capacité=pn.widgets.IntInput(name='Capacité taxi', value=3, step=1, start=0, e
75
 
76
 
77
 
78
-
79
-
80
  def mise_en_forme_dfinal(sol, dfadr):
81
  dfsol=sol.routes
82
  dfsol["num_vehicule"]=dfsol.groupby("vehicle_id").ngroup()
@@ -152,9 +112,10 @@ tabp=Tableur(idx_offset=1001, delivery_tab=tabd)
152
  def tous_calculs(_):
153
 
154
  dfp=tabp.df.dropna()
 
155
  gdfadr=gpd.GeoDataFrame(pd.concat([
156
  dfp[["suggestion", "geometry_p"]].rename(columns={"suggestion" : "lieu", "geometry_p": "geometry"}),
157
- tabd.df[["ECOLE", "geometry"]][tabd.df.ECOLE.isin(tabp.df.dropna()["ECOLE"])].rename(columns={"ECOLE" : "lieu"})],
158
  keys=["p", "d"], names=['type', 'oid']).drop_duplicates().reset_index() )
159
  ladr=gdfadr["lieu"].to_list()
160
 
@@ -166,9 +127,9 @@ def tous_calculs(_):
166
 
167
  sol=calcul_routes(dfp, ladr, mat_dist, mat_dur, duree_taxi.value, duree_ecole.value, capacité.value)
168
 
169
- gpu=dfp.loc[list({u["id"] for u in sol.to_dict()["unassigned"]})][['suggestion', 'indice école', 'ECOLE', 'arrivée_max']].copy()
170
- gpu["Distance (km)"]=gpu.apply(lambda row: mat_dist[ladr.index(row["suggestion"])][ladr.index( row["ECOLE"])], axis=1)
171
- gpu["Durée (min)"]=gpu.apply(lambda row: mat_dur[ladr.index( row["suggestion"])][ladr.index( row["ECOLE"])]//60, axis=1)
172
  tabs.append(('Non assignés', pn.widgets.Tabulator(gpu, disabled=True, height=1000)))
173
 
174
 
 
1
  import requests
2
  import folium
3
  import colorcet as cc
 
4
  import plotly.express as px
5
  import panel as pn
6
  import pandas as pd
 
24
  pal=cc.glasbey_dark
25
 
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  def mat_figs(dfadr, mat_dist, mat_dur):
28
  labels=[ f'{row["oid"]}:{row["lieu"]}' for _, row in dfadr.iterrows()]
29
  fig_dist = px.imshow(mat_dur, height=1500, x=labels, y=labels, title = "Distance (m) entre deux adresses")
 
37
 
38
 
39
 
 
 
40
  def mise_en_forme_dfinal(sol, dfadr):
41
  dfsol=sol.routes
42
  dfsol["num_vehicule"]=dfsol.groupby("vehicle_id").ngroup()
 
112
  def tous_calculs(_):
113
 
114
  dfp=tabp.df.dropna()
115
+ dest_col=tabp.dest_col.value
116
  gdfadr=gpd.GeoDataFrame(pd.concat([
117
  dfp[["suggestion", "geometry_p"]].rename(columns={"suggestion" : "lieu", "geometry_p": "geometry"}),
118
+ tabd.df[[dest_col, "geometry"]][tabd.df[dest_col].isin(tabp.df.dropna()[dest_col])].rename(columns={dest_col : "lieu"})],
119
  keys=["p", "d"], names=['type', 'oid']).drop_duplicates().reset_index() )
120
  ladr=gdfadr["lieu"].to_list()
121
 
 
127
 
128
  sol=calcul_routes(dfp, ladr, mat_dist, mat_dur, duree_taxi.value, duree_ecole.value, capacité.value)
129
 
130
+ gpu=dfp.loc[list({u["id"] for u in sol.to_dict()["unassigned"]})][['suggestion', 'indice_destination', dest_col, 'arrivée_max']].copy()
131
+ gpu["Distance (km)"]=gpu.apply(lambda row: mat_dist[ladr.index(row["suggestion"])][ladr.index( row[dest_col])], axis=1)
132
+ gpu["Durée (min)"]=gpu.apply(lambda row: mat_dur[ladr.index( row["suggestion"])][ladr.index( row[dest_col])]//60, axis=1)
133
  tabs.append(('Non assignés', pn.widgets.Tabulator(gpu, disabled=True, height=1000)))
134
 
135
 
requirements.txt CHANGED
@@ -106,7 +106,6 @@ pyogrio==0.11.1
106
  pyparsing==3.2.5
107
  pyproj==3.7.2
108
  pyproject-hooks==1.2.0
109
- pyrouting==1.3.0
110
  pyshp==2.3.1
111
  python-dateutil==2.9.0.post0
112
  python-json-logger==3.3.0
 
106
  pyparsing==3.2.5
107
  pyproj==3.7.2
108
  pyproject-hooks==1.2.0
 
109
  pyshp==2.3.1
110
  python-dateutil==2.9.0.post0
111
  python-json-logger==3.3.0
tableur.py CHANGED
@@ -1,52 +1,15 @@
1
- import numpy as np
2
-
3
  import dateparser
 
4
  import panel as pn
5
  import pandas as pd
 
6
 
7
  from bokeh.models.widgets.tables import DateFormatter
 
8
  from io import BytesIO, StringIO
9
  from datetime import timedelta
10
 
11
- import requests
12
- import shapely
13
-
14
- from time import sleep
15
-
16
-
17
- def cree_adresse(df, cols):
18
- return df[cols].astype(str).agg(' '.join, axis=1).str.strip()
19
-
20
-
21
- def geocode(adresse, as_point=False, as_dict=False):
22
- r=requests.get(url="https://data.geopf.fr/geocodage/search",
23
- params={"q": adresse})
24
- if not r.ok:
25
- return None
26
- try:
27
- js=r.json()
28
- feature=js['features'][0]
29
- if as_point:
30
- return shapely.Point(feature['geometry']['coordinates'])
31
- if as_dict:
32
- return {js["query"] : feature}
33
- return feature
34
- except:
35
- return None
36
-
37
-
38
- def add_coo(adresse, details=False):
39
- feature=geocode(adresse)
40
- if feature is None:
41
- sleep(0.1)
42
- feature=geocode(adresse)
43
- if feature is None:
44
- return None, None, 0
45
- coo=shapely.Point(feature['geometry']['coordinates'])
46
- score=feature['properties']['score']
47
- suggestion=feature['properties']['label']
48
- return (coo, suggestion, score) if details else coo
49
-
50
 
51
  def frenchdrop(dropper: pn.widgets.FileDropper):
52
  """Example of a FileDropper with French labels via JS injection."""
@@ -86,7 +49,7 @@ class Tableur():
86
  self.df=pd.DataFrame()
87
  self.dmime={'csv': 'text/csv', 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}
88
  self.feuille=pn.widgets.Select(name='Feuille de calcul', disabled=True, width=200)
89
- self.ecole_col=pn.widgets.Select(name="Colonne identifiant la destination", disabled=True, width=200)
90
  self.adr_col=pn.widgets.MultiChoice(name="Colonnes composant l'adresse", disabled=True, width=250)
91
  self.tabd=pn.widgets.Tabulator(height=1000,
92
  formatters={"arrivée_max" : DateFormatter(format="%H:%M")},
@@ -124,32 +87,42 @@ class Tableur():
124
  self.df = self.dfeuilles.get(self.feuille.value)
125
  if self.df is not None:
126
  cols=self.df.columns.to_list()
127
- self.ecole_col.options=cols
128
- self.ecole_col.disabled = False
129
  self.adr_col.options = cols
130
  self.adr_col.disabled = False
131
  #print("Colonnes :", adr_col.options)
 
 
 
 
 
 
 
 
 
 
 
132
 
133
 
134
  def lancer_calculs(self, _):
135
- self.df["adresse"]=cree_adresse(self.df, self.adr_col.value)
136
- self.df["geometry"], self.df["suggestion"], self.df["score"] = zip(*self.df.adresse.apply(add_coo, details=True))
137
- display_cols= self.adr_col.value + ["suggestion", "score", self.ecole_col.value, "arrivée_max"]
138
  if self.delivery_tab is None:
139
  auj9=dateparser.parse("aujourd'hui 9:00")
140
- self.df["arrivée_max"] = (auj9+ (self.df[self.ecole_col.value].str.split().str[0].isin({'Collège', 'Lycée'}))*timedelta(hours=-0.5))
141
  else:
142
  self.delivery_tab.df["arrivée_max"]=self.delivery_tab.tabd.value["arrivée_max"]
143
  self.df=self.df.merge(
144
- self.delivery_tab.df[[self.delivery_tab.ecole_col.value, "geometry", "arrivée_max"]].reset_index(names="indice école"),
145
- left_on=self.ecole_col.value, right_on=self.delivery_tab.ecole_col.value,
146
  how="left", suffixes=["_p", "_d"]
147
  )
148
- display_cols+=["indice école"]
149
  self.df.index = self.idx_offset+np.arange(len(self.df))
150
  self.tabd.value=self.df[display_cols]
151
  self.tabd.style.background_gradient(subset=["score"], cmap="RdYlGn", vmin=0.5, vmax=1.0)\
152
- .map(lambda v: '' if v == v else 'background-color:grey;', subset=["indice école", "arrivée_max"])
153
  self.tabd.param.trigger("value")
154
  return None
155
 
@@ -157,7 +130,7 @@ class Tableur():
157
  def get_panel(self):
158
  return pn.Column(pn.Row(frenchdrop(self.file_dropper),
159
  self.feuille,
160
- self.ecole_col,
161
  self.adr_col,
162
  self.bouton,
163
  height=100),
 
1
+ import requests
 
2
  import dateparser
3
+ import numpy as np
4
  import panel as pn
5
  import pandas as pd
6
+ from shapely import Point
7
 
8
  from bokeh.models.widgets.tables import DateFormatter
9
+
10
  from io import BytesIO, StringIO
11
  from datetime import timedelta
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  def frenchdrop(dropper: pn.widgets.FileDropper):
15
  """Example of a FileDropper with French labels via JS injection."""
 
49
  self.df=pd.DataFrame()
50
  self.dmime={'csv': 'text/csv', 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}
51
  self.feuille=pn.widgets.Select(name='Feuille de calcul', disabled=True, width=200)
52
+ self.dest_col=pn.widgets.Select(name="Colonne identifiant la destination", disabled=True, width=200)
53
  self.adr_col=pn.widgets.MultiChoice(name="Colonnes composant l'adresse", disabled=True, width=250)
54
  self.tabd=pn.widgets.Tabulator(height=1000,
55
  formatters={"arrivée_max" : DateFormatter(format="%H:%M")},
 
87
  self.df = self.dfeuilles.get(self.feuille.value)
88
  if self.df is not None:
89
  cols=self.df.columns.to_list()
90
+ self.dest_col.options=cols
91
+ self.dest_col.disabled = False
92
  self.adr_col.options = cols
93
  self.adr_col.disabled = False
94
  #print("Colonnes :", adr_col.options)
95
+
96
+
97
+ def batch_geocode(self):
98
+ r=requests.post(
99
+ url='https://data.geopf.fr/geocodage/search/csv',
100
+ files={'data': self.df[self.adr_col.value].to_csv(index=False)} )
101
+ if r.ok:
102
+ dfr=pd.read_csv(StringIO(r.text)) \
103
+ .rename(columns={"result_score": "score", "result_label": "suggestion"})
104
+ dfr["geometry"] = [Point(r["longitude"], r["latitude"]) for _, r in dfr.iterrows()]
105
+ self.df=pd.concat([self.df, dfr[["geometry", "score", "suggestion"]]], axis=1)
106
 
107
 
108
  def lancer_calculs(self, _):
109
+ self.batch_geocode()
110
+ display_cols= self.adr_col.value + ["suggestion", "score", self.dest_col.value, "arrivée_max"]
 
111
  if self.delivery_tab is None:
112
  auj9=dateparser.parse("aujourd'hui 9:00")
113
+ self.df["arrivée_max"] = (auj9+ (self.df[self.dest_col.value].str.split().str[0].isin({'Collège', 'Lycée'}))*timedelta(hours=-0.5))
114
  else:
115
  self.delivery_tab.df["arrivée_max"]=self.delivery_tab.tabd.value["arrivée_max"]
116
  self.df=self.df.merge(
117
+ self.delivery_tab.df[[self.delivery_tab.dest_col.value, "geometry", "arrivée_max"]].reset_index(names="indice_destination"),
118
+ left_on=self.dest_col.value, right_on=self.delivery_tab.dest_col.value,
119
  how="left", suffixes=["_p", "_d"]
120
  )
121
+ display_cols+=["indice_destination"]
122
  self.df.index = self.idx_offset+np.arange(len(self.df))
123
  self.tabd.value=self.df[display_cols]
124
  self.tabd.style.background_gradient(subset=["score"], cmap="RdYlGn", vmin=0.5, vmax=1.0)\
125
+ .map(lambda v: '' if v == v else 'background-color:grey;', subset=["indice_destination", "arrivée_max"])
126
  self.tabd.param.trigger("value")
127
  return None
128
 
 
130
  def get_panel(self):
131
  return pn.Column(pn.Row(frenchdrop(self.file_dropper),
132
  self.feuille,
133
+ self.dest_col,
134
  self.adr_col,
135
  self.bouton,
136
  height=100),