Skip to content
Snippets Groups Projects
homer_navigation_node.cpp 47.06 KiB
#include "homer_navigation/homer_navigation_node.h"

HomerNavigationNode::HomerNavigationNode() {
    ros::NodeHandle nh;

    // subscribers
    m_map_sub = nh.subscribe<nav_msgs::OccupancyGrid>(
        "/map", 1, &HomerNavigationNode::mapCallback, this);
    m_pose_sub = nh.subscribe<geometry_msgs::PoseStamped>(
        "/pose", 1, &HomerNavigationNode::poseCallback, this);
    m_laser_data_sub = nh.subscribe<sensor_msgs::LaserScan>(
        "/scan", 1, &HomerNavigationNode::laserDataCallback, this);
    m_down_laser_data_sub = nh.subscribe<sensor_msgs::LaserScan>(
        "/front_scan", 1, &HomerNavigationNode::laserDataCallback, this);
    m_start_navigation_sub = nh.subscribe<homer_mapnav_msgs::StartNavigation>(
        "/homer_navigation/start_navigation", 1,
        &HomerNavigationNode::startNavigationCallback, this);
    m_move_base_simple_goal_sub = nh.subscribe<geometry_msgs::PoseStamped>(
        "/move_base_simple/goal", 1,
        &HomerNavigationNode::moveBaseSimpleGoalCallback,
        this);  // for RVIZ usage
    m_stop_navigation_sub = nh.subscribe<std_msgs::Empty>(
        "/homer_navigation/stop_navigation", 1,
        &HomerNavigationNode::stopNavigationCallback, this);
    m_navigate_to_poi_sub = nh.subscribe<homer_mapnav_msgs::NavigateToPOI>(
        "/homer_navigation/navigate_to_POI", 1,
        &HomerNavigationNode::navigateToPOICallback, this);
    m_unknown_threshold_sub = nh.subscribe<std_msgs::Int8>(
        "/homer_navigation/unknown_threshold", 1,
        &HomerNavigationNode::unknownThresholdCallback, this);
    m_refresh_param_sub = nh.subscribe<std_msgs::Empty>(
        "/homer_navigation/refresh_params", 1,
        &HomerNavigationNode::refreshParamsCallback, this);
    m_max_move_depth_sub = nh.subscribe<std_msgs::Float32>(
        "/homer_navigation/max_depth_move_distance", 1,
        &HomerNavigationNode::maxDepthMoveDistanceCallback, this);
    m_ignore_laser_sub = nh.subscribe<std_msgs::String>(
        "/homer_navigation/ignore_laser", 1,
        &HomerNavigationNode::ignoreLaserCallback, this);

    m_cmd_vel_pub =
        nh.advertise<geometry_msgs::Twist>("/robot_platform/cmd_vel", 3);
    m_target_reached_string_pub =
        nh.advertise<std_msgs::String>("/homer_navigation/target_reached", 1);
    // m_target_reached_empty_pub 	=
    // nh.advertise<std_msgs::Empty>("/homer_navigation/target_reached", 1);
    m_target_unreachable_pub =
        nh.advertise<homer_mapnav_msgs::TargetUnreachable>(
            "/homer_navigation/target_unreachable", 1);
    m_path_pub = nh.advertise<nav_msgs::Path>("/homer_navigation/path", 1);
    m_ptu_center_world_point_pub =
        nh.advertise<homer_ptu_msgs::CenterWorldPoint>(
            "/ptu/center_world_point", 1);
    m_set_pan_tilt_pub =
        nh.advertise<homer_ptu_msgs::SetPanTilt>("/ptu/set_pan_tilt", 1);
    m_debug_pub = nh.advertise<std_msgs::String>("/homer_navigation/debug", 1);

    m_get_POIs_client =
        nh.serviceClient<homer_mapnav_msgs::GetPointsOfInterest>(
            "/map_manager/get_pois");

    m_MainMachine.setName("HomerNavigation Main");
    ADD_MACHINE_STATE(m_MainMachine, IDLE);
    ADD_MACHINE_STATE(m_MainMachine, FOLLOWING_PATH);
    ADD_MACHINE_STATE(m_MainMachine, AVOIDING_COLLISION);
    ADD_MACHINE_STATE(m_MainMachine, FINAL_TURN);

    init();
}
void HomerNavigationNode::loadParameters() {
    // Explorer constructor
    ros::param::param("/homer_navigation/allowed_obstacle_distance/min",
                      m_AllowedObstacleDistance.first, (float)0.3);
    ros::param::param("/homer_navigation/allowed_obstacle_distance/max",
                      m_AllowedObstacleDistance.second, (float)5.0);
    ros::param::param("/homer_navigation/safe_obstacle_distance/min",
                      m_SafeObstacleDistance.first, (float)0.7);
    ros::param::param("/homer_navigation/safe_obstacle_distance/max",
                      m_SafeObstacleDistance.second, (float)1.5);
    ros::param::param("/homer_navigation/frontier_safeness_factor",
                      m_FrontierSafenessFactor, (float)1.4);
    ros::param::param("/homer_navigation/safe_path_weight", m_SafePathWeight,
                      (double)1.2);
    ros::param::param("/homer_navigation/waypoint_sampling_threshold",
                      m_waypoint_sampling_threshold, (float)1.5);
    m_AllowedObstacleDistance.first;
    m_AllowedObstacleDistance.second;
    m_SafeObstacleDistance.first;
    m_SafeObstacleDistance.second;

    // check path
    ros::param::param("/homer_navigation/check_path", m_check_path, (bool)true);
    ros::param::param("/homer_navigation/check_path_max_distance",
                      m_check_path_max_distance, (float)2.0);

    // collision
    ros::param::param("/homer_navigation/collision_distance",
                      m_collision_distance, (float)0.3);
    ros::param::param("/homer_navigation/collision_distance_near_target",
                      m_collision_distance_near_target, (float)0.2);
    ros::param::param("/homer_navigation/backward_collision_distance",
                      m_backward_collision_distance, (float)0.5);

    // cmd_vel config values
    ros::param::param("/homer_navigation/min_turn_angle", m_min_turn_angle,
                      (float)0.15);
    ros::param::param("/homer_navigation/max_turn_speed", m_max_turn_speed,
                      (float)0.6);
    ros::param::param("/homer_navigation/min_turn_speed", m_min_turn_speed,
                      (float)0.3);
    ros::param::param("/homer_navigation/max_move_speed", m_max_move_speed,
                      (float)0.4);
    ros::param::param("/homer_navigation/max_drive_angle", m_max_drive_angle,
                      (float)0.6);

    // caution factors
    ros::param::param("/homer_navigation/map_speed_factor", m_map_speed_factor,
                      (float)1.0);
    ros::param::param("/homer_navigation/waypoint_speed_factor",
                      m_waypoint_speed_factor, (float)1.0);
    ros::param::param("/homer_navigation/obstacle_speed_factor",
                      m_obstacle_speed_factor, (float)1.0);
    ros::param::param("/homer_navigation/target_distance_speed_factor",
                      m_target_distance_speed_factor, (float)0.4);

    // robot dimensions
    ros::param::param("/homer_navigation/min_x", m_min_x, (float)0.3);
    ros::param::param("/homer_navigation/min_y", m_min_y, (float)0.27);

    // error durations
    ros::param::param("/homer_navigation/callback_error_duration",
                      m_callback_error_duration, (float)0.3);

    ros::param::param("/homer_navigation/use_ptu", m_use_ptu, (bool)false);

    ros::param::param("/homer_navigation/unknown_threshold",
                      m_unknown_threshold, (int)50);
    ros::param::param("/homer_navigation/waypoint_radius_factor",
                      m_waypoint_radius_factor, (float)0.25);
    if (m_explorer) {
        delete m_explorer;
    }
}

