Lbasara commited on
Commit
4282592
·
verified ·
1 Parent(s): 59fb8b4

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +0 -0
  2. pyproject.toml +27 -0
  3. tournenrond.py +214 -0
  4. uv.lock +0 -0
README.md ADDED
File without changes
pyproject.toml ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "autoroute"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "matplotlib>=3.9.2",
9
+ "networkx>=3.4.2",
10
+ "osmnx>=2",
11
+ "pdfplumber>=0.11.4",
12
+ "plotly>=5.24.1",
13
+ "pymupdf>=1.24.13",
14
+ "pymupdf4llm>=0.0.17",
15
+ "pypdf2<3.0",
16
+ "pyvis>=0.3.2",
17
+ "scipy>=1.14.1",
18
+ "tabula-py>=2.10.0",
19
+ "tabula>=1.0.5",
20
+ "scikit-learn>=1.5.2",
21
+ "jupyterlab>=4.3.1",
22
+ "folium>=0.18.0",
23
+ "panel>=1.5.4",
24
+ "param>=2.1.1",
25
+ "mapclassify>=2.8.1",
26
+ "colorcet>=3.1.0",
27
+ ]
tournenrond.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import osmnx as ox
3
+ import networkx as nx
4
+ import folium
5
+ from networkx.classes.function import path_weight
6
+ from osmnx.geocoder import geocode
7
+ import panel as pn
8
+ import param
9
+ import colorcet as cc
10
+ from plotly import graph_objects as go
11
+ from shapely.ops import linemerge
12
+ from pyproj import Transformer
13
+ import pandas as pd
14
+ import geopandas as gpd
15
+ from io import BytesIO
16
+ from time import sleep
17
+
18
+ ox.settings.elevation_url_template="https://api.opentopodata.org/v1/aster30m?locations={locations}"
19
+ pn.extension('plotly')
20
+
21
+ class Pars(param.Parameterized):
22
+ adresse= param.String(label="Adresse", default="Rue du Stade Saint-Ouen-de-Mimbré")
23
+ bornes = param.Range(label="Distance (km)", bounds=(1, 43), default=(5, 7), step=1)
24
+ pente=param.Number(label="Pente segment max (%)", bounds=(0, 15), default=10)
25
+ nresults=param.Integer(label="Number of results", bounds=(1, 20), default=5)
26
+ precision= param.Number(label="Précision (%)", softbounds=(0, 10), default=1,
27
+ doc="Plus faible : plus précis, plus élevé : calcul plus rapide\n5 est un bon compromis")
28
+
29
+ par=Pars()
30
+
31
+
32
+ def is_feasible(points, p, min_dist):
33
+ selected_points = [points[0]]
34
+ last_position = points[0]
35
+
36
+ for i in range(1, len(points)):
37
+ if points[i] - last_position >= min_dist:
38
+ selected_points.append(points[i])
39
+ last_position = points[i]
40
+ if len(selected_points) == p:
41
+ return True, selected_points
42
+ return False, []
43
+
44
+ def sorted_maxmin_dispersion(points, p):
45
+ left = 0
46
+ right = points[-1] - points[0]
47
+ best_dist = 0
48
+ best_points = []
49
+
50
+ while left <= right:
51
+ mid = (left + right) // 2
52
+ feasible, selected_points = is_feasible(points, p, mid)
53
+ if feasible:
54
+ best_dist = mid
55
+ best_points = selected_points
56
+ left = mid + 1
57
+ else:
58
+ right = mid - 1
59
+ return best_points, best_dist
60
+
61
+ def maxmin_dispersion(points, p) :
62
+ if p>=len(points):
63
+ return points, 0
64
+ points.sort()
65
+ best_points, best_dist=sorted_maxmin_dispersion(points, p)
66
+ best_points[-1]=points[-1]
67
+ return best_points, best_dist
68
+
69
+
70
+ def cycle_maxmin_dispersion(points, p, longueur):
71
+ if p>=len(points):
72
+ return points, 0
73
+ points.sort()
74
+ best_points, best_dist=sorted_maxmin_dispersion( points + [points[0] + longueur], p+1)
75
+ return best_points[:-1], best_dist
76
+
77
+
78
+
79
+
80
+
81
+ def create_trace(choixtrace):
82
+ df= dic_dfg[ int(choixtrace)-1 ]
83
+ dfg = gpd.GeoDataFrame(index=[0], crs='epsg:4326', geometry=[linemerge(df.geometry.to_list())])
84
+ sio = BytesIO()
85
+ dfg.to_file(sio, 'GPX')
86
+ sio.seek(0)
87
+ return sio
88
+
89
+
90
+ fig = go.Figure()
91
+ dic_dfg={}
92
+ calcul=pn.widgets.Button(button_type= "primary", name="Calculer parcours")
93
+ carte=pn.pane.plot.Folium(min_height=1000, sizing_mode='stretch_both')
94
+ choixtrace=pn.widgets.Select(name="Sélection trace", visible=False)
95
+ filedownload = pn.widgets.FileDownload(name="Télécharger GPX", button_type="primary",
96
+ callback=pn.bind(create_trace, choixtrace), filename='RunLoop.gpx', visible=False
97
+ )
98
+
99
+ elev=pn.pane.Plotly(fig, sizing_mode='stretch_width', visible=False)
100
+
101
+ tuiles= pn.widgets.RadioBoxGroup(name='Style carte',
102
+ options={'Style classique' : 'OpenStreetMap', 'Style clair': 'Cartodb Positron'}, inline=True)
103
+
104
+
105
+ def download_graph(coo):
106
+ G = ox.graph_from_point(coo, dist=par.bornes[1]*1000/2, dist_type="network", network_type='walk')
107
+ return G
108
+
109
+ def consolidate_intersections(G):
110
+ Gp= ox.projection.project_graph(G)
111
+ tolerance=sum(par.bornes)/2 * par.precision *10 # bornes en km, precision en %
112
+ Gc=ox.simplification.consolidate_intersections(Gp, tolerance=tolerance)
113
+ return Gc
114
+
115
+
116
+ def filter_elevations(Gc):
117
+ Gc=ox.projection.project_graph(Gc, to_latlong=True)
118
+ Ge=ox.elevation.add_edge_grades(ox.elevation.add_node_elevations_google(Gc, batch_size=100, pause=0.5))
119
+ Gf=Ge.copy()
120
+ for s, t, d in Ge.edges(data=True):
121
+ if d['grade_abs']>par.pente/100:
122
+ Gf.remove_edge(s, t)
123
+ return Gf
124
+
125
+ def get_paths(Gf, lon, lat):
126
+ source, target, _=ox.distance.nearest_edges(Gf, lon, lat)
127
+ mindist, maxdist=1000*par.bornes[0], 1000*par.bornes[1]
128
+ Gfu=ox.convert.to_digraph(Gf).to_undirected()
129
+ short_paths=nx.shortest_simple_paths(Gfu, source=source, target=target, weight="length")
130
+ dist=0
131
+ chemins=[]
132
+ i=0
133
+
134
+ while dist<maxdist:
135
+ i+=1
136
+ path=next(short_paths)+[source]
137
+ dist=path_weight(Gfu, path, weight="length")
138
+ if dist>mindist:
139
+ chemins.append((dist, path))
140
+
141
+ return chemins
142
+
143
+
144
+ def get_elevation(dfg, Gc):
145
+ df=pd.DataFrame(linemerge(dfg.geometry.to_list()).coords, columns=["x", "y"])
146
+ df[["ym", "xm"]]=linemerge(dfg.to_crs(Gc.graph["crs"]).geometry.to_list()).coords
147
+ df["segdists"]=np.hypot(*(df[["xm", "ym"]].diff().fillna(0).T.values))
148
+ df["segcumsum"]=np.cumsum(df["segdists"])
149
+
150
+ pts, optdist = maxmin_dispersion(df.segcumsum.to_list(), 100)
151
+
152
+ df2=df[df['segcumsum'].isin(pts)].copy()
153
+ Gchemin=nx.MultiDiGraph()
154
+ Gchemin.add_nodes_from( list(zip (df2.index, df2[["x", "y"]].to_dict('records'))) )
155
+ Gchemin=ox.elevation.add_node_elevations_google(Gchemin)
156
+ sleep(0.5)
157
+ idx, prop= zip(*Gchemin.nodes(data=True))
158
+ df2["elevation"]=pd.DataFrame(prop, index=idx)["elevation"]
159
+ return df2
160
+
161
+ def calculs(dummy):
162
+
163
+ coo=geocode(par.adresse)
164
+ lat, lon = coo
165
+
166
+ G=download_graph(coo)
167
+ Gll=consolidate_intersections(G)
168
+ Gf=filter_elevations(Gll)
169
+ chemins=get_paths(Gf, lon, lat)
170
+
171
+ distances=[chemin[0] for chemin in chemins]
172
+ selected_points, distance = maxmin_dispersion(distances, p=par.nresults)
173
+ good_chemins=[chemin for chemin in chemins if chemin[0] in selected_points]
174
+ elevations={}
175
+
176
+ m=None
177
+
178
+ for ic, (distance, chemin) in enumerate(good_chemins):
179
+ dfg=ox.routing.route_to_gdf(Gf, chemin).to_crs("latlon")
180
+ dic_dfg[ic]=dfg
181
+ m=dfg.explore(m=m, color=cc.b_glasbey_category10[ic], line_width=2, tiles="Cartodb Positron")
182
+ elevations[ic]=get_elevation(dfg, Gll)
183
+
184
+
185
+ folium.Marker( location=[lat, lon], tooltip=par.adresse,
186
+ icon=folium.Icon(prefix="fa", icon="fa-person-running")).add_to(m)
187
+
188
+ carte.object=m
189
+ labels=[]
190
+
191
+ fig.data=[]
192
+
193
+ for idx, df in elevations.items():
194
+ label=str(idx+1)
195
+ labels.append(label)
196
+ fig.add_trace(go.Scatter(x=df.segcumsum, y=df.elevation, name=label,
197
+ mode='lines', line_color=cc.b_glasbey_category10[idx], line_shape='spline'))
198
+
199
+ fig.update_layout(xaxis_title="Distance", yaxis_title="Élévation", margin=dict(l=20, r=20, t=20, b=20),)
200
+
201
+ choixtrace.options=labels
202
+ carte.visible=True
203
+ filedownload.visible=True
204
+ elev.visible=True
205
+ choixtrace.visible=True
206
+
207
+
208
+ calcul.on_click(calculs)
209
+
210
+
211
+ app=pn.Column(pn.Row(pn.Column( pn.Param(par, width=300, name="Paramètres"), tuiles, calcul, choixtrace, filedownload),
212
+ elev),
213
+ carte)
214
+ app.servable()
uv.lock ADDED
The diff for this file is too large to render. See raw diff