Program Listing for File polygon.hpp

Return to documentation for file (/tmp/ws/src/slg_msgs/include/slg_msgs/polygon.hpp)

/*
 * POLYGON CLASS
 *
 * Copyright (c) 2020-2023 Alberto José Tudela Roldán <ajtudela@gmail.com>
 *
 * This file is part of slg_msgs.
 *
 * All rights reserved.
 *
 */

/*
################################################################
# Ray-casting algorithm
#
# adapted from http://rosettacode.org/wiki/Ray-casting_algorithm
################################################################
*/

#ifndef SLG_MSGS__POLYGON_HPP_
#define SLG_MSGS__POLYGON_HPP_

#include <string>
#include <vector>
#include <algorithm>
#include <limits>
#include <numeric>

#include <geometry_msgs/msg/polygon.hpp>

#include "point2D.hpp"

const double epsilon = std::numeric_limits<float>().epsilon();
const std::numeric_limits<double> DOUBLE;
const double MIN = DOUBLE.min();
const double MAX = DOUBLE.max();

namespace slg{
struct Edge{
    Point2D a, b;
    Edge(Point2D a = Point2D(0.0, 0.0), Point2D b = Point2D(0.0, 0.0)): a(a), b(b) {}

    bool operator()(const Point2D & p) const{
        if (a.y > b.y) {return Edge{b, a}(p);}
        if (p.y == a.y || p.y == b.y) {return operator()({ p.x, p.y + epsilon });}
        if (p.y > b.y || p.y < a.y || p.x > std::max(a.x, b.x)) {return false;}
        if (p.x < std::min(a.x, b.x)) {return true;}
        auto blue = std::abs(a.x - p.x) > MIN ? (p.y - a.y) / (p.x - a.x) : MAX;
        auto red = std::abs(a.x - b.x) > MIN ? (b.y - a.y) / (b.x - a.x) : MAX;
        return blue >= red;
    }

    double distance(const Point2D & p){
        Point2D difAB = b - a;
        Point2D difPA = a - p;
        return fabs(difAB.x * difPA.y - difAB.y * difPA.x) / difAB.length();
    }

    bool self() const{
        return a == b;
    }
};

class Polygon{
    public:
        Polygon() : name("") {}

        Polygon(const Polygon& poly) :
                    name(poly.get_name()),
                    edges(poly.get_edges()) {}

        Polygon(const geometry_msgs::msg::Polygon & polygonMsg){
            // Read n-1 points
            for (unsigned int i = 0; i < polygonMsg.points.size()-1; i++){
                edges.push_back({polygonMsg.points[i], polygonMsg.points[i+1]});
            }
            // Add the last edge
            edges.push_back({polygonMsg.points[0], polygonMsg.points[polygonMsg.points.size()-1]});
        }

        ~Polygon(){}

        operator geometry_msgs::msg::Polygon (){
            geometry_msgs::msg::Polygon polygonMsg;
            for (const auto& edge : edges){
                polygonMsg.points.push_back(edge.a);
            }
            return polygonMsg;
        }

        Polygon& operator= (const Polygon& poly){
            if (this != &poly){
                this->name = poly.get_name();
                this->edges = poly.get_edges();
            }
            return *this;
        }

        Polygon& operator= (const geometry_msgs::msg::Polygon & polygonMsg){
            *this = Polygon(polygonMsg);
            return *this;
        }

        int size()                      const { return edges.size(); }
        bool empty()                    const { return edges.empty(); }
        void clear()                        { edges.clear(); name.clear(); }
        std::string get_name()          const { return name; }
        void set_name(std::string name)     { this->name = name; }
        std::vector<Edge> get_edges()   const { return edges; }
        Edge get_edge(int e)            const{ return edges[e]; }
        void add_edge(Edge edge)            { edges.push_back(edge); }
        void add_edge(Point2D a, Point2D b) { edges.push_back({a, b}); }

        bool contains(const Point2D & p) const{
            auto c = 0;
            for (auto e : edges) if (e(p)) c++;
            return c % 2 != 0;
        }

        Point2D centroid() const{
            std::vector<Point2D> points;
            for (const auto & edge : edges){
                points.push_back(edge.a);
            }
            Point2D sum = std::accumulate(points.begin(), points.end(), Point2D(0.0, 0.0));
            return sum / points.size();
        }

        bool is_closed() const{
            return edges.front().a == edges.back().b;
        }

        void close(){
            if (!is_closed()){
                edges.push_back({edges.back().b, edges.front().a});
            }
        }

        void add_point(const Point2D & p){
            // If the polygon is empty, add the first point
            if (edges.empty()){
                edges.push_back({p, p});
            }else{
                // If the last edge is a self edge, add a new point to it
                if (edges.back().self()){
                    edges.back().b = p;
                }else{
                    // If the polygon is closed, remove the last edge
                    if (is_closed()){
                        edges.pop_back();
                    }
                    // Add the new point
                    edges.push_back({edges.back().b, p});
                    // Close the polygon
                    close();
                }
            }
        }

        std::vector<Point2D> get_points() const{
            std::vector<Point2D> points;
            for (const auto & edge : edges){
                points.push_back(edge.a);
            }
            return points;
        }

    private:
        std::string name;
        std::vector<Edge> edges;
};
}  // namespace slg

#endif  // SLG_MSGS__POLYGON_HPP_