void HomerNavigationNode::init() {
    m_explorer = 0;
    m_robot_pose.position.x = 0.0;
    m_robot_pose.position.y = 0.0;
    m_robot_pose.position.z = 0.0;
    m_robot_pose.orientation = tf::createQuaternionMsgFromYaw(0.0);
    m_robot_last_pose = m_robot_pose;
    m_avoided_collision = false;
    m_act_speed = 0;
    m_angular_avoidance = 0;
    m_last_check_path_time = ros::Time::now();
    m_ignore_scan = "";

    m_obstacle_on_path = false;
    m_obstacle_position.x = 0;
    m_obstacle_position.y = 0;
    m_obstacle_position.z = 0;

    nav_msgs::OccupancyGrid tmp_map;
    tmp_map.header.frame_id = "/map";
    tmp_map.info.resolution = 0;
    tmp_map.info.width = 0;
    tmp_map.info.height = 0;
    tmp_map.info.origin.position.x = 0;
    tmp_map.info.origin.position.y = 0;
    tmp_map.info.origin.position.z = 0;
    m_map = boost::make_shared<nav_msgs::OccupancyGrid>(tmp_map);

    loadParameters();

    m_MainMachine.setState(IDLE);
}

HomerNavigationNode::~HomerNavigationNode() {
    if (m_explorer) {
        delete m_explorer;
    }
}

void HomerNavigationNode::stopRobot() {
    m_act_speed = 0;
    geometry_msgs::Twist cmd_vel_msg;
    cmd_vel_msg.linear.x = 0;
    cmd_vel_msg.linear.y = 0;
    cmd_vel_msg.angular.z = 0;
    m_cmd_vel_pub.publish(cmd_vel_msg);
    // ros::Duration(0.1).sleep();
    m_cmd_vel_pub.publish(cmd_vel_msg);
    // ros::Duration(0.1).sleep();
    m_cmd_vel_pub.publish(cmd_vel_msg);
}

void HomerNavigationNode::idleProcess() {
    if (m_MainMachine.state() != IDLE) {
        if ((ros::Time::now() - m_last_laser_time) >
            ros::Duration(m_callback_error_duration)) {
            ROS_ERROR_STREAM("Laser data timeout!\n");
            stopRobot();
        }
        if ((ros::Time::now() - m_last_pose_time) >
            ros::Duration(m_callback_error_duration)) {
            ROS_ERROR_STREAM("Pose timeout!\n");
            stopRobot();
        }
    }
}

bool HomerNavigationNode::isInIgnoreList(std::string frame_id) {
    std::regex reg("(^|\\s)" + frame_id + "(\\s|$)");
    return regex_match(m_ignore_scan, reg);
}

void HomerNavigationNode::setExplorerMap() {
    // adding lasers to map
    nav_msgs::OccupancyGrid temp_map = *m_map;
    for (const auto& scan : m_scan_map) {
        if (!isInIgnoreList(scan.second->header.frame_id)) {
            std::vector<geometry_msgs::Point> scan_points;
            scan_points = map_tools::laser_msg_to_points(
                scan.second, m_transform_listener, "/map");
            for (const auto& point : scan_points) {
                Eigen::Vector2i map_point =
                    map_tools::toMapCoords(point, m_map);
                int index = map_point.y() * m_map->info.width + map_point.x();
                if (index > 0 &&
                    index < m_map->info.width * m_map->info.height) {
                    temp_map.data[index] = (int8_t)100;
                }
            }
        }
    }
    if (m_fast_path_planning) {
        maskMap(temp_map);
    }
    m_explorer->setOccupancyMap(
        boost::make_shared<nav_msgs::OccupancyGrid>(temp_map));
}

