From 2102eb8346b0d18f5d4247a240c53aece084a46c Mon Sep 17 00:00:00 2001 From: RitikaSen27 Date: Fri, 13 Mar 2026 17:11:06 +0530 Subject: [PATCH 1/3] Add CSV export for route summary --- WeatherRoutingTool/routeparams.py | 50 +++++++++++++++++++++++++++++++ cli.py | 9 +++++- 2 files changed, 58 insertions(+), 1 deletion(-) 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) From eb13e4d13abf78c78fa27cfe10c441d781f545f8 Mon Sep 17 00:00:00 2001 From: RitikaSen27 Date: Sat, 14 Mar 2026 00:42:46 +0530 Subject: [PATCH 2/3] Add unit tests for distance() and time_diffs() --- tests/test_route_utils.py | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/test_route_utils.py 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 From cd718aad3f2e87995e677770f6b3e77e776c28c9 Mon Sep 17 00:00:00 2001 From: RitikaSen27 Date: Sat, 14 Mar 2026 00:50:19 +0530 Subject: [PATCH 3/3] Add tests for distance and time_diffs --- WeatherRoutingTool/execute_routing.py | 4 +++- gdfDB.sqlite | Bin 0 -> 36864 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 gdfDB.sqlite 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/gdfDB.sqlite b/gdfDB.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..763adfa202c7f874de141e795e95f3e798320c08 GIT binary patch literal 36864 zcmeI52~-nj9>8Y^kOWAAs~FIhj)){2xqf#@ChOvfh zYJ7HTMnZNzm1fAN5^{~>QZu1&h9T1!L+QpE#$_0c+4(vuF@0PjBsk3_4jnZiAv-B0 zAv@9~)5x_t9qBUgeJC~CET(d{w5Ei$Iov?u-XFKTAKR`?6jIoO?IZ(^5};t)+2$trd4n!ovVVBYX=V|=LYd(En(O)#%ZrH6|tq2Wj2Q= zd%2C)GZ)S)E)<*ix`Yvl=ZG*U$nq#7okL>&!(3-gIU~6e^NLv3ZZTP`WsJ>XWo;h0 zo#hGz9@ZsZ=!-p}B2_SMZ%sX+xuH3uX@VaZ&;S}h184vZpaC?12G9T+Km%w14WNPl zsDb_g^8SyLB+*;EHz(a`^YVH=EpdRqT*qg+-6)ebk#U;@`^kg&O}HCdQVw4@SSi;& zu7sNTn?w(@pbU5Y50m~F&;S}h184vZpaC?12G9T+Km%w14WNOau>n7BYyd*mkGVcDB-H zF&DCxPUwN}qxAruf`=Z^K2Unn5AQ*dopm^@wi2kp`_XEE-oqH|HM-h38PqVYhkYol zWZ``RX1m2|GneXWf8_;bllZcMd82lnJU@`G`z`&}$-4CK*n3mWc4*bEt9_1yQY>Fe zI1>BNLOcXYRhgZP9Tos^`@57anr1421RcDlLR?hv@L=H6YgL-_u>SwBm*%wQu;xq6 zCqhN|jRw#F8bAYR01co4G=K)s02)98XaEiTqzowhWI-gi;NMrNlxazRfj{@we&Nmi z{sFT7q?3ZJEK1172xP^ zFHU(Ic*m*A?Q?oMhK!E?1bEDu z%}Kj~kIXvyYT$c%ddbC+@9YJ>b6IgC33#C8SkOM;f9OulYXY9_`^((-fyb~R5z5_q zdP2l+R-XYrksoG`1Ws}dW?F$Oq7>~}zy|`Rwf+J4ctXyhI^ek1H;-xuZr${`dOz^} z6_z6$_xmh#<3-?s>y~Y6174r9?Z#KYYZTL;=gJ?w`~FJx9zFeOZS$Xx1IJ`E-iri& zb@EerM7fBZ%Sa7@Wp^)0}!l%LtD1MXEEUf&E%8!NvU1Uzs5wU4&~7Y4DGP~gKS z3XU}aOZ9KaI~BGWIKuCgc?LNCl3KM0cur!S-xI)>f^z%S1An2oQv5aSKTT|(;-$cz z$4d_;!Ck$4dsjT^QTK^c5!9d>@eU_4CEi~E-Qn=)bI(yF}8Bk4(|@Lku>X3$4w|Mm8jcSq8d z-sSIBAJ6!XY4=Y1MtJC;(hClk&`JER;r{P{I@zpW44WN7FDVNtUlp82*E*N>8K7-81`1))`LAhGs> zxvfw7(H~WR9ua#hm7f3m89^aql(ca|+nwZ_somGw@$>@s{6DFAlYoC1&;S}h184vZ zpaC?12G9T+Km%w14WNPlg#j~M^1GfTWzaUaz{3YVQ*F@`2hJHlFW&fEeAN6DTH?{8 zsjAY|sr2&P&?`Ge1=61ExxK{P^}izEHaGuoRNq&xRmZ7XR7Oa^fCkV28bAYR01co4 zG=K)s02=teH&6g;-e(Jw{oI9>5T2xkI9QCM#5l|&j_Q71RhS?yfOYX*T99fE_o(qP zk@aF6*pn7qBs?bLdRiC-cUdTeA+!)nZH0*})h)Fp^+6hCptxN}`EKW=g+~+$VC{XE zQO1fjNiD^QEVU@bG5=zeR&I5F0j$mM(n5q-qe6@+F_y+Gt%VQkNef2q;ed`gb%GBa tH3%$y2X|j%$DFzmR`qwO)wACnt#s?UQ?2gv|3a^%JuCIGqm>@k`cFe+RBHeL literal 0 HcmV?d00001