From 0e379e7feb03f643a521c04dc7ae44e5f58256af Mon Sep 17 00:00:00 2001 From: Ludovic Date: Wed, 13 Nov 2024 16:06:23 +0100 Subject: [PATCH] add custom step duration feature --- README.md | 5 ++- tests/linearbestisorouter_test.py | 28 ++++++++++++++- tests/shortestpathrouter_test.py | 34 +++++++++++++++++++ weatherrouting/routers/linearbestisorouter.py | 24 ++++++++----- weatherrouting/routers/router.py | 16 ++++----- weatherrouting/routers/shortestpathrouter.py | 7 ++-- weatherrouting/routing.py | 10 ++++-- 7 files changed, 99 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index cc954e1..c303a43 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,11 @@ Calculate subsequent steps until the end track point is reached ```python while not self.routing_obj.end: - res = self.routing_obj.step() + res = self.routing_obj.step() # default step duration is set to 1 hour +# you can call a step with custom timedelta (in hour) at anytime +while not self.routing_obj.end: + res = self.routing_obj.step(timedelta=0.25) # 15min time delta ``` the step method returns a RoutingResult object with the following informations during routing calculation: ```python diff --git a/tests/linearbestisorouter_test.py b/tests/linearbestisorouter_test.py index dae072a..4e4dd47 100644 --- a/tests/linearbestisorouter_test.py +++ b/tests/linearbestisorouter_test.py @@ -196,7 +196,7 @@ def test_step(self): res = self.routing_obj.step() i += 1 - self.assertEqual(i, 9) + self.assertEqual(i, 10) self.assertEqual(not res.path, False) @@ -254,3 +254,29 @@ def test_step(self): self.assertEqual(i, 5) self.assertEqual(not res.path, False) + + +class TestRouting_custom_step(unittest.TestCase): + def setUp(self): + grib = mock_grib(2, 180, 0.1) + self.track = [(5, 38), (5.2, 38.2)] + island_route = mock_point_validity(self.track, factor=5) + self.routing_obj = weatherrouting.Routing( + LinearBestIsoRouter, + polar_bavaria38, + self.track, + grib, + datetime.datetime.fromisoformat("2021-04-02T12:00:00"), + pointValidity=island_route.point_validity, + ) + + def test_step(self): + res = None + i = 0 + + while not self.routing_obj.end: + res = self.routing_obj.step(timedelta=0.5) + i += 1 + + self.assertEqual(i, 14) + self.assertEqual(not res.path, False) diff --git a/tests/shortestpathrouter_test.py b/tests/shortestpathrouter_test.py index 268b856..b1aa7c4 100644 --- a/tests/shortestpathrouter_test.py +++ b/tests/shortestpathrouter_test.py @@ -114,3 +114,37 @@ def test_step(self): self.assertEqual(i, 4) self.assertEqual(not res.path, False) + + +class TestRouting_custom_step(unittest.TestCase): + def setUp(self): + grib = mock_grib(2, 180, 0.1) + self.track = [(5, 38), (5.2, 38.2)] + island_route = mock_point_validity(self.track) + self.routing_obj = weatherrouting.Routing( + ShortestPathRouter, + None, + self.track, + grib, + datetime.datetime.fromisoformat("2021-04-02T12:00:00"), + pointValidity=island_route.point_validity, + ) + + def test_step(self): + res = None + i = 0 + + while not self.routing_obj.end: + res = self.routing_obj.step(timedelta=0.5) + i += 1 + + self.assertEqual(i, 5) + self.assertEqual(not res.path, False) + + path_to_end = res.path + [IsoPoint(self.track[-1])] + self.assertEqual( + res.time, datetime.datetime.fromisoformat("2021-04-02 14:00:00") + ) + self.assertEqual( + len(json.dumps(weatherrouting.utils.pathAsGeojson(path_to_end))), 1783 + ) diff --git a/weatherrouting/routers/linearbestisorouter.py b/weatherrouting/routers/linearbestisorouter.py index 3ab6065..25f74fd 100644 --- a/weatherrouting/routers/linearbestisorouter.py +++ b/weatherrouting/routers/linearbestisorouter.py @@ -36,7 +36,7 @@ class LinearBestIsoRouter(Router): ) } - def _route(self, lastlog, time, start, end, isoF): # noqa: C901 + def _route(self, lastlog, time, timedelta, start, end, isoF): # noqa: C901 position = start path = [] @@ -50,13 +50,21 @@ def generate_path(p): path = path[::-1] position = path[-1].pos - if self.grib.getWindAt(time + datetime.timedelta(hours=1), end[0], end[1]): + if self.grib.getWindAt( + time + datetime.timedelta(hours=timedelta), end[0], end[1] + ): if lastlog is not None and len(lastlog.isochrones) > 0: - isoc = isoF(time + datetime.timedelta(hours=1), lastlog.isochrones, end) + isoc = isoF( + time + datetime.timedelta(hours=timedelta), + timedelta, + lastlog.isochrones, + end, + ) else: nwdist = utils.pointDistance(end[0], end[1], start[0], start[1]) isoc = isoF( - time + datetime.timedelta(hours=1), + time + datetime.timedelta(hours=timedelta), + timedelta, [[IsoPoint((start[0], start[1]), time=time, nextWPDist=nwdist)]], end, ) @@ -66,7 +74,7 @@ def generate_path(p): for p in isoc[-1]: distance_to_end_point = p.pointDistance(end) if distance_to_end_point < self.getParamValue("minIncrease"): - # (twd,tws) = self.grib.getWindAt (time + datetime.timedelta(hours=1), + # (twd,tws) = self.grib.getWindAt (time + datetime.timedelta(hours=timedelta), # p.pos[0], p.pos[1]) maxReachDistance = utils.maxReachDistance(p.pos, p.speed) if distance_to_end_point < abs(maxReachDistance * 1.1): @@ -94,11 +102,11 @@ def generate_path(p): generate_path(minP) return RoutingResult( - time=time + datetime.timedelta(hours=1), + time=time + datetime.timedelta(hours=timedelta), path=path, position=position, isochrones=isoc, ) - def route(self, lastlog, t, start, end) -> RoutingResult: - return self._route(lastlog, t, start, end, self.calculateIsochrones) + def route(self, lastlog, t, timedelta, start, end) -> RoutingResult: + return self._route(lastlog, t, timedelta, start, end, self.calculateIsochrones) diff --git a/weatherrouting/routers/router.py b/weatherrouting/routers/router.py index ce89b10..6bee4a7 100644 --- a/weatherrouting/routers/router.py +++ b/weatherrouting/routers/router.py @@ -153,7 +153,7 @@ def setParamValue(self, code, value): def getParamValue(self, code): return self.PARAMS[code].value - def calculateShortestPathIsochrones(self, fixedSpeed, t, isocrone, nextwp): + def calculateShortestPathIsochrones(self, fixedSpeed, t, dt, isocrone, nextwp): """Calculates isochrones based on shortest path at fixed speed (motoring); the speed considers reductions / increases derived from leeway""" @@ -167,9 +167,9 @@ def pointF(p, tws, twa, dt, brg): speed, ) - return self._calculateIsochronesConcurrent(t, isocrone, nextwp, pointF) + return self._calculateIsochronesConcurrent(t, dt, isocrone, nextwp, pointF) - def calculateIsochrones(self, t, isocrone, nextwp): + def calculateIsochrones(self, t, dt, isocrone, nextwp): """Calculate isochrones depending on routageSpeed from polar""" def pointF(p, tws, twa, dt, brg): @@ -184,7 +184,7 @@ def pointF(p, tws, twa, dt, brg): # math.degrees(brg), 'rpd', rpd) return rpd - return self._calculateIsochronesConcurrent(t, isocrone, nextwp, pointF) + return self._calculateIsochronesConcurrent(t, dt, isocrone, nextwp, pointF) def _filterValidity(self, isonew, last): # noqa: C901 def validPoint(a): @@ -232,9 +232,8 @@ def validLine(a): return isonew - def _calculateIsochronesConcurrent(self, t, isocrone, nextwp, pointF): + def _calculateIsochronesConcurrent(self, t, dt, isocrone, nextwp, pointF): """Calcuates isochrones based on pointF next point calculation""" - dt = 1.0 / 60.0 * 60.0 last = isocrone[-1] newisopoints = [] @@ -313,9 +312,8 @@ def cisopoints(i): return isocrone - def _calculateIsochrones(self, t, isocrone, nextwp, pointF): + def _calculateIsochrones(self, t, dt, isocrone, nextwp, pointF): """Calcuates isochrones based on pointF next point calculation""" - dt = 1.0 / 60.0 * 60.0 last = isocrone[-1] newisopoints = [] @@ -390,5 +388,5 @@ def calculateVMG(self, speed, angle, start, end) -> float: at current speed / angle""" return speed * math.cos(angle) - def route(self, lastlog, t, start, end) -> RoutingResult: + def route(self, lastlog, t, timedelta, start, end) -> RoutingResult: raise Exception("Not implemented") diff --git a/weatherrouting/routers/shortestpathrouter.py b/weatherrouting/routers/shortestpathrouter.py index 91f621f..eb9701a 100644 --- a/weatherrouting/routers/shortestpathrouter.py +++ b/weatherrouting/routers/shortestpathrouter.py @@ -43,13 +43,14 @@ class ShortestPathRouter(LinearBestIsoRouter): ), } - def route(self, lastlog, t, start, end) -> RoutingResult: + def route(self, lastlog, t, timedelta, start, end) -> RoutingResult: return self._route( lastlog, t, + timedelta, start, end, - lambda t, isoc, end: self.calculateShortestPathIsochrones( - self.getParamValue("fixedSpeed"), t, isoc, end + lambda t, dt, isoc, end: self.calculateShortestPathIsochrones( + self.getParamValue("fixedSpeed"), t, timedelta, isoc, end ), ) diff --git a/weatherrouting/routing.py b/weatherrouting/routing.py index 7e6ad6c..eea21b6 100644 --- a/weatherrouting/routing.py +++ b/weatherrouting/routing.py @@ -100,7 +100,7 @@ def __init__( self.wp = 1 self.position = self.track[0] - def step(self) -> RoutingResult: + def step(self, timedelta=1) -> RoutingResult: """Execute a single routing step""" self.steps += 1 @@ -113,10 +113,14 @@ def step(self) -> RoutingResult: nextwp = self.track[self.wp] if self._startingNewPoint or len(self.log) == 0: - res = self.algorithm.route(None, self.time, self.position, nextwp) + res = self.algorithm.route( + None, self.time, timedelta, self.position, nextwp + ) self._startingNewPoint = False else: - res = self.algorithm.route(self.log[-1], self.time, self.position, nextwp) + res = self.algorithm.route( + self.log[-1], self.time, timedelta, self.position, nextwp + ) # self.time += 0.2 ff = 100 / len(self.track)