void HomerNavigationNode::calculatePath() {
    if (!m_explorer) {
        return;
    }
    if (isTargetPositionReached()) {
        return;
    }

    // setExplorerMap();
    m_explorer->setStart(map_tools::toMapCoords(m_robot_pose.position, m_map));

    bool success;
    m_pixel_path = m_explorer->getPath(success);
    if (!success) {
        ROS_WARN_STREAM("no path to target possible - drive to obstacle");
        m_obstacle_on_path = true;
    } else {
        m_obstacle_on_path = false;
        std::vector<Eigen::Vector2i> waypoint_pixels =
            m_explorer->sampleWaypointsFromPath(m_pixel_path,
                                                m_waypoint_sampling_threshold);
        m_waypoints.clear();
        ROS_INFO_STREAM("homer_navigation::calculatePath - Path Size: "
                        << waypoint_pixels.size());
        if (waypoint_pixels.size() > 0) {
            for (std::vector<Eigen::Vector2i>::iterator it =
                     waypoint_pixels.begin();
                 it != waypoint_pixels.end(); ++it) {
                geometry_msgs::PoseStamped poseStamped;
                poseStamped.header.frame_id = "/map";
                poseStamped.pose.position =
                    map_tools::fromMapCoords(*it, m_map);
                poseStamped.pose.orientation.x = 0.0;
                poseStamped.pose.orientation.y = 0.0;
                poseStamped.pose.orientation.z = 0.0;
                poseStamped.pose.orientation.w = 1.0;
                m_waypoints.push_back(poseStamped);
            }
            if (m_path_reaches_target) {
                geometry_msgs::PoseStamped poseStamped;
                poseStamped.header.frame_id = "/map";
                poseStamped.pose.position = m_target_point;
                poseStamped.pose.orientation.x = 0;
                poseStamped.pose.orientation.y = 0;
                poseStamped.pose.orientation.z = 0;
                poseStamped.pose.orientation.w = 1;
                m_waypoints.push_back(poseStamped);
            }
            sendPathData();
        } else {
            sendTargetUnreachableMsg(
                homer_mapnav_msgs::TargetUnreachable::NO_PATH_FOUND);
        }

        m_last_laser_time = ros::Time::now();
        m_last_pose_time = ros::Time::now();
    }
}

void HomerNavigationNode::startNavigation() {
    if (!m_explorer) {
        return;
    }
    if (isTargetPositionReached()) {
        return;
    }

    ROS_INFO_STREAM("Distance to target still too large ("
                    << m_distance_to_target
                    << "m; requested: " << m_desired_distance << "m)");
    setExplorerMap();

    // check if there still exists a path to the original target
    if (m_avoided_collision && m_initial_path_reaches_target &&
        m_stop_before_obstacle) {
        m_explorer->setStart(
            map_tools::toMapCoords(m_robot_pose.position, m_map));

        bool success;
        m_pixel_path = m_explorer->getPath(success);
        if (!success) {
            ROS_INFO_STREAM(
                "Initial path would have reached target, new path does not. "
                << "Sending target unreachable.");
            sendTargetUnreachableMsg(
                homer_mapnav_msgs::TargetUnreachable::LASER_OBSTACLE);
            return;
        }
    }

    m_explorer->setStart(map_tools::toMapCoords(m_robot_pose.position, m_map));
    Eigen::Vector2i new_target_approx = m_explorer->getNearestAccessibleTarget(
        map_tools::toMapCoords(m_target_point, m_map));

    geometry_msgs::Point new_target_approx_world =
        map_tools::fromMapCoords(new_target_approx, m_map);
    ROS_INFO_STREAM(
        "start Navigation: Approx target: " << new_target_approx_world);

    m_path_reaches_target =
        (map_tools::distance(new_target_approx_world, m_target_point) <
         m_desired_distance);
    m_initial_path_reaches_target = m_path_reaches_target;

    if (map_tools::distance(m_robot_pose.position, new_target_approx_world) <
        m_map->info.resolution * 1.5) {
        ROS_WARN_STREAM("close to aprox target - final turn");
        m_MainMachine.setState(FINAL_TURN);
    }

    bool new_approx_is_better =
        (map_tools::distance(m_robot_pose.position, m_target_point) -
         map_tools::distance(new_target_approx_world, m_target_point)) >
        2 * m_desired_distance;
    if (!new_approx_is_better && !m_path_reaches_target) {
        ROS_WARN_STREAM(
            "No better way to target found, turning and then reporting as "
            "unreachable."
            << std::endl
            << "Distance to target: " << m_distance_to_target
            << "m; requested: " << m_desired_distance << "m");
        m_MainMachine.setState(FINAL_TURN);
    } else {
        m_explorer->setTarget(new_target_approx);
        m_MainMachine.setState(FOLLOWING_PATH);
        calculatePath();
    }
}

void HomerNavigationNode::sendPathData() {
    nav_msgs::Path msg;
    msg.header.frame_id = "/map";
    msg.header.stamp = ros::Time::now();
    if (m_waypoints.size() > 0) {
        msg.poses = m_waypoints;
        geometry_msgs::PoseStamped pose_stamped;
        pose_stamped.pose = m_robot_pose;
        pose_stamped.header.frame_id = "/map";
        pose_stamped.header.stamp = ros::Time::now();
        msg.poses.insert(msg.poses.begin(), pose_stamped);
    }
    m_path_pub.publish(msg);
}

void HomerNavigationNode::sendTargetReachedMsg() {
    stopRobot();
    m_MainMachine.setState(IDLE);
    std_msgs::String reached_string_msg;
    reached_string_msg.data = m_target_name;
    m_target_reached_string_pub.publish(reached_string_msg);
    m_waypoints.clear();
    ROS_INFO_STREAM("=== Reached Target " << m_target_name << " ===");
}

void HomerNavigationNode::sendTargetUnreachableMsg(int8_t reason) {
    stopRobot();
    m_MainMachine.setState(IDLE);
    homer_mapnav_msgs::TargetUnreachable unreachable_msg;
    unreachable_msg.reason = reason;
    unreachable_msg.point = geometry_msgs::PointStamped();
    unreachable_msg.point.header.frame_id = "/map";
    unreachable_msg.point.point = m_obstacle_position;
    m_target_unreachable_pub.publish(unreachable_msg);
    m_waypoints.clear();
    ROS_INFO_STREAM("=== TargetUnreachableMsg ===");
}

bool HomerNavigationNode::isTargetPositionReached() {
    if (m_distance_to_target < m_desired_distance && !m_new_target) {
        ROS_INFO_STREAM("Target position reached. Distance to target: "
                        << m_distance_to_target
                        << "m. Desired distance:" << m_desired_distance << "m");
        stopRobot();
        m_waypoints.clear();
        m_MainMachine.setState(FINAL_TURN);
        ROS_INFO_STREAM("Turning to look-at point");
        return true;
    } else {
        return false;
    }
}

