ThEyAtH commited on
Commit
6a705e0
Β·
1 Parent(s): d7e466e

Fixing random turns

Browse files
backend/api/routes.py CHANGED
@@ -11,14 +11,40 @@ DISTANCE_BUDGET_FACTOR_POLLUTION = 1.8
11
  DISTANCE_BUDGET_FACTOR_OVERALL = 1.5
12
 
13
 
14
- def route_to_geojson(G, route):
 
 
 
 
 
 
 
 
15
  """
16
- Build GeoJSON coordinates from the route node list.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- For each edge, use the OSM edge geometry (intermediate curve points)
19
- if available — otherwise fall back to straight start→end node coords.
20
- This prevents phantom turns caused by straight-line interpolation
21
- between widely-spaced nodes on curved roads.
22
  """
23
  if len(route) == 0:
24
  coordinates = []
@@ -26,20 +52,16 @@ def route_to_geojson(G, route):
26
  n = route[0]
27
  coordinates = [[G.nodes[n]["x"], G.nodes[n]["y"]]]
28
  else:
29
- coordinates = [[G.nodes[route[0]]["x"], G.nodes[route[0]]["y"]]]
30
  for i in range(len(route) - 1):
31
  u, v = route[i], route[i + 1]
32
  edge = list(G[u][v].values())[0]
33
- geom = edge.get("geometry")
34
- if geom is not None:
35
- # geom is a Shapely LineString β€” coords are (lon, lat)
36
- # Skip the first point (already added as previous node)
37
- pts = list(geom.coords)
38
- for lon, lat in pts[1:]:
39
- coordinates.append([lon, lat])
40
  else:
41
- # No intermediate geometry β€” just add the end node
42
- coordinates.append([G.nodes[v]["x"], G.nodes[v]["y"]])
43
 
44
  return {
45
  "type": "Feature",
 
11
  DISTANCE_BUDGET_FACTOR_OVERALL = 1.5
12
 
13
 
14
+ def _edge_geometry_coords(edge, u_node, v_node, G):
15
+ """
16
+ Return (lon, lat) coordinate list for an edge, using intermediate
17
+ geometry points where available.
18
+
19
+ osmnx stores geometry as either:
20
+ - A Shapely LineString (when built/pickled from fresh download)
21
+ - A WKT string like "LINESTRING(...)" (when loaded from graphml)
22
+ Both are handled here. Falls back to straight node→node if missing.
23
  """
24
+ geom = edge.get("geometry")
25
+ if geom is None:
26
+ return [
27
+ [G.nodes[u_node]["x"], G.nodes[u_node]["y"]],
28
+ [G.nodes[v_node]["x"], G.nodes[v_node]["y"]],
29
+ ]
30
+ try:
31
+ # Shapely LineString
32
+ if hasattr(geom, "coords"):
33
+ return [[lon, lat] for lon, lat in geom.coords]
34
+ # WKT string β€” parse it
35
+ from shapely.wkt import loads as wkt_loads
36
+ return [[lon, lat] for lon, lat in wkt_loads(str(geom)).coords]
37
+ except Exception:
38
+ return [
39
+ [G.nodes[u_node]["x"], G.nodes[u_node]["y"]],
40
+ [G.nodes[v_node]["x"], G.nodes[v_node]["y"]],
41
+ ]
42
+
43
 
44
+ def route_to_geojson(G, route):
45
+ """
46
+ Build GeoJSON coordinates from the route node list using full edge
47
+ geometry (intermediate curve points), preventing phantom turns.
48
  """
49
  if len(route) == 0:
50
  coordinates = []
 
52
  n = route[0]
53
  coordinates = [[G.nodes[n]["x"], G.nodes[n]["y"]]]
54
  else:
55
+ coordinates = []
56
  for i in range(len(route) - 1):
57
  u, v = route[i], route[i + 1]
58
  edge = list(G[u][v].values())[0]
59
+ pts = _edge_geometry_coords(edge, u, v, G)
60
+ if i == 0:
61
+ coordinates.extend(pts)
 
 
 
 
62
  else:
63
+ # Skip first point β€” already added as last point of prev edge
64
+ coordinates.extend(pts[1:])
65
 
66
  return {
67
  "type": "Feature",
backend/routing/routing_engine.py CHANGED
@@ -231,6 +231,12 @@ def weighted_directional_route(
231
 
232
  pq = []
233
 
 
 
 
 
 
 
234
  def edge_cost(edge, prev_node=None, curr_node=None, next_node=None):
235
  # If this edge leads into a signalled junction but the maneuver
236
  # is a free left turn, don't apply the signal delay cost
@@ -266,10 +272,15 @@ def weighted_directional_route(
266
 
267
  state = (prev, current)
268
 
 
 
 
 
269
  # Skip if we already found a cheaper way to this (prev, curr)
270
  if state in visited and visited[state] <= cost:
271
  continue
272
  visited[state] = cost
 
273
 
274
  # ── Reached destination β€” reconstruct path ────────────────────────
275
  if current == dest:
 
231
 
232
  pq = []
233
 
234
+ # ── Node-level guard: once a node is settled as 'current', never
235
+ # expand it again. This prevents the route from looping back
236
+ # through the same intersection via a different predecessor,
237
+ # which causes T-detours and U-shaped routes. ──────────────────────────
238
+ settled_nodes = set()
239
+
240
  def edge_cost(edge, prev_node=None, curr_node=None, next_node=None):
241
  # If this edge leads into a signalled junction but the maneuver
242
  # is a free left turn, don't apply the signal delay cost
 
272
 
273
  state = (prev, current)
274
 
275
+ # Skip if this node has already been settled (prevents backtracking)
276
+ if current in settled_nodes:
277
+ continue
278
+
279
  # Skip if we already found a cheaper way to this (prev, curr)
280
  if state in visited and visited[state] <= cost:
281
  continue
282
  visited[state] = cost
283
+ settled_nodes.add(current)
284
 
285
  # ── Reached destination β€” reconstruct path ────────────────────────
286
  if current == dest: