@@ -63,8 +63,9 @@ def render_and_cache_tiles(
6363 output_dir: where rendered PNG tiles are written (`{clip_id}.png`).
6464 radius_m: render radius around each clip's centroid.
6565 image_size: output `(W, H)`.
66- network_cache_dir: if given, fetched graphs are persisted here keyed by
67- centroid so neighbouring clips share a cached download.
66+ network_cache_dir: if given, fetched graphs and the shared raw-OSM XML
67+ are persisted here (keyed by the clip's trajectory bbox) so
68+ neighbouring clips and the OSM vector branch reuse downloads.
6869 skip_existing: do not re-render clips whose PNG already exists.
6970
7071 Returns:
@@ -91,8 +92,11 @@ def render_and_cache_tiles(
9192 ego_lon = float (lons [- 1 ])
9293 ego_heading = _heading_from_trace (lats , lons , ego_lat )
9394
95+ # Fetch sized to the whole trajectory + the render radius, so the route
96+ # and the ±radius draw window are always covered (a centroid radius can
97+ # miss long clips).
9498 graph = _load_or_fetch_network (
95- ego_lat , ego_lon , radius_m , net_cache
99+ list ( lats ), list ( lons ) , radius_m , net_cache
96100 )
97101 if graph is None :
98102 logger .warning ("clip %s: failed to obtain road network; skipping" , clip_id )
@@ -146,35 +150,108 @@ def _heading_from_trace(
146150 return math .atan2 (dx , dy )
147151
148152
153+ def _graph_from_shared_osm (
154+ lats : Sequence [float ],
155+ lons : Sequence [float ],
156+ margin_m : int ,
157+ cache_dir : Path | None ,
158+ ) -> nx .MultiDiGraph | None :
159+ """Build the road graph from the shared raw-OSM cache (trace bbox + margin).
160+
161+ The SD-map (OSM vector) branch and this rendered-tile branch share a single
162+ on-disk artifact: the raw Overpass OSM XML (see
163+ ``data_parsing.osm_sd_map.overpass``). Both fetch the bounding box of the
164+ clip's trajectory expanded by their own margin (here the render radius); the
165+ shared cache reuses any already-fetched XML that covers the request, so one
166+ download serves both. Returns ``None`` (caller falls back to a direct fetch)
167+ if the shared path is unavailable for any reason.
168+ """
169+ try :
170+ import os
171+ import tempfile
172+
173+ import osmnx as ox
174+
175+ from ..osm_sd_map .overpass import load_or_fetch_osm_for_trace
176+ except Exception as exc : # noqa: BLE001 — optional path; never break rendering
177+ logger .debug ("shared OSM path unavailable (%s); using direct fetch" , exc )
178+ return None
179+
180+ xml = load_or_fetch_osm_for_trace (lats , lons , margin_m , cache_dir )
181+ if xml is None :
182+ return None
183+
184+ tmp = tempfile .NamedTemporaryFile ("w" , suffix = ".osm" , delete = False , encoding = "utf-8" )
185+ try :
186+ tmp .write (xml )
187+ tmp .close ()
188+ return ox .graph_from_xml (tmp .name , bidirectional = False , simplify = True , retain_all = True )
189+ except Exception as exc : # noqa: BLE001 — osmnx version/parse differences
190+ logger .warning ("graph_from_xml failed (%s); using direct fetch" , exc )
191+ return None
192+ finally :
193+ os .unlink (tmp .name )
194+
195+
196+ def _trace_centroid_radius (
197+ lats : Sequence [float ], lons : Sequence [float ], margin_m : int
198+ ) -> tuple [float , float , int ]:
199+ """Centroid + a radius that covers the whole trace plus ``margin_m``."""
200+ clat = sum (lats ) / len (lats )
201+ clon = sum (lons ) / len (lons )
202+ deg_to_m = EARTH_RADIUS_M * math .pi / 180.0
203+ cos_lat = math .cos (math .radians (clat ))
204+ max_d = 0.0
205+ for la , lo in zip (lats , lons ):
206+ dx = (lo - clon ) * cos_lat * deg_to_m
207+ dy = (la - clat ) * deg_to_m
208+ max_d = max (max_d , math .hypot (dx , dy ))
209+ return clat , clon , int (max_d + margin_m )
210+
211+
212+ def _graph_cache_path (
213+ lats : Sequence [float ], lons : Sequence [float ], margin_m : int , cache_dir : Path
214+ ) -> Path | None :
215+ """Pickle path for the built graph, keyed by the trace bbox."""
216+ try :
217+ from ..osm_sd_map .overpass import bbox_from_points
218+ except Exception : # noqa: BLE001
219+ return None
220+ s , w , n , e = bbox_from_points (lats , lons , margin_m )
221+ return cache_dir / f"graph_{ s :.4f} _{ w :.4f} _{ n :.4f} _{ e :.4f} .pkl"
222+
223+
149224def _load_or_fetch_network (
150- center_lat : float ,
151- center_lon : float ,
152- radius_m : int ,
225+ lats : Sequence [ float ] ,
226+ lons : Sequence [ float ] ,
227+ margin_m : int ,
153228 cache_dir : Path | None ,
154229) -> nx .MultiDiGraph | None :
155- """Return a cached graph if available, otherwise fetch and cache it.
230+ """Return a cached graph if available, otherwise build/ fetch and cache it.
156231
157- Centroids are quantized to ~100 m so nearby clips reuse the same download.
232+ The fetch is sized to the trajectory bounding box plus ``margin_m`` (the
233+ render radius), so long clips are fully covered (a fixed centroid radius can
234+ miss them). The graph is built from the shared raw-OSM cache when possible
235+ (reused by the OSM vector branch via bbox containment); otherwise it falls
236+ back to a direct osmnx fetch sized to cover the trace. The built graph is
237+ memoized as a pickle keyed by the trace bbox.
158238 """
159- if cache_dir is not None :
160- key = f"{ round (center_lat , 3 )} _{ round (center_lon , 3 )} _{ radius_m } .pkl"
161- cache_path = cache_dir / key
239+ cache_path = _graph_cache_path (lats , lons , margin_m , cache_dir ) if cache_dir is not None else None
240+ if cache_path is not None :
162241 cached = load_cached_network (cache_path )
163242 if cached is not None :
164243 return cached
165- else :
166- cache_path = None
167244
168- try :
169- graph = fetch_road_network ( center_lat , center_lon , radius_m = radius_m )
170- except Exception as exc : # noqa: BLE001 — network/Overpass failures
171- logger . warning (
172- " fetch_road_network(%.4f, %.4f) failed: %s" ,
173- center_lat ,
174- center_lon ,
175- exc ,
176- )
177- return None
245+ graph = _graph_from_shared_osm ( lats , lons , margin_m , cache_dir )
246+ if graph is None :
247+ clat , clon , radius = _trace_centroid_radius ( lats , lons , margin_m )
248+ try :
249+ graph = fetch_road_network (clat , clon , radius_m = radius )
250+ except Exception as exc : # noqa: BLE001 — network/Overpass failures
251+ logger . warning (
252+ "fetch_road_network(%.4f, %.4f) failed: %s" , clat , clon , exc
253+ )
254+ return None
178255
179256 if cache_path is not None :
180257 cache_network (graph , cache_path )
0 commit comments