geometry_msgs::Point HomerNavigationNode::calculateMeanPoint(
    const std::vector<geometry_msgs::Point>& points) {
    geometry_msgs::Point mean_point = geometry_msgs::Point();
    for (auto const& point : points) {
        mean_point.x += point.x;
        mean_point.y += point.y;
    }
    if (points.size() > 0) {
        mean_point.x /= points.size();
        mean_point.y /= points.size();
    }
    return mean_point;
}

bool HomerNavigationNode::obstacleOnPath() {
    m_last_check_path_time = ros::Time::now();
    if (m_pixel_path.size() == 0) {
        ROS_DEBUG_STREAM("no path found for finding an obstacle");
        return false;
    }
    std::vector<geometry_msgs::Point> obstacle_vec;

    for (auto const& scan : m_scan_map) {
        if (!isInIgnoreList(scan.second->header.frame_id)) {
            std::vector<geometry_msgs::Point> scan_points;
            scan_points = map_tools::laser_msg_to_points(
                scan.second, m_transform_listener, "/map");

            for (unsigned i = 1; i < m_pixel_path.size() - 1; i++) {
                geometry_msgs::Point lp =
                    map_tools::fromMapCoords(m_pixel_path.at(i - 1), m_map);
                geometry_msgs::Point p =
                    map_tools::fromMapCoords(m_pixel_path.at(i), m_map);
                if (map_tools::distance(m_robot_pose.position, p) >
                    m_check_path_max_distance * 2) {
                    if (obstacle_vec.size() > 0) {
                        m_obstacle_position = calculateMeanPoint(obstacle_vec);
                        ROS_DEBUG_STREAM(
                            "found obstacle at: " << m_obstacle_position);
                        return true;
                    } else {
                        return false;
                    }
                }
                for (int k = 0; k < 4; k++) {
                    geometry_msgs::Point t;
                    t.x = lp.x + (p.x - lp.x) * k / 4.0;
                    t.y = lp.y + (p.y - lp.y) * k / 4.0;
                    for (const auto& sp : scan_points) {
                        if (map_tools::distance(sp, t) <
                            m_AllowedObstacleDistance.first -
                                m_map->info.resolution) {
                            if (map_tools::distance(m_robot_pose.position, sp) <
                                m_check_path_max_distance) {
                                obstacle_vec.push_back(sp);
                            }
                        }
                    }
                }
            }
        }
    }
    if (obstacle_vec.size() > 0) {
        m_obstacle_position = calculateMeanPoint(obstacle_vec);
        ROS_DEBUG_STREAM("found obstacle at: " << m_obstacle_position);
        return true;
    } else {
        return false;
    }
}

