Program Listing for File polygon.hpp

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

// Copyright (c) 2017 Alberto J. Tudela Roldán
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
################################################################
# 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
{
  explicit Edge(
    const Point2D & new_a = Point2D(0.0, 0.0),
    const Point2D & new_b = Point2D(0.0, 0.0))
  : a(new_a), b(new_b) {}

  Edge(const Edge & e)
  : a(e.a), b(e.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()(Point2D(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;
  }

  Edge & operator=(const Edge & e)
  {
    if (this != &e) {
      this->a = e.a;
      this->b = e.b;
    }
    return *this;
  }

  bool operator==(const Edge & e) const
  {
    return (a == e.a && b == e.b) || (a == e.b && b == e.a);
  }

  bool operator!=(const Edge & e) const
  {
    return !(*this == e);
  }

  friend std::ostream & operator<<(std::ostream & out, const Edge & e)
  {
    out << "[" << e.a << " -> " << e.b << "] ";
    return out;
  }

  Point2D a, b;
};

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

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

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

  ~Polygon() {}

  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 new_name) {name = new_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);}

  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(Edge(edges.back().b, edges.front().a));
    }
  }

  void add_point(const Point2D & p)
  {
    // If the polygon is empty, add the first edge as a self edge
    if (edges.empty()) {
      edges.push_back(Edge(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(Edge(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;
  }

  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;
  }

  bool operator==(const Polygon & poly) const
  {
    return name == poly.get_name() && edges == poly.get_edges();
  }

  bool operator!=(const Polygon & poly) const
  {
    return !(*this == poly);
  }

  friend std::ostream & operator<<(std::ostream & out, const Polygon & poly)
  {
    out << "Polygon: " << poly.get_name() << std::endl;
    for (const auto & edge : poly.get_edges()) {
      out << edge.a << " -> " << edge.b << std::endl;
    }
    return out;
  }

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

#endif  // SLG_MSGS__POLYGON_HPP_