Program Listing for File pnpoly.hpp

Return to documentation for file (include/as2_geozones/pnpoly.hpp)

/*
 * MIT License
 *
 * Copyright (c) 2020  Christian Berger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef PNPOLY_HPP
#define PNPOLY_HPP

#include <cmath>
#include <cstdint>
#include <algorithm>
#include <array>
#include <limits>
#include <type_traits>
#include <vector>
#include <ostream>
#include <iostream>

namespace Pnpoly
{

template<typename T>
inline bool isEqual(T a, T b)
{
  // Inspired by: https://www.embeddeduse.com/2019/08/26/qt-compare-two-floats/
  static_assert(std::is_arithmetic<T>::value, "T must be an arithmetic type");
#pragma GCC diagnostic push
#if defined(__clang__)
#pragma GCC diagnostic ignored "-Wabsolute-value"
#endif
  constexpr auto EPSILON = 1.0e-09f;
  return (std::abs(a - b) <= EPSILON) ? true : std::abs(a - b) <=
         EPSILON * (std::max)(std::abs(a), std::abs(b));
#pragma GCC diagnostic pop
}

template<typename T>
inline std::vector<std::array<T, 2>> getConvexHull(const std::vector<std::array<T, 2>> & polygon)
{
  static_assert(std::is_arithmetic<T>::value, "T must be an arithmetic type");

  // Inspired by: https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain#C++

  auto isLeft = [](const std::array<T, 2> & a, const std::array<T, 2> & b) {
      constexpr const uint8_t X{0};
      constexpr const uint8_t Y{1};
      return a[X] < b[X] || ( (!(a[X] < b[X]) && !(a[X] > b[X]) /*a[X] == b[X]*/) && a[Y] < b[Y]);
    };

  auto ccw =
    [](const std::array<T, 2> & a, const std::array<T, 2> & b, const std::array<T, 2> & c) {
      constexpr const uint8_t X{0};
      constexpr const uint8_t Y{1};
      return (b[X] - a[X]) * (c[Y] - a[Y]) - (b[Y] - a[Y]) * (c[X] - a[X]);
    };

  auto sortedPolygon{polygon};
  std::sort(sortedPolygon.begin(), sortedPolygon.end(), isLeft);

  // Construct lower half of convex hull.
  std::vector<std::array<T, 2>> lowerHalf;
  for (auto it{sortedPolygon.begin()}; it != sortedPolygon.end(); ++it) {
    while (lowerHalf.size() >= 2 &&
      !(ccw(*(lowerHalf.rbegin() + 1), *(lowerHalf.rbegin()), *it) < 0))
    {
      lowerHalf.pop_back();
    }
    lowerHalf.push_back(*it);
  }

  // Construct upper half of convex hull.
  std::vector<std::array<T, 2>> upperHalf;
  for (auto it{sortedPolygon.rbegin()}; it != sortedPolygon.rend(); ++it) {
    while (upperHalf.size() >= 2 &&
      !(ccw(*(upperHalf.rbegin() + 1), *(upperHalf.rbegin()), *it) < 0))
    {
      upperHalf.pop_back();
    }
    upperHalf.push_back(*it);
  }

  std::vector<std::array<T, 2>> convexHull;
  convexHull.insert(convexHull.end(), lowerHalf.begin(), lowerHalf.end());
  convexHull.insert(convexHull.end(), upperHalf.begin() + 1, upperHalf.end() - 1);
  return convexHull;
}

template<typename T>
inline bool isIn(std::vector<std::array<T, 2>> & polygon, std::array<T, 2> & p)
{
  static_assert(std::is_arithmetic<T>::value, "T must be an arithmetic type");
  bool inside{false};
  if (2 < polygon.size()) {
    constexpr const uint8_t X{0};
    constexpr const uint8_t Y{1};
    const std::size_t POINTS{polygon.size()};
    std::size_t i{0};
    std::size_t j{POINTS - 1};
    for (; i < POINTS; j = i++) {
      if (isEqual(p[X], polygon.at(i)[X]) && isEqual(p[Y], polygon.at(i)[Y]) ) {
        return true;
      }

      // The algorithms is based on W. Randolph Franklin's implementation that can be found here:
      // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html

      if ( ((polygon.at(i)[Y] > p[Y]) != (polygon.at(j)[Y] > p[Y])) &&
        (p[X] <
        (polygon.at(j)[X] - polygon.at(i)[X]) * (p[Y] - polygon.at(i)[Y]) /
        (polygon.at(j)[Y] - polygon.at(i)[Y]) + polygon.at(i)[X]) )
      {
        inside = !inside;
      }
    }
  }
  return inside;
}

}  // namespace Pnpoly
#endif  // PNPOLY_HPP_