void HomerNavigationNode::performNextMove() {
    if (m_MainMachine.state() == FOLLOWING_PATH) {
        if (isTargetPositionReached()) {
            return;
        }
        // check if an obstacle is on the current path
        if (m_check_path &&
            (ros::Time::now() - m_last_check_path_time) > ros::Duration(0.2)) {
            if (obstacleOnPath()) {
                if (m_seen_obstacle_before) {
                    if (!m_obstacle_on_path) {
                        stopRobot();
                        calculatePath();
                        return;
                    } else {
                        calculatePath();
                    }
                }
                m_seen_obstacle_before = true;
            } else {
                m_seen_obstacle_before = false;
            }
        }

        if (m_waypoints.size() == 0) {
            ROS_WARN_STREAM(
                "No waypoints but trying to perform next move! Skipping.");
            return;
        }
        // if we have accidentaly skipped waypoints, recalculate path
        float minDistance = FLT_MAX;
        float distance;
        unsigned nearestWaypoint = 0;
        for (unsigned i = 0; i < m_waypoints.size(); i++) {
            distance = map_tools::distance(m_robot_pose.position,
                                           m_waypoints[i].pose.position);
            if (distance < minDistance) {
                nearestWaypoint = i;
                minDistance = distance;
            }
        }
        if (nearestWaypoint != 0) {
            // if the target is nearer than the waypoint don't recalculate
            if (m_waypoints.size() != 2) {
                ROS_WARN_STREAM("Waypoints skipped. Recalculating path!");
                calculatePath();
                if (m_MainMachine.state() != FOLLOWING_PATH) {
                    return;
                }
            } else {
                m_waypoints.erase(m_waypoints.begin());
            }
        }

        Eigen::Vector2i waypointPixel =
            map_tools::toMapCoords(m_waypoints[0].pose.position, m_map);
        float obstacleDistanceMap =
            m_explorer->getObstacleTransform()->getValue(waypointPixel.x(),
                                                         waypointPixel.y()) *
            m_map->info.resolution;
        float waypointRadius = m_waypoint_radius_factor * obstacleDistanceMap;

        if ((waypointRadius < m_map->info.resolution) ||
            (m_waypoints.size() == 1)) {
            waypointRadius = m_map->info.resolution;
        }

        if (m_waypoints.size() != 0) {
            // calculate distance between last_pose->current_pose and waypoint
            Eigen::Vector2f line;  // direction of the line
            line[0] = m_robot_pose.position.x - m_robot_last_pose.position.x;
            line[1] = m_robot_pose.position.y - m_robot_last_pose.position.y;
            Eigen::Vector2f point_to_start;  // vector from the point to the
                                             // start of the line
            point_to_start[0] =
                m_robot_last_pose.position.x - m_waypoints[0].pose.position.x;
            point_to_start[1] =
                m_robot_last_pose.position.y - m_waypoints[0].pose.position.y;
            float dot = point_to_start[0] * line[0] +
                        point_to_start[1] *
                            line[1];  // dot product of point_to_start * line
            Eigen::Vector2f
                distance;  // shortest distance vector from point to line
            distance[0] = point_to_start[0] - dot * line[0];
            distance[1] = point_to_start[1] - dot * line[1];
            float distance_to_waypoint = sqrt((
                double)(distance[0] * distance[0] + distance[1] * distance[1]));

            // check if current waypoint has been reached
            if (distance_to_waypoint < waypointRadius &&
                m_waypoints.size() >= 1) {
                m_waypoints.erase(m_waypoints.begin());
            }
        }

        if (m_waypoints.size() == 0) {
            ROS_INFO_STREAM("Last waypoint reached");
            currentPathFinished();
            return;
        }

        sendPathData();

        if (m_use_ptu) {
            ROS_INFO_STREAM("Moving PTU to center next Waypoint");
            homer_ptu_msgs::CenterWorldPoint ptu_msg;
            ptu_msg.point.x = m_waypoints[0].pose.position.x;
            ptu_msg.point.y = m_waypoints[0].pose.position.y;
            ptu_msg.point.z = 0;
            ptu_msg.permanent = true;
            m_ptu_center_world_point_pub.publish(ptu_msg);
        }

        float distanceToWaypoint = map_tools::distance(
            m_robot_pose.position, m_waypoints[0].pose.position);
        float angleToWaypoint = angleToPointDeg(m_waypoints[0].pose.position);
        if (angleToWaypoint < -180) {
            angleToWaypoint += 360;
        }
        float angle = deg2Rad(angleToWaypoint);

        // linear speed calculation
        if (m_avoided_collision) {
            if (std::fabs(angleToWaypoint) < 10) {
                m_avoided_collision = false;
            }
        } else if (fabs(angle) > m_max_drive_angle) {
            m_act_speed = 0.0;
        } else if (m_obstacle_on_path &&
                   map_tools::distance(m_robot_pose.position,
                                       m_obstacle_position) < 1.0) {
            m_act_speed = 0.0;
            float angleToWaypoint2 = angleToPointDeg(m_obstacle_position);
            if (angleToWaypoint2 < -180) {
                angleToWaypoint2 += 360;
            }
            angle = deg2Rad(angleToWaypoint2);

            if (std::fabs(angle) < m_min_turn_angle) {
                ROS_INFO_STREAM("angle = " << angle);
                m_avoided_collision = true;
                m_initial_path_reaches_target = true;
                m_stop_before_obstacle = true;
                startNavigation();
                return;
            }
        } else {
            float obstacleMapDistance = 1;
            for (int wpi = -1; wpi < std::min((int)m_waypoints.size(), (int)2);
                 wpi++) {
                Eigen::Vector2i robotPixel;
                if (wpi == -1) {
                    robotPixel =
                        map_tools::toMapCoords(m_robot_pose.position, m_map);
                } else {
                    robotPixel = map_tools::toMapCoords(
                        m_waypoints[wpi].pose.position, m_map);
                }
                obstacleMapDistance = std::min(
                    (float)obstacleMapDistance,
                    (float)(m_explorer->getObstacleTransform()->getValue(
                                robotPixel.x(), robotPixel.y()) *
                            m_map->info.resolution));
                if (obstacleMapDistance <= 0.00001) {
                    ROS_ERROR_STREAM(
                        "obstacleMapDistance is below threshold to 0 setting "
                        "to 1");
                    obstacleMapDistance = 1;
                }
            }

            float max_move_distance_speed = m_max_move_speed *
                                            m_max_move_distance *
                                            m_obstacle_speed_factor;
            float max_map_distance_speed =
                m_max_move_speed * obstacleMapDistance * m_map_speed_factor;

            float max_waypoint_speed = 1;
            if (m_waypoints.size() > 1) {
                float angleToNextWaypoint =
                    angleToPointDeg(m_waypoints[1].pose.position);
                max_waypoint_speed = (1 - (angleToNextWaypoint / 180.0)) *
                                     distanceToWaypoint *
                                     m_waypoint_speed_factor;
            }

            m_act_speed = std::min(
                {std::max((float)0.1, m_distance_to_target *
                                          m_target_distance_speed_factor),
                 m_max_move_speed, max_move_distance_speed,
                 max_map_distance_speed, max_waypoint_speed});
            std_msgs::String tmp;
            std::stringstream str;
            str << "m_obstacle_speed " << max_move_distance_speed
                << " max_map_distance_speed " << max_map_distance_speed;
            tmp.data = str.str();
            m_debug_pub.publish(tmp);
        }

        // angular speed calculation
        if (angle < 0) {
            angle = std::max(angle * (float)0.8, -m_max_turn_speed);
            m_act_speed = m_act_speed + angle / 2.0;
            if (m_act_speed < 0) {
                m_act_speed = 0;
            }
        } else {
            angle = std::min(angle * (float)0.8, m_max_turn_speed);
            m_act_speed = m_act_speed - angle / 2.0;
            if (m_act_speed < 0) {
                m_act_speed = 0;
            }
        }
        geometry_msgs::Twist cmd_vel_msg;
        cmd_vel_msg.linear.x = m_act_speed;
        cmd_vel_msg.angular.z = angle;
        m_cmd_vel_pub.publish(cmd_vel_msg);

        ROS_DEBUG_STREAM(
            "Driving & turning"
            << std::endl
            << "linear: " << m_act_speed << " angular: " << angle << std::endl
            << "distanceToWaypoint:" << distanceToWaypoint
            << "angleToWaypoint: " << angleToWaypoint << std::endl);
    } else if (m_MainMachine.state() == AVOIDING_COLLISION) {
        if (isTargetPositionReached()) {
            return;
        } else if (m_max_move_distance <= m_collision_distance &&
                       m_waypoints.size() > 1 ||
                   m_max_move_distance <= m_collision_distance_near_target) {
            ROS_WARN_STREAM("Maximum driving distance too short ("
                            << m_max_move_distance << "m)! Moving back.");
            geometry_msgs::Twist cmd_vel_msg;
            if (!HomerNavigationNode::backwardObstacle()) {
                cmd_vel_msg.linear.x = -0.2;
            } else {
                if (m_angular_avoidance == 0) {
                    float angleToWaypoint =
                        angleToPointDeg(m_waypoints[0].pose.position);
                    if (angleToWaypoint < -180) {
                        angleToWaypoint += 360;
                    }
                    if (angleToWaypoint < 0) {
                        m_angular_avoidance = -0.4;
                    } else {
                        m_angular_avoidance = 0.4;
                    }
                }
                cmd_vel_msg.angular.z = m_angular_avoidance;
            }
            m_cmd_vel_pub.publish(cmd_vel_msg);
        } else {
            m_angular_avoidance = 0;
            m_avoided_collision = true;
            ROS_WARN_STREAM("Collision avoided. Updating path.");
            currentPathFinished();
        }
    } else if (m_MainMachine.state() == FINAL_TURN) {
        if (m_use_ptu) {
            // reset PTU
            homer_ptu_msgs::SetPanTilt msg;
            msg.absolute = true;
            msg.panAngle = 0;
            msg.tiltAngle = 0;
            m_set_pan_tilt_pub.publish(msg);
        }

        if (m_skip_final_turn) {
            ROS_INFO_STREAM("Final turn skipped. Target reached.");
            if (m_path_reaches_target) {
                sendTargetReachedMsg();
            } else {
                sendTargetUnreachableMsg(
                    homer_mapnav_msgs::TargetUnreachable::NO_PATH_FOUND);
            }
            return;
        }

        float turnAngle = minTurnAngle(tf::getYaw(m_robot_pose.orientation),
                                       m_target_orientation);
        ROS_DEBUG_STREAM(
            "homer_navigation::PerformNextMove:: Final Turn. Robot "
            "orientation: "
            << rad2Deg(tf::getYaw(m_robot_pose.orientation))
            << ". Target orientation: " << rad2Deg(m_target_orientation)
            << "homer_navigation::PerformNextMove:: turnAngle: "
            << rad2Deg(turnAngle));

        if (std::fabs(turnAngle) < m_min_turn_angle) {
            ROS_INFO_STREAM(
                ":::::::NEAREST WALKABLE TARGET REACHED BECAUSE lower "
                << m_min_turn_angle);
            ROS_INFO_STREAM("target angle = " << m_target_orientation);
            ROS_INFO_STREAM(
                "is angle = " << tf::getYaw(m_robot_pose.orientation));
            ROS_INFO_STREAM("m_distance_to_target = " << m_distance_to_target);
            ROS_INFO_STREAM("m_desired_distance = " << m_desired_distance);
            if (m_path_reaches_target) {
                sendTargetReachedMsg();
            } else {
                sendTargetUnreachableMsg(
                    homer_mapnav_msgs::TargetUnreachable::NO_PATH_FOUND);
            }
            return;
        } else {
            if (turnAngle < 0) {
                turnAngle = std::max(std::min(turnAngle, -m_min_turn_speed),
                                     -m_max_turn_speed);
            } else {
                turnAngle = std::min(std::max(turnAngle, m_min_turn_speed),
                                     m_max_turn_speed);
            }
            geometry_msgs::Twist cmd_vel_msg;
            cmd_vel_msg.angular.z = turnAngle;
            m_cmd_vel_pub.publish(cmd_vel_msg);
        }
    }
}

