diff --git a/WeatherRoutingTool/execute_routing.py b/WeatherRoutingTool/execute_routing.py index 9958b52..87c878b 100644 --- a/WeatherRoutingTool/execute_routing.py +++ b/WeatherRoutingTool/execute_routing.py @@ -13,7 +13,7 @@ def merge_figures_to_gif(path, nof_figures): graphics.merge_figs(path, nof_figures) -def execute_routing(config, ship_config): +def execute_routing(config, ship_config, csv_export_path: str = None): """ Execute route optimization based on the user-defined configuration. After a successful run the final route is saved into the configured folder. @@ -67,6 +67,8 @@ def execute_routing(config, ship_config): min_fuel_route, error_code = alg.execute_routing(boat, wt, constraint_list) # min_fuel_route.print_route() min_fuel_route.write_to_geojson(routepath / f"{min_fuel_route.route_type}.geojson") + if csv_export_path: + min_fuel_route.write_summary_to_csv(csv_export_path) if config.ROUTE_POSTPROCESSING: postprocessed_route = RoutePostprocessing(min_fuel_route, boat) diff --git a/WeatherRoutingTool/routeparams.py b/WeatherRoutingTool/routeparams.py index 064db28..18a3b5e 100644 --- a/WeatherRoutingTool/routeparams.py +++ b/WeatherRoutingTool/routeparams.py @@ -1,3 +1,4 @@ +from fileinput import filename import json from datetime import datetime, timedelta @@ -204,6 +205,55 @@ def write_to_geojson(self, filename): with open(filename, 'w') as file: json.dump(rp_dict, file, cls=NumpyArrayEncoder, indent=4) + def write_summary_to_csv(self, filename: str) -> None: + import csv + + with open(filename, mode="w", newline="") as csvfile: + writer = csv.writer(csvfile) + + writer.writerow([ + "waypoint_id", + "latitude_deg", + "longitude_deg", + "timestamp", + "speed_mps", + "engine_power_kW", + "fuel_rate_kg_per_s", + "wave_height_m", + "wind_resistance_N", + "dist_to_next_m", + "status" + ]) + for i in range(0, self.count + 2): + if i == (self.count + 1): + # Destination row with sentinel values + writer.writerow([ + i, + self.lats_per_step[i], + self.lons_per_step[i], + self.starttime_per_step[i], + -99, + -99, + -99, + -99, + -99, + -99, + -99 + ]) + else: + writer.writerow([ + i, + self.lats_per_step[i], + self.lons_per_step[i], + self.starttime_per_step[i], + self.ship_params_per_step.speed[i].value, + self.ship_params_per_step.power[i].to("kW").value, + self.ship_params_per_step.fuel_rate[i].value, + self.ship_params_per_step.wave_height[i].value, + self.ship_params_per_step.r_wind[i].value, + -99, + self.ship_params_per_step.status[i] + ]) @classmethod def from_file(cls, filename): diff --git a/cli.py b/cli.py index 472bdfd..f1d58a6 100644 --- a/cli.py +++ b/cli.py @@ -17,6 +17,13 @@ required=False, type=str, default='False') parser.add_argument('--filter-warnings', help="Filter action. ." "Defaults to 'default'.", required=False, type=str, default='default') + parser.add_argument( + '--export-csv', + help="Path for a per-waypoint CSV summary of the optimised route.", + required=False, + type=str, + default=None + ) args = parser.parse_args() if not args.file: raise RuntimeError("No config file name provided!") @@ -37,7 +44,7 @@ # Validate config with pydantic and run route optimization config = Config.assign_config(args.file) ship_config = ShipConfig.assign_config(args.file) - execute_routing(config, ship_config) + execute_routing(config, ship_config, csv_export_path=args.export_csv) # set warning filter action (https://docs.python.org/3/library/warnings.html) warnings.filterwarnings(args.filter_warnings) diff --git a/gdfDB.sqlite b/gdfDB.sqlite new file mode 100644 index 0000000..763adfa Binary files /dev/null and b/gdfDB.sqlite differ diff --git a/tests/test_route_utils.py b/tests/test_route_utils.py new file mode 100644 index 0000000..a54a702 --- /dev/null +++ b/tests/test_route_utils.py @@ -0,0 +1,40 @@ +import numpy as np +from geographiclib.geodesic import Geodesic + +from WeatherRoutingTool.algorithms.data_utils import distance, time_diffs + + +def test_distance_two_point_meridian(): + """ + Route: (0,0) -> (1,0) + Expected distance ≈ geodesic distance between the two points. + """ + route = np.array([ + [0.0, 0.0], + [1.0, 0.0] + ]) + + dists = distance(route) + + expected = Geodesic.WGS84.Inverse(0, 0, 1, 0)['s12'] + + assert np.isclose(dists[-1], expected, rtol=1e-4) + + +def test_time_diffs_constant_speed(): + """ + Same route but convert distance to time using speed. + """ + route = np.array([ + [0.0, 0.0], + [1.0, 0.0] + ]) + + speed = 10.0 # m/s + + times = time_diffs(speed, route) + + expected_dist = Geodesic.WGS84.Inverse(0, 0, 1, 0)['s12'] + expected_time = expected_dist / speed + + assert np.isclose(times[-1], expected_time, rtol=1e-4) \ No newline at end of file