def test_createPath_advanced(self):
        # Create tacking path (requires tacking because of wind is 45 degrees, goal is 45 degrees)
        start = latlon(0, 0)
        goal = latlon(0.2, 0.2)
        state = sbot.BoatState(globalWaypoint=goal,
                               position=start,
                               globalWindDirectionDegrees=45,
                               globalWindSpeedKmph=10,
                               AISData=AISMsg(),
                               headingDegrees=0,
                               speedKmph=0)
        path = createPath(state,
                          runtimeSeconds=0.5,
                          numRuns=2,
                          maxAllowableRuntimeSeconds=1)
        latlons = path.getLatlons()

        # Check that first state matches the setup start
        startWaypoint = latlons[0]
        self.assertAlmostEqual(start.lat, startWaypoint.lat, places=2)
        self.assertAlmostEqual(start.lon, startWaypoint.lon, places=2)

        # Check that the path reaches the goal
        self.assertTrue(path.reachesGoalLatlon(goal))

        # Check that total path length is reasonable
        maxAcceptablePathLength = 2 * distance((start.lat, start.lon),
                                               (goal.lat, goal.lon)).kilometers
        self.assertLessEqual(path.getLength(), maxAcceptablePathLength)

        # Check that path cost is acceptable
        self.assertFalse(utils.pathCostThresholdExceeded(path))
    def createPathWithGivenPositionsXY(self, xys):
        def createState(spaceInformation, xy):
            state = spaceInformation.allocState()
            state.setXY(xy[0], xy[1])
            return state

        # Create dummy path and change private variables directly
        state = sbot.BoatState(globalWaypoint=latlon(0.2, 0.2),
                               position=latlon(0, 0),
                               globalWindDirectionDegrees=90,
                               globalWindSpeedKmph=10,
                               AISData=AISMsg(),
                               headingDegrees=0,
                               speedKmph=0)
        path = createPath(state, runtimeSeconds=1, numRuns=2)
        spaceInformation = path.getSpaceInformation()

        # Create acutal path
        actualPath = og.PathGeometric(spaceInformation)
        for xy in xys:
            actualPath.append(createState(spaceInformation, xy))
        path.getOMPLPath()._solutionPath = actualPath
        path._latlons = path._getLatlonsFromOMPLPath(path._omplPath)

        return path
    def test_indexOfObstacleOnPath(self):
        '''To visualize the obstacleOnPath check, go to updated_geometric_planner.py
           and uncomment the plotting in indexOfObstacleOnPath()'''
        # Create simple path from latlon(0,0) to latlon(0.2,0.2)
        state = sbot.BoatState(globalWaypoint=latlon(0.2, 0.2),
                               position=latlon(0, 0),
                               globalWindDirectionDegrees=90,
                               globalWindSpeedKmph=10,
                               AISData=AISMsg(),
                               headingDegrees=0,
                               speedKmph=0)
        path = createPath(state, runtimeSeconds=1, numRuns=2)
        omplPath = path.getOMPLPath()
        '''
        Test the following:
        Let positionXY be between index 2 and 3 in the omplPath
        Thus, nextLocalWaypointIndex = 3
        Let numLookAheadWaypoints = 2
        Let B = sailbot. X = obstacle. Numbers = waypoint indices in omplPath.
        Scenario 0: 0     1     2  B X3     4     5     6     7  => Returns 3  (1 index forward from B)
        Scenario 1: 0     1     2  B  3  X  4     5     6     7  => Returns 4  (2 indices forward from B)
        Scenario 2: 0     1     2  B  3     4  X  5     6     7  => Returns -1 (Nothing btwn B->3->4, look ahead 2)
        Scenario 3: 0     1     2X B  3     4     5     6     7  => Returns -1 (Nothing btwn B->3->4, not look behind)
        Scenario 4: 0     1     2 XBX 3     4     5     6     7  => Returns 0 (B invalidState)
        '''
        # Get waypoints setup
        waypoint2 = path.getOMPLPath().getSolutionPath().getState(2)
        waypoint3 = path.getOMPLPath().getSolutionPath().getState(3)
        waypoint4 = path.getOMPLPath().getSolutionPath().getState(4)
        waypoint5 = path.getOMPLPath().getSolutionPath().getState(5)

        # Get in between positions
        between2and3XY = [
            waypoint2.getX() + (waypoint3.getX() - waypoint2.getX()) / 2,
            waypoint2.getY() + (waypoint3.getY() - waypoint2.getY()) / 2
        ]
        closeTo2XY = [
            waypoint2.getX() + (waypoint3.getX() - waypoint2.getX()) / 5,
            waypoint2.getY() + (waypoint3.getY() - waypoint2.getY()) / 5
        ]
        closeTo3XY = [
            waypoint2.getX() + (waypoint3.getX() - waypoint2.getX()) * 4 / 5,
            waypoint2.getY() + (waypoint3.getY() - waypoint2.getY()) * 4 / 5
        ]
        between3and4XY = [
            waypoint3.getX() + (waypoint4.getX() - waypoint3.getX()) / 2,
            waypoint3.getY() + (waypoint4.getY() - waypoint3.getY()) / 2
        ]
        between4and5XY = [
            waypoint4.getX() + (waypoint5.getX() - waypoint4.getX()) / 2,
            waypoint4.getY() + (waypoint5.getY() - waypoint4.getY()) / 2
        ]

        # Setup values from tests
        sailbotPositionXY = between2and3XY
        nextLocalWaypointIndex = 3
        numLookAheadWaypoints = 2

        # Scenario 0
        closeTo3Latlon = utils.XYToLatlon(closeTo3XY,
                                          path.getReferenceLatlon())
        state.AISData = AISMsg([
            AISShip(ID=0,
                    lat=closeTo3Latlon.lat,
                    lon=closeTo3Latlon.lon,
                    headingDegrees=utils.HEADING_EAST,
                    speedKmph=0)
        ])
        omplPath.updateObstacles(state)
        self.assertEqual(
            3,
            indexOfObstacleOnPath(
                positionXY=sailbotPositionXY,
                nextLocalWaypointIndex=nextLocalWaypointIndex,
                numLookAheadWaypoints=numLookAheadWaypoints,
                omplPath=path.getOMPLPath()))

        # Scenario 1
        between3and4Latlon = utils.XYToLatlon(between3and4XY,
                                              path.getReferenceLatlon())
        state.AISData = AISMsg([
            AISShip(ID=0,
                    lat=between3and4Latlon.lat,
                    lon=between3and4Latlon.lon,
                    headingDegrees=utils.HEADING_EAST,
                    speedKmph=0)
        ])
        omplPath.updateObstacles(state)
        self.assertEqual(
            4,
            indexOfObstacleOnPath(
                positionXY=sailbotPositionXY,
                nextLocalWaypointIndex=nextLocalWaypointIndex,
                numLookAheadWaypoints=numLookAheadWaypoints,
                omplPath=path.getOMPLPath()))

        # Scenario 2
        between4and5Latlon = utils.XYToLatlon(between4and5XY,
                                              path.getReferenceLatlon())
        state.AISData = AISMsg([
            AISShip(ID=0,
                    lat=between4and5Latlon.lat,
                    lon=between4and5Latlon.lon,
                    headingDegrees=utils.HEADING_EAST,
                    speedKmph=0)
        ])
        omplPath.updateObstacles(state)
        self.assertEqual(
            -1,
            indexOfObstacleOnPath(
                positionXY=sailbotPositionXY,
                nextLocalWaypointIndex=nextLocalWaypointIndex,
                numLookAheadWaypoints=numLookAheadWaypoints,
                omplPath=path.getOMPLPath()))

        # Scenario 3
        closeTo2Latlon = utils.XYToLatlon(closeTo2XY,
                                          path.getReferenceLatlon())
        state.AISData = AISMsg([
            AISShip(ID=0,
                    lat=closeTo2Latlon.lat,
                    lon=closeTo2Latlon.lon,
                    headingDegrees=utils.HEADING_EAST,
                    speedKmph=0)
        ])
        omplPath.updateObstacles(state)
        self.assertEqual(
            -1,
            indexOfObstacleOnPath(
                positionXY=sailbotPositionXY,
                nextLocalWaypointIndex=nextLocalWaypointIndex,
                numLookAheadWaypoints=numLookAheadWaypoints,
                omplPath=path.getOMPLPath()))

        # Scenario 4
        between2and3Latlon = utils.XYToLatlon(between2and3XY,
                                              path.getReferenceLatlon())
        state.AISData = AISMsg([
            AISShip(ID=0,
                    lat=between2and3Latlon.lat,
                    lon=between2and3Latlon.lon,
                    headingDegrees=utils.HEADING_EAST,
                    speedKmph=0)
        ])
        omplPath.updateObstacles(state)
        self.assertEqual(
            0,
            indexOfObstacleOnPath(
                positionXY=sailbotPositionXY,
                nextLocalWaypointIndex=nextLocalWaypointIndex,
                numLookAheadWaypoints=numLookAheadWaypoints,
                omplPath=path.getOMPLPath()))
    def test_obstacleOnPath(self):
        '''To visualize the obstacleOnPath check, go to updated_geometric_planner.py
           and uncomment the plotting in indexOfObstacleOnPath()'''
        # Create simple path from latlon(0,0) to latlon(0.2,0.2)
        state = sbot.BoatState(globalWaypoint=latlon(0.2, 0.2),
                               position=latlon(0, 0),
                               globalWindDirectionDegrees=90,
                               globalWindSpeedKmph=10,
                               AISData=AISMsg(),
                               headingDegrees=0,
                               speedKmph=0)
        path = createPath(state, runtimeSeconds=1, numRuns=2)

        waypoint0 = path.getOMPLPath().getSolutionPath().getState(0)
        waypoint1 = path.getOMPLPath().getSolutionPath().getState(1)

        # No obstacles at all
        self.assertFalse(path.obstacleOnPath(state))

        # Put obstacle on path between waypoints 0 and 1, going east quickly
        between0and1XY = [
            waypoint0.getX() + (waypoint1.getX() - waypoint0.getX()) / 2,
            waypoint0.getY() + (waypoint1.getY() - waypoint0.getY()) / 2
        ]
        between0and1Latlon = utils.XYToLatlon(between0and1XY,
                                              path.getReferenceLatlon())
        state.AISData = AISMsg([
            AISShip(ID=0,
                    lat=between0and1Latlon.lat,
                    lon=between0and1Latlon.lon,
                    headingDegrees=utils.HEADING_EAST,
                    speedKmph=0)
        ])

        # Obstacle on path
        self.assertTrue(path.obstacleOnPath(state))

        # Only look ahead 1 waypoint, but still has obstacle on path
        # (defaults to all waypoints if numLookAheadWaypoints not given)
        self.assertTrue(path.obstacleOnPath(state, numLookAheadWaypoints=1))

        # Move obstacle to be off the path
        between0and1shiftedRightXY = [
            waypoint0.getX() + (waypoint1.getX() - waypoint0.getX()) * 3 / 4,
            waypoint0.getY() + (waypoint1.getY() - waypoint0.getY()) / 2
        ]
        between0and1shiftedRightLatlon = utils.XYToLatlon(
            between0and1shiftedRightXY, path.getReferenceLatlon())
        state.AISData = AISMsg([
            AISShip(ID=0,
                    lat=between0and1shiftedRightLatlon.lat,
                    lon=between0and1shiftedRightLatlon.lon,
                    headingDegrees=utils.HEADING_EAST,
                    speedKmph=0)
        ])
        self.assertFalse(path.obstacleOnPath(state))
        ''' Tests not working, need to investigate or remove
        # Move boat position, so that new "path" has an obstacle on it
        waypoint0Latlon = utils.XYToLatlon([waypoint0.getX(), waypoint0.getY()], path.getReferenceLatlon())
        waypoint1Latlon = utils.XYToLatlon([waypoint1.getX(), waypoint1.getY()], path.getReferenceLatlon())
        state.position = latlon(waypoint0Latlon.lat, waypoint1Latlon.lon)
        self.assertTrue(path.obstacleOnPath(state))

        # Move boat position, so that new "path" does not have an obstacle on it
        state.position = latlon(waypoint1Latlon.lat, waypoint0Latlon.lon)
        self.assertFalse(path.obstacleOnPath(state))

        waypoint2 = path.getOMPLPath().getSolutionPath().getState(2)
        between1and2XY = [waypoint1.getX() + (waypoint2.getX() - waypoint1.getX()) / 2,
                          waypoint1.getY() + (waypoint2.getY() - waypoint1.getY()) / 2]
        between1and2Latlon = utils.XYToLatlon(between1and2XY, path.getReferenceLatlon())
        state.AISData = AISMsg([AISShip(ID=0, lat=between1and2Latlon.lat, lon=between1and2Latlon.lon,
                                        headingDegrees=utils.HEADING_EAST, speedKmph=0)])

        # Obstacle on path
        self.assertTrue(path.obstacleOnPath(state))
        '''

        # Only look ahead 1 waypoint, so does not have obstacle on path
        # (defaults to all waypoints if numLookAheadWaypoints not given)
        self.assertFalse(path.obstacleOnPath(state, numLookAheadWaypoints=1))
    def test_upwindOrDownwindOnPath(self):
        # Create simple path from latlon(0,0) to latlon(0.2,0.2)
        state = sbot.BoatState(globalWaypoint=latlon(0.2, 0.2),
                               position=latlon(0, 0),
                               globalWindDirectionDegrees=90,
                               globalWindSpeedKmph=10,
                               AISData=AISMsg(),
                               headingDegrees=0,
                               speedKmph=0)
        path = createPath(state, runtimeSeconds=1, numRuns=2)
        desiredHeadingDegrees = utils.getDesiredHeadingDegrees(
            state.position, path.getNextWaypoint())

        # Set state with global wind direction nearly same as boat current direction (sailing downwind)
        downwindGlobalWindDirectionDegrees = desiredHeadingDegrees + DOWNWIND_MAX_ANGLE_DEGREES / 2
        downwindState = sbot.BoatState(
            globalWaypoint=latlon(0.2, 0.2),
            position=latlon(0, 0),
            globalWindDirectionDegrees=downwindGlobalWindDirectionDegrees,
            globalWindSpeedKmph=10,
            AISData=AISMsg(),
            headingDegrees=120,
            speedKmph=1)
        # Needs to maintain downwind for time limit before returning true
        self.assertFalse(
            path.upwindOrDownwindOnPath(downwindState,
                                        numLookAheadWaypoints=1))
        time.sleep(UPWIND_DOWNWIND_TIME_LIMIT_SECONDS * 1.5)
        self.assertTrue(
            path.upwindOrDownwindOnPath(downwindState,
                                        numLookAheadWaypoints=1))

        # Set state with global wind direction nearly 180 degrees from boat current direction (sailing upwind)
        upwindGlobalWindDirectionDegrees = desiredHeadingDegrees + 180 + UPWIND_MAX_ANGLE_DEGREES / 2
        upwindState = sbot.BoatState(
            globalWaypoint=latlon(0.2, 0.2),
            position=latlon(0, 0),
            globalWindDirectionDegrees=upwindGlobalWindDirectionDegrees,
            globalWindSpeedKmph=10,
            AISData=AISMsg(),
            headingDegrees=120,
            speedKmph=1)
        self.assertFalse(
            path.upwindOrDownwindOnPath(upwindState, numLookAheadWaypoints=1))
        time.sleep(UPWIND_DOWNWIND_TIME_LIMIT_SECONDS * 1.5)
        self.assertTrue(
            path.upwindOrDownwindOnPath(upwindState, numLookAheadWaypoints=1))

        # Set state so the boat is not going downwind or upwind
        validGlobalWindDirectionDegrees = desiredHeadingDegrees + DOWNWIND_MAX_ANGLE_DEGREES * 2
        validState = sbot.BoatState(
            globalWaypoint=latlon(0.2, 0.2),
            position=latlon(0, 0),
            globalWindDirectionDegrees=validGlobalWindDirectionDegrees,
            globalWindSpeedKmph=10,
            AISData=AISMsg(),
            headingDegrees=120,
            speedKmph=1)
        self.assertFalse(
            path.upwindOrDownwindOnPath(validState, numLookAheadWaypoints=1))
        time.sleep(UPWIND_DOWNWIND_TIME_LIMIT_SECONDS * 1.5)
        self.assertFalse(
            path.upwindOrDownwindOnPath(validState, numLookAheadWaypoints=1))

        # Move boat so that boat is not going downwind or upwind
        newPosition = latlon(0.2, 0)
        desiredHeadingDegrees = utils.getDesiredHeadingDegrees(
            newPosition, path.getNextWaypoint())
        validGlobalWindDirectionDegrees = desiredHeadingDegrees + UPWIND_MAX_ANGLE_DEGREES * 2
        validState = sbot.BoatState(
            globalWaypoint=latlon(0.2, 0.2),
            position=newPosition,
            globalWindDirectionDegrees=validGlobalWindDirectionDegrees,
            globalWindSpeedKmph=10,
            AISData=AISMsg(),
            headingDegrees=120,
            speedKmph=1)
        self.assertFalse(
            path.upwindOrDownwindOnPath(validState, numLookAheadWaypoints=1))
        time.sleep(UPWIND_DOWNWIND_TIME_LIMIT_SECONDS * 1.5)
        self.assertFalse(
            path.upwindOrDownwindOnPath(validState, numLookAheadWaypoints=1))

        # Move boat so that boat is going downwind
        newPosition = latlon(0, 0.2)
        desiredHeadingDegrees = utils.getDesiredHeadingDegrees(
            newPosition, path.getNextWaypoint())
        downwindGlobalWindDirectionDegrees = desiredHeadingDegrees + DOWNWIND_MAX_ANGLE_DEGREES / 2
        invalidState = sbot.BoatState(
            globalWaypoint=latlon(0.2, 0.2),
            position=newPosition,
            globalWindDirectionDegrees=downwindGlobalWindDirectionDegrees,
            globalWindSpeedKmph=10,
            AISData=AISMsg(),
            headingDegrees=120,
            speedKmph=1)
        self.assertFalse(
            path.upwindOrDownwindOnPath(invalidState, numLookAheadWaypoints=1))
        time.sleep(UPWIND_DOWNWIND_TIME_LIMIT_SECONDS * 1.5)
        self.assertTrue(
            path.upwindOrDownwindOnPath(invalidState, numLookAheadWaypoints=1))