void HomerNavigationNode::currentPathFinished() {
    ROS_INFO_STREAM("Current path was finished, initiating recalculation.");
    m_waypoints.clear();
    stopRobot();
    startNavigation();
}

int HomerNavigationNode::angleToPointDeg(geometry_msgs::Point target) {
    double cx = m_robot_pose.position.x;
    double cy = m_robot_pose.position.y;
    int targetAngle = rad2Deg(atan2(target.y - cy, target.x - cx));
    int currentAngle = rad2Deg(tf::getYaw(m_robot_pose.orientation));

    int angleDiff = targetAngle - currentAngle;
    angleDiff = (angleDiff + 180) % 360 - 180;
    return angleDiff;
}

bool HomerNavigationNode::drawPolygon(
    std::vector<geometry_msgs::Point> vertices) {
    if (vertices.size() == 0) {
        ROS_INFO_STREAM("No vertices given!");
        return false;
    }
    // make temp. map
    std::vector<int> data(m_map->info.width * m_map->info.height);
    for (int i = 0; i < data.size(); i++) {
        data[i] = 0;
    }

    // draw the lines surrounding the polygon
    for (unsigned int i = 0; i < vertices.size(); i++) {
        int i2 = (i + 1) % vertices.size();
        drawLine(data, vertices[i].x, vertices[i].y, vertices[i2].x,
                 vertices[i2].y, 30);
    }
    // calculate a point in the middle of the polygon
    float midX = 0;
    float midY = 0;
    for (unsigned int i = 0; i < vertices.size(); i++) {
        midX += vertices[i].x;
        midY += vertices[i].y;
    }
    midX /= vertices.size();
    midY /= vertices.size();
    // fill polygon
    return fillPolygon(data, (int)midX, (int)midY, 30);
}

void HomerNavigationNode::drawLine(std::vector<int>& data, int startX,
                                   int startY, int endX, int endY, int value) {
    // bresenham algorithm
    int x, y, t, dist, xerr, yerr, dx, dy, incx, incy;
    // compute distances
    dx = endX - startX;
    dy = endY - startY;

    // compute increment
    if (dx < 0) {
        incx = -1;
        dx = -dx;
    } else {
        incx = dx ? 1 : 0;
    }

    if (dy < 0) {
        incy = -1;
        dy = -dy;
    } else {
        incy = dy ? 1 : 0;
    }

    // which distance is greater?
    dist = (dx > dy) ? dx : dy;
    // initializing
    x = startX;
    y = startY;
    xerr = dx;
    yerr = dy;

    // compute cells
    for (t = 0; t < dist; t++) {
        int index = x + m_map->info.width * y;
        if (index < 0 || index > data.size()) {
            continue;
        }
        data[index] = value;

        xerr += dx;
        yerr += dy;
        if (xerr > dist) {
            xerr -= dist;
            x += incx;
        }
        if (yerr > dist) {
            yerr -= dist;
            y += incy;
        }
    }
}

bool HomerNavigationNode::fillPolygon(std::vector<int>& data, int x, int y,
                                      int value) {
    int index = x + m_map->info.width * y;
    bool tmp = false;

    if ((int)m_map->data.at(index) > 90) {
        tmp = true;
    }
    if (data[index] != value) {
        data[index] = value;
        if (fillPolygon(data, x + 1, y, value)) {
            tmp = true;
        }
        if (fillPolygon(data, x - 1, y, value)) {
            tmp = true;
        }
        if (fillPolygon(data, x, y + 1, value)) {
            tmp = true;
        }
        if (fillPolygon(data, x, y - 1, value)) {
            tmp = true;
        }
    }
    return tmp;
}

