1+ import dataclasses
12import json
3+ from collections .abc import Callable
24
35import folium
6+ import jsonpath_ng
47import plotly .graph_objects as go
58from folium import plugins
69
1417# ==================== Pre-configured plot profiles
1518
1619
20+ @dataclasses .dataclass
1721class RoutePlotProfile :
1822 """
1923 Pre-configured plot profiles for routes.
2024 """
2125
22- def __init__ (
23- self ,
24- jpath_route : str = "" ,
25- jpath_pos : str = "" ,
26- jpath_x : str = "" ,
27- jpath_y : str = "" ,
28- jpath_unassigned : str = "" ,
29- jpath_unassigned_x : str = "" ,
30- jpath_unassigned_y : str = "" ,
31- ):
32- self .jpath_route = jpath_route
33- self .jpath_pos = jpath_pos
34- self .jpath_x = jpath_x
35- self .jpath_y = jpath_y
36- self .jpath_unassigned = jpath_unassigned
37- self .jpath_unassigned_x = jpath_unassigned_x
38- self .jpath_unassigned_y = jpath_unassigned_y
26+ jpath_route : str = ""
27+ jpath_pos : str = ""
28+ jpath_x : str = ""
29+ jpath_y : str = ""
30+ jpath_unassigned : str = ""
31+ jpath_unassigned_x : str = ""
32+ jpath_unassigned_y : str = ""
3933
4034 def __str__ (self ):
4135 return (
@@ -50,6 +44,28 @@ def __str__(self):
5044 )
5145
5246
47+ @dataclasses .dataclass
48+ class MultiRoutePlotProfile :
49+ """
50+ Multiple pre-configured plot profiles selected via given input tests.
51+ """
52+
53+ profiles : list [tuple [Callable [[dict , dict ], bool ], RoutePlotProfile ]] = dataclasses .field (default_factory = list )
54+ fail_message : str = "No suitable profile found for plotting."
55+
56+ def unwrap (self , content_route : dict , content_pos : dict ) -> RoutePlotProfile :
57+ """
58+ Tests the given data against the profiles and returns the first matching profile.
59+ """
60+ for test , profile in self .profiles :
61+ if test (content_route , content_pos ):
62+ return profile
63+ raise Exception (self .fail_message )
64+
65+ def __str__ (self ):
66+ return "MultiRoutePlotProfile(" + ", " .join ([f"{ p [0 ]} : { p [1 ]} " for p in self .profiles ]) + ")"
67+
68+
5369# ==================== Route mode argument definition
5470
5571
@@ -219,21 +235,26 @@ def arguments(parser):
219235
220236def parse (
221237 input_route : str ,
222- jpath_route : str ,
223- jpath_unassigned : str ,
224- jpath_unassigned_x : str ,
225- jpath_unassigned_y : str ,
226238 input_pos : str ,
227- jpath_pos : str ,
228- jpath_x : str ,
229- jpath_y : str ,
239+ profile : MultiRoutePlotProfile | RoutePlotProfile ,
230240) -> tuple [list [list [types .Position ]], list [list [types .Position ]]]:
231241 """
232242 Parses the route data from the file(s).
233243 """
234244 # Load json data
235245 content_route , content_pos = common .load_data (input_route , input_pos )
236246
247+ # Dynamically set profile, if given
248+ if isinstance (profile , MultiRoutePlotProfile ):
249+ profile = profile .unwrap (json .loads (content_route ), json .loads (content_pos ))
250+ jpath_route = profile .jpath_route
251+ jpath_pos = profile .jpath_pos
252+ jpath_x = profile .jpath_x
253+ jpath_y = profile .jpath_y
254+ jpath_unassigned = profile .jpath_unassigned
255+ jpath_unassigned_x = profile .jpath_unassigned_x
256+ jpath_unassigned_y = profile .jpath_unassigned_y
257+
237258 # Extract routes
238259 points = common .extract_position_groups (
239260 content_route ,
@@ -553,17 +574,7 @@ def plot(
553574 profile = nextroute_profile ()
554575
555576 # Parse data
556- points , unassigned = parse (
557- input_route ,
558- profile .jpath_route ,
559- profile .jpath_unassigned ,
560- profile .jpath_unassigned_x ,
561- profile .jpath_unassigned_y ,
562- input_pos ,
563- profile .jpath_pos ,
564- profile .jpath_x ,
565- profile .jpath_y ,
566- )
577+ points , unassigned = parse (input_route , input_pos , profile )
567578
568579 # Quit on no points
569580 if len (points ) <= 0 :
@@ -691,13 +702,37 @@ def nextroute_profile() -> RoutePlotProfile:
691702 """
692703 Returns the nextroute profile.
693704 """
694- return RoutePlotProfile (
695- jpath_route = "solutions[-1].vehicles[*].route" ,
696- jpath_x = "stop.location.lon" ,
697- jpath_y = "stop.location.lat" ,
698- jpath_unassigned = "solutions[-1].unplanned[*]" ,
699- jpath_unassigned_x = "location.lon" ,
700- jpath_unassigned_y = "location.lat" ,
705+ base_paths = [
706+ "solutions[-1]" ,
707+ "solution" ,
708+ "output.solutions[-1]" ,
709+ "output.solution" ,
710+ ]
711+
712+ def make_path_exists (path ):
713+ def path_exists (content_route , path ):
714+ matches = jsonpath_ng .parse (path ).find (content_route )
715+ return len (list (matches )) > 0
716+
717+ return lambda content_route , _ : path_exists (content_route , path )
718+
719+ return MultiRoutePlotProfile (
720+ [
721+ (
722+ make_path_exists (p ),
723+ RoutePlotProfile (
724+ jpath_route = f"{ p } .vehicles[*].route" ,
725+ jpath_x = "stop.location.lon" ,
726+ jpath_y = "stop.location.lat" ,
727+ jpath_unassigned = f"{ p } .unplanned[*]" ,
728+ jpath_unassigned_x = "location.lon" ,
729+ jpath_unassigned_y = "location.lat" ,
730+ ),
731+ )
732+ for p in base_paths
733+ ],
734+ "Input data does not match any known profile for nextroute plotting. Routes are expected at one of:\n "
735+ + "\n " .join (f"{ p } .vehicles[*].route" for p in base_paths ),
701736 )
702737
703738
0 commit comments