From 689fe65e7de446aea4bdbc73e061b1385d895063 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Mon, 28 Oct 2024 17:03:13 +0100 Subject: [PATCH 1/5] Making nextplot compatible with more forms of nextroute output --- nextplot/route.py | 119 ++++++++++++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 42 deletions(-) diff --git a/nextplot/route.py b/nextplot/route.py index 63bd80d..6c0e645 100644 --- a/nextplot/route.py +++ b/nextplot/route.py @@ -1,6 +1,9 @@ +import dataclasses import json +from collections.abc import Callable import folium +import jsonpath_ng import plotly.graph_objects as go from folium import plugins @@ -14,28 +17,19 @@ # ==================== Pre-configured plot profiles +@dataclasses.dataclass class RoutePlotProfile: """ Pre-configured plot profiles for routes. """ - def __init__( - self, - jpath_route: str = "", - jpath_pos: str = "", - jpath_x: str = "", - jpath_y: str = "", - jpath_unassigned: str = "", - jpath_unassigned_x: str = "", - jpath_unassigned_y: str = "", - ): - self.jpath_route = jpath_route - self.jpath_pos = jpath_pos - self.jpath_x = jpath_x - self.jpath_y = jpath_y - self.jpath_unassigned = jpath_unassigned - self.jpath_unassigned_x = jpath_unassigned_x - self.jpath_unassigned_y = jpath_unassigned_y + jpath_route: str = "" + jpath_pos: str = "" + jpath_x: str = "" + jpath_y: str = "" + jpath_unassigned: str = "" + jpath_unassigned_x: str = "" + jpath_unassigned_y: str = "" def __str__(self): return ( @@ -50,6 +44,28 @@ def __str__(self): ) +@dataclasses.dataclass +class MultiRoutePlotProfile: + """ + Multiple pre-configured plot profiles selected via given input tests. + """ + + profiles: list[tuple[Callable[[dict, dict], bool], RoutePlotProfile]] = dataclasses.field(default_factory=list) + fail_message: str = "No suitable profile found for plotting." + + def unwrap(self, content_route: dict, content_pos: dict) -> RoutePlotProfile: + """ + Tests the given data against the profiles and returns the first matching profile. + """ + for test, profile in self.profiles: + if test(content_route, content_pos): + return profile + raise Exception(self.fail_message) + + def __str__(self): + return "MultiRoutePlotProfile(" + ", ".join([f"{p[0]}: {p[1]}" for p in self.profiles]) + ")" + + # ==================== Route mode argument definition @@ -219,14 +235,8 @@ def arguments(parser): def parse( input_route: str, - jpath_route: str, - jpath_unassigned: str, - jpath_unassigned_x: str, - jpath_unassigned_y: str, input_pos: str, - jpath_pos: str, - jpath_x: str, - jpath_y: str, + profile: MultiRoutePlotProfile | RoutePlotProfile, ) -> tuple[list[list[types.Position]], list[list[types.Position]]]: """ Parses the route data from the file(s). @@ -234,6 +244,17 @@ def parse( # Load json data content_route, content_pos = common.load_data(input_route, input_pos) + # Dynamically set profile, if given + if isinstance(profile, MultiRoutePlotProfile): + profile = profile.unwrap(json.loads(content_route), json.loads(content_pos)) + jpath_route = profile.jpath_route + jpath_pos = profile.jpath_pos + jpath_x = profile.jpath_x + jpath_y = profile.jpath_y + jpath_unassigned = profile.jpath_unassigned + jpath_unassigned_x = profile.jpath_unassigned_x + jpath_unassigned_y = profile.jpath_unassigned_y + # Extract routes points = common.extract_position_groups( content_route, @@ -553,17 +574,7 @@ def plot( profile = nextroute_profile() # Parse data - points, unassigned = parse( - input_route, - profile.jpath_route, - profile.jpath_unassigned, - profile.jpath_unassigned_x, - profile.jpath_unassigned_y, - input_pos, - profile.jpath_pos, - profile.jpath_x, - profile.jpath_y, - ) + points, unassigned = parse(input_route, input_pos, profile) # Quit on no points if len(points) <= 0: @@ -691,13 +702,37 @@ def nextroute_profile() -> RoutePlotProfile: """ Returns the nextroute profile. """ - return RoutePlotProfile( - jpath_route="solutions[-1].vehicles[*].route", - jpath_x="stop.location.lon", - jpath_y="stop.location.lat", - jpath_unassigned="solutions[-1].unplanned[*]", - jpath_unassigned_x="location.lon", - jpath_unassigned_y="location.lat", + base_paths = [ + "solutions[-1]", + "solution", + "output.solutions[-1]", + "output.solution", + ] + + def make_path_exists(path): + def path_exists(content_route, path): + matches = jsonpath_ng.parse(path).find(content_route) + return len(list(matches)) > 0 + + return lambda content_route, _: path_exists(content_route, path) + + return MultiRoutePlotProfile( + [ + ( + make_path_exists(p), + RoutePlotProfile( + jpath_route=f"{p}.vehicles[*].route", + jpath_x="stop.location.lon", + jpath_y="stop.location.lat", + jpath_unassigned=f"{p}.unplanned[*]", + jpath_unassigned_x="location.lon", + jpath_unassigned_y="location.lat", + ), + ) + for p in base_paths + ], + "Input data does not match any known profile for nextroute plotting. Routes are expected at one of:\n" + + "\n".join(f"{p}.vehicles[*].route" for p in base_paths), ) From af67550a93895a78491f86f5164bcf827d140a90 Mon Sep 17 00:00:00 2001 From: nextmv-bot Date: Thu, 31 Oct 2024 16:04:21 +0000 Subject: [PATCH 2/5] Bump version to v0.1.7 --- nextplot/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextplot/__about__.py b/nextplot/__about__.py index 229160c..c7f5b95 100644 --- a/nextplot/__about__.py +++ b/nextplot/__about__.py @@ -1 +1 @@ -__version__ = "v0.1.6" +__version__ = "v0.1.7" From 78516ac6d1cb6647a1e4de2bd0a5a02535e07b84 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Thu, 23 Jan 2025 21:19:12 +0100 Subject: [PATCH 3/5] Fix jpath based nested geojson plotting --- nextplot/geojson.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nextplot/geojson.py b/nextplot/geojson.py index 113781f..371155c 100644 --- a/nextplot/geojson.py +++ b/nextplot/geojson.py @@ -104,14 +104,15 @@ def parse( Parses the geojson data object(s) from the file(s). """ # Load json data - content_geojson, _ = common.load_data(input_geojson, "") + content, _ = common.load_data(input_geojson, "") + json_content = json.loads(content) # Extract geojsons if jpath_geojson: expression = jsonpath_ng.parse(jpath_geojson) - geojsons = [json.loads(match.value) for match in expression.find(content_geojson)] + geojsons = [match.value for match in expression.find(json_content)] else: - geojsons = [json.loads(content_geojson)] + geojsons = [json_content] return geojsons From edb03251ca09a903152ea077d4ad5ba09f0a450b Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 24 Jan 2025 01:37:54 +0100 Subject: [PATCH 4/5] Adding nested geojson test case --- tests/test_cli.py | 18 ++ tests/testdata/geojson-nested-data.json | 39 +++ .../testdata/geojson-nested-data.json.golden | 0 .../geojson-nested-data.json.map.html.golden | 226 ++++++++++++++++++ 4 files changed, 283 insertions(+) create mode 100644 tests/testdata/geojson-nested-data.json create mode 100644 tests/testdata/geojson-nested-data.json.golden create mode 100644 tests/testdata/geojson-nested-data.json.map.html.golden diff --git a/tests/test_cli.py b/tests/test_cli.py index 621d2ad..93d522f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -480,6 +480,23 @@ def test_map_plot_cli_geojson(): _run_geojson_test(test) +def test_map_plot_cli_geojson_nested(): + test = GeoJSONTest( + "geojson", + [ + "geojson", + "--input_geojson", + os.path.join(DATA_DIR, "geojson-nested-data.json"), + "--jpath_geojson", + "assets[*].content", + ], + os.path.join(OUTPUT_DIR, "geojson-nested-data.json.map.html"), + os.path.join(DATA_DIR, "geojson-nested-data.json.golden"), + os.path.join(DATA_DIR, "geojson-nested-data.json.map.html.golden"), + ) + _run_geojson_test(test) + + def test_progression_plot_cli_fleet_cloud_comparison(): test = ProgressionTest( "fleet-cloud-comparison", @@ -507,5 +524,6 @@ def test_progression_plot_cli_fleet_cloud_comparison(): test_map_plot_cli_paris_point() test_map_plot_cli_paris_route_indexed() test_map_plot_cli_geojson() + test_map_plot_cli_geojson_nested() test_progression_plot_cli_fleet_cloud_comparison() print("Everything passed") diff --git a/tests/testdata/geojson-nested-data.json b/tests/testdata/geojson-nested-data.json new file mode 100644 index 0000000..dc234b9 --- /dev/null +++ b/tests/testdata/geojson-nested-data.json @@ -0,0 +1,39 @@ +{ + "assets": [ + { + "type": "geojson", + "content": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-74.076, 4.598] + }, + "properties": {} + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-75.1649, 39.9525] + }, + "properties": {} + } + ] + } + }, + { + "type": "geojson", + "content": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [7.6281, 51.962] + }, + "properties": {} + } + } + ] +} diff --git a/tests/testdata/geojson-nested-data.json.golden b/tests/testdata/geojson-nested-data.json.golden new file mode 100644 index 0000000..e69de29 diff --git a/tests/testdata/geojson-nested-data.json.map.html.golden b/tests/testdata/geojson-nested-data.json.map.html.golden new file mode 100644 index 0000000..74b6708 --- /dev/null +++ b/tests/testdata/geojson-nested-data.json.map.html.golden @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file From c2356734796cce0c95d8d006fabeb7f1ade33502 Mon Sep 17 00:00:00 2001 From: nextmv-bot Date: Wed, 29 Jan 2025 21:45:47 +0000 Subject: [PATCH 5/5] Bump version to v0.1.8 --- nextplot/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextplot/__about__.py b/nextplot/__about__.py index c7f5b95..b67ea67 100644 --- a/nextplot/__about__.py +++ b/nextplot/__about__.py @@ -1 +1 @@ -__version__ = "v0.1.7" +__version__ = "v0.1.8"