bool HomerNavigationNode::backwardObstacle() {
    std::vector<geometry_msgs::Point> vertices;
    geometry_msgs::Point base_link_point;
    geometry_msgs::Point map_point;
    Eigen::Vector2i map_coord;

    std::vector<float> x;
    std::vector<float> y;

    x.push_back(-m_min_x - m_backward_collision_distance);
    y.push_back(m_min_y);

    x.push_back(-m_min_x - m_backward_collision_distance);
    y.push_back(-m_min_y);

    x.push_back(-0.1);
    y.push_back(-m_min_y);

    x.push_back(-0.1);
    y.push_back(m_min_y);

    for (int i = 0; i < x.size(); i++) {
        base_link_point.x = x[i];
        base_link_point.y = y[i];
        map_coord = map_tools::toMapCoords(
            map_tools::transformPoint(base_link_point, m_transform_listener,
                                      "/base_link", "/map"),
            m_map);
        map_point.x = map_coord.x();
        map_point.y = map_coord.y();
        vertices.push_back(map_point);
    }

    return drawPolygon(vertices);
}

void HomerNavigationNode::maskMap(nav_msgs::OccupancyGrid& cmap) {
    // generate bounding box
    ROS_INFO_STREAM("Calculating Bounding box for fast planning");
    Eigen::Vector2i pose_pixel =
        map_tools::toMapCoords(m_robot_pose.position, m_map);
    Eigen::Vector2i target_pixel =
        map_tools::toMapCoords(m_target_point, m_map);
    Eigen::Vector2i safe_pixel_distance(m_AllowedObstacleDistance.first * 4,
                                        m_AllowedObstacleDistance.first * 4);
    Eigen::AlignedBox2i planning_box;
    planning_box.extend(pose_pixel);
    planning_box.extend(target_pixel);
    ROS_INFO_STREAM("Bounding Box: (" << planning_box.min() << " "
                                      << planning_box.max());
    Eigen::AlignedBox2i safe_planning_box(
        planning_box.min() - safe_pixel_distance,
        planning_box.max() + safe_pixel_distance);
    ROS_INFO_STREAM("safe Bounding Box: (" << safe_planning_box.min() << " "
                                           << safe_planning_box.max());
    ROS_INFO_STREAM("min in m: " << map_tools::fromMapCoords(
                        safe_planning_box.min(), m_map));
    ROS_INFO_STREAM("max in m: " << map_tools::fromMapCoords(
                        safe_planning_box.max(), m_map));
    for (size_t x = 0; x < m_map->info.width; x++) {
        for (size_t y = 0; y < m_map->info.height; y++) {
            if (!safe_planning_box.contains(Eigen::Vector2i(x, y))) {
                cmap.data.at(y * m_map->info.width + x) = -1;
            }
        }
    }
}

// convenience math functions
float HomerNavigationNode::minTurnAngle(float angle1, float angle2) {
    angle1 *= 180.0 / M_PI;
    angle2 *= 180.0 / M_PI;

    int diff = angle2 - angle1;
    diff = (diff + 180) % 360 - 180;
    if (diff < -180) {
        diff += 360;
    }

    float ret = static_cast<double>(diff) * M_PI / 180.0;
    return ret;
}

void HomerNavigationNode::refreshParamsCallback(
    const std_msgs::Empty::ConstPtr& msg) {
    ROS_INFO_STREAM("Refreshing Parameters");
    loadParameters();
}

void HomerNavigationNode::mapCallback(
    const nav_msgs::OccupancyGrid::ConstPtr& msg) {
    if (!m_explorer) {
        float resolution = msg->info.resolution;
        m_explorer = new Explorer(m_AllowedObstacleDistance.first / resolution,
                                  m_AllowedObstacleDistance.second / resolution,
                                  m_SafeObstacleDistance.first / resolution,
                                  m_SafeObstacleDistance.second / resolution,
                                  m_SafePathWeight, m_FrontierSafenessFactor,
                                  m_unknown_threshold);
    }
    m_map = msg;
    if (m_MainMachine.state() != IDLE) {
        setExplorerMap();
    }
}

void HomerNavigationNode::poseCallback(
    const geometry_msgs::PoseStamped::ConstPtr& msg) {
    m_robot_last_pose = m_robot_pose;
    m_robot_pose = msg->pose;
    m_last_pose_time = ros::Time::now();
    m_new_target = false;
    m_distance_to_target =
        map_tools::distance(m_robot_pose.position, m_target_point);
    performNextMove();
}

void HomerNavigationNode::calcMaxMoveDist() {
    m_max_move_distance = 99;
    for (auto d : m_max_move_distances) {
        m_max_move_distance = std::min(m_max_move_distance, d.second);
    }
    if (m_max_move_distance <= m_collision_distance &&
            std::fabs(m_act_speed) > 0.1 && m_waypoints.size() > 1 ||
        m_max_move_distance <= m_collision_distance_near_target &&
            std::fabs(m_act_speed) > 0.1 && m_waypoints.size() == 1 ||
        m_max_move_distance <= 0.1) {
        if (m_MainMachine.state() == FOLLOWING_PATH) {
            stopRobot();
            m_MainMachine.setState(AVOIDING_COLLISION);
            ROS_WARN_STREAM("Collision detected while following path!");
        }
    }
}

void HomerNavigationNode::ignoreLaserCallback(
    const std_msgs::String::ConstPtr& msg) {
    ROS_INFO_STREAM("changed ignore laser to: " << msg->data);
    m_ignore_scan = msg->data;
}

void HomerNavigationNode::maxDepthMoveDistanceCallback(
    const std_msgs::Float32::ConstPtr& msg) {
    m_max_move_distances["depth"] = msg->data;
    calcMaxMoveDist();
}

void HomerNavigationNode::laserDataCallback(
    const sensor_msgs::LaserScan::ConstPtr& msg) {
    m_scan_map[msg->header.frame_id] = msg;
    m_last_laser_time = ros::Time::now();
    if (m_MainMachine.state() != IDLE) {
        if (!isInIgnoreList(msg->header.frame_id)) {
            m_max_move_distances[msg->header.frame_id] =
                map_tools::get_max_move_distance(
                    map_tools::laser_msg_to_points(msg, m_transform_listener,
                                                   "/base_link"),
                    m_min_x, m_min_y);
        } else {
            m_max_move_distances[msg->header.frame_id] = 99;
        }
        calcMaxMoveDist();
    }
}

void HomerNavigationNode::initNewTarget() {
    m_initial_path_reaches_target = false;
    m_avoided_collision = false;
    m_new_target = true;
}
void HomerNavigationNode::startNavigationCallback(
    const homer_mapnav_msgs::StartNavigation::ConstPtr& msg) {
    m_target_point = msg->goal.position;
    m_target_orientation = tf::getYaw(msg->goal.orientation);
    m_desired_distance =
        msg->distance_to_target < 0.1 ? 0.1 : msg->distance_to_target;
    m_skip_final_turn = msg->skip_final_turn;
    m_fast_path_planning = msg->fast_planning;
    m_target_name = "";

    ROS_INFO_STREAM("Navigating to target "
                    << m_target_point.x << ", " << m_target_point.y
                    << "\nTarget orientation: " << m_target_orientation
                    << "Desired distance to target: " << m_desired_distance);
    initNewTarget();
    startNavigation();
}

void HomerNavigationNode::moveBaseSimpleGoalCallback(
    const geometry_msgs::PoseStamped::ConstPtr& msg) {
    if (msg->header.frame_id != "map") {
        tf::StampedTransform transform;
        if (m_transform_listener.waitForTransform("map", msg->header.frame_id,
                                                  msg->header.stamp,
                                                  ros::Duration(1.0))) {
            try {
                m_transform_listener.lookupTransform(
                    "map", msg->header.frame_id, msg->header.stamp, transform);
                tf::Vector3 targetPos(msg->pose.position.x,
                                      msg->pose.position.y,
                                      msg->pose.position.z);
                targetPos = transform * targetPos;
                m_target_point.x = targetPos.getX();
                m_target_point.y = targetPos.getY();
                m_target_orientation = tf::getYaw(
                    transform * tf::Quaternion(msg->pose.orientation.x,
                                               msg->pose.orientation.y,
                                               msg->pose.orientation.z,
                                               msg->pose.orientation.w));
            } catch (tf::TransformException ex) {
                ROS_ERROR_STREAM(ex.what());
                return;
            }
        } else {
            try {
                m_transform_listener.lookupTransform(
                    "map", msg->header.frame_id, ros::Time(0), transform);
                tf::Vector3 targetPos(msg->pose.position.x,
                                      msg->pose.position.y,
                                      msg->pose.position.z);
                targetPos = transform * targetPos;
                m_target_point.x = targetPos.getX();
                m_target_point.y = targetPos.getY();
                m_target_orientation = tf::getYaw(
                    transform * tf::Quaternion(msg->pose.orientation.x,
                                               msg->pose.orientation.y,
                                               msg->pose.orientation.z,
                                               msg->pose.orientation.w));
            } catch (tf::TransformException ex) {
                ROS_ERROR_STREAM(ex.what());
                return;
            }
        }
    } else {
        m_target_point = msg->pose.position;
        m_target_orientation = tf::getYaw(msg->pose.orientation);
    }
    m_desired_distance = 0.1;
    m_skip_final_turn = false;
    m_fast_path_planning = false;

    ROS_INFO_STREAM("Navigating to target via Move Base Simple x: "
                    << m_target_point.x << ", y: " << m_target_point.y
                    << "\nTarget orientation: " << m_target_orientation
                    << " Desired distance to target: " << m_desired_distance
                    << "\nframe_id: " << msg->header.frame_id);
    initNewTarget();
    startNavigation();
}

void HomerNavigationNode::navigateToPOICallback(
    const homer_mapnav_msgs::NavigateToPOI::ConstPtr& msg) {
    homer_mapnav_msgs::GetPointsOfInterest srv;
    m_get_POIs_client.call(srv);
    std::vector<homer_mapnav_msgs::PointOfInterest>::iterator it;
    for (it = srv.response.poi_list.pois.begin();
         it != srv.response.poi_list.pois.end(); ++it) {
        if (it->name == msg->poi_name) {
            m_target_point = it->pose.position;
            m_target_orientation = tf::getYaw(it->pose.orientation);
            m_desired_distance =
                msg->distance_to_target < 0.1 ? 0.1 : msg->distance_to_target;
            m_skip_final_turn = msg->skip_final_turn;
            m_fast_path_planning = false;
            m_target_name = msg->poi_name;
            m_stop_before_obstacle = msg->stop_before_obstacle;

            ROS_INFO_STREAM("Navigating to target "
                            << m_target_point.x << ", " << m_target_point.y
                            << "\nTarget orientation: " << m_target_orientation
                            << "Desired distance to target: "
                            << m_desired_distance);
            initNewTarget();
            startNavigation();
            return;
        }
    }

    ROS_ERROR_STREAM("No point of interest with name '"
                     << msg->poi_name << "' found in current poi list");
    sendTargetUnreachableMsg(homer_mapnav_msgs::TargetUnreachable::UNKNOWN);
}

void HomerNavigationNode::stopNavigationCallback(
    const std_msgs::Empty::ConstPtr& msg) {
    ROS_INFO_STREAM("Stopping navigation.");
    m_MainMachine.setState(IDLE);
    stopRobot();

    m_waypoints.clear();
}

void HomerNavigationNode::unknownThresholdCallback(
    const std_msgs::Int8::ConstPtr& msg) {
    if (m_explorer) {
        m_explorer->setUnknownThreshold(static_cast<int>(msg->data));
    }
}

int main(int argc, char** argv) {
    ros::init(argc, argv, "homer_navigation");

    HomerNavigationNode node;

    ros::Rate rate(50);

    while (ros::ok()) {
        ros::spinOnce();
        node.idleProcess();
        rate.sleep();
    }

    return 0;
}