You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

632 lines
21 KiB
C

1 month ago
/*------------------------------------------------------------------------------
* Copyright (c) 2023 by Bai Bing (seread@163.com)
* See COPYING file for copying and redistribution conditions.
*
* Alians IT Studio.
*----------------------------------------------------------------------------*/
#pragma once
#include <string.h>
#include <algorithm>
#include <cfloat>
#include <climits>
#include <cmath>
#include <execution>
#include <iomanip>
#include <iostream>
#include <regex>
#include <stdexcept>
#include <vector>
#include "_Define.h"
#include "core/String.h"
#include "utils/EssentiallyEqual.h"
namespace ais
{
inline int dcmp(double x, double eps = DBL_EPSILON)
{
if (fabs(x) < eps)
return 0;
else
return x < 0 ? -1 : 1;
};
template <typename dtype = double>
inline bool between(dtype x, dtype a, dtype b)
{
return (a <= x && x <= b) || (a >= x && x >= b);
};
struct AIS_EXPORT Point2D
{
public:
double x{0};
double y{0};
Point2D() : x(std::nan("1")), y(std::nan("1")) {}
Point2D(double x_, double y_) : x(x_), y(y_) {}
Point2D(const std::pair<double, double> &p) : x(p.first), y(p.second) {}
Point2D(const char *buffer)
{
std::string line(buffer);
auto tokens = ais::str_split_all(line, ",");
if (!tokens.empty())
{
x = tokens.size() >= 2 ? std::stod(tokens[0]) : std::nan("1");
y = tokens.size() >= 2 ? std::stod(tokens[1]) : std::nan("1");
}
}
inline double distance(const Point2D &p) const
{
return std::sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}
inline double distance(const std::pair<double, double> &p) const
{
return std::sqrt((x - p.first) * (x - p.first) + (y - p.second) * (y - p.second));
}
inline double distance(double px, double py) const
{
return std::sqrt((x - px) * (x - px) + (y - py) * (y - py));
}
inline double distance2D(const Point2D &p) const
{
return distance(p);
}
inline bool operator<(const Point2D &rhs) const
{
return x < rhs.x - 1e-6 || (std::abs(x - rhs.x) < 1e-6 && y < rhs.y);
}
inline bool operator==(const Point2D &rhs) const
{
// return utils::essentially_equal(x, rhs.x, std::numeric_limits<double>::epsilon()) &&
// utils::essentially_equal(y, rhs.y, std::numeric_limits<double>::epsilon());
return (std::abs(x - rhs.x) < 1e-6 && std::abs(y - rhs.y) < 1e-6);
}
inline Point2D operator+(const Point2D &rhs) const
{
return Point2D(x + rhs.x, y + rhs.y);
}
inline Point2D operator-(const Point2D &rhs) const
{
return Point2D(x - rhs.x, y - rhs.y);
}
inline double operator*(const Point2D &rhs) const
{
return x * rhs.x + y * rhs.y;
}
// Cross product
// P^Q > 0, P in the clockwise direction of Q
// < 0, P in the counterclockwise direction of Q
// = 0, P and Q Co-linear, possibly in the same or opposite direction
inline double operator^(const Point2D &rhs) const
{
return x * rhs.y - rhs.x * y;
}
inline bool has_value() const
{
return !(std::isnan(x) || std::isnan(y));
}
inline bool isNan() const
{
return std::isnan(x) || std::isnan(y);
}
//============================================================================
/// Test point in the line of point p1 and p2, use quick compare
///
/// @param p1
/// @param p2
///
/// @return bool
///
inline bool in_line(const Point2D &p1, const Point2D &p2, bool segment = true) const
{
bool inSegment = segment ? (between(x, p1.x, p2.x) && between(y, p1.y, p2.y)) : true;
if (!inSegment)
return false;
auto d1 = (x - p1.x) * (p2.y - y);
auto d2 = (y - p1.y) * (p2.x - x);
// bool inLine = utils::essentially_equal(d1, d2);
bool inLine = abs(d1 - d2) < 1e-6;
return inLine;
}
template <typename PT>
inline bool in_box(const std::vector<PT> &points) const
{
double xMin = std::nan("1");
double xMax = std::nan("1");
double yMin = std::nan("1");
double yMax = std::nan("1");
if (points.size() == 5)
{
auto &pointMin = points[0];
auto &pointMax = points[2];
xMin = pointMin.x;
xMax = pointMax.x;
yMin = pointMin.y;
yMax = pointMax.y;
}
else
{
std::vector<double> xElements, yElements;
std::for_each(std::execution::par_unseq, points.begin(), points.end(), [&xElements](const PT &p)
{ xElements.push_back(p.x); });
std::for_each(std::execution::par_unseq, points.begin(), points.end(), [&yElements](const PT &p)
{ yElements.push_back(p.y); });
auto xMinMax = std::minmax_element(std::execution::par_unseq, xElements.begin(), xElements.end());
auto yMinMax = std::minmax_element(std::execution::par_unseq, yElements.begin(), yElements.end());
xMin = *xMinMax.first;
xMax = *xMinMax.second;
yMin = *yMinMax.first;
yMax = *yMinMax.second;
}
bool betweenX = (xMin <= x) && (x <= xMax);
bool betweenY = (yMin <= y) && (y <= yMax);
return betweenX && betweenY;
}
template <typename PT>
inline bool in_box(const PT &p_min, const PT &p_max) const
{
double xMin = p_min.x;
double xMax = p_max.x;
double yMin = p_min.y;
double yMax = p_max.y;
bool betweenX = (xMin <= x) && (x <= xMax);
bool betweenY = (yMin <= y) && (y <= yMax);
return betweenX && betweenY;
}
template <typename PT>
inline bool in_polylines(const std::vector<PT> &points) const
{
if (points.size() < 2)
return false;
auto s = points.size();
for (int i = 0; i < s - 1; i++)
{
auto p1 = points[i];
auto p2 = points[i + 1];
if (in_line(p1, p2))
return true;
}
return false;
}
//============================================================================
/// Test point in the polygon of points
///
/// @param points, which to build polygon, included 1 duplicates point to build a closed polygon
/// @param controlPoints, test point in fault loose or strict, if >0 the point's corner points in fault must >= controlPoints
/// the node can be identified to a fault node
/// @param xSize, ySize: grid space size in x y coordinate
///
/// @return bool
///
template <class PT = Point2D>
bool in_polygon(const std::vector<PT> &points, const size_t controlPoints = 0, const double xSize = 0.0, const double ySize = 0.0) const
{
if (points.size() < 2)
{
// single point can't build a polygon
return false;
}
else if (points.size() == 2)
{
return in_line(points[0], points[1]);
}
else
{
// fault point box, used to control the fault point in fault or not
std::vector<PT> faultPointBox = {
{x - 0.5 * xSize, y - 0.5 * ySize},
{x - 0.5 * xSize, y + 0.5 * ySize},
{x + 0.5 * xSize, y + 0.5 * ySize},
{x + 0.5 * xSize, y - 0.5 * ySize}};
bool flag = false;
bool boxPointFlags[4] = {false * 4};
// use ray casting algorithm
// for (auto iter = points.begin(); iter != points.end() - 1; ++iter)
for (auto i = 0; i < points.size() - 1; i++)
{
auto p1 = points[i];
auto p2 = points[i + 1];
if (in_line(p1, p2) && controlPoints == 0)
{
// only loose test return turn while node on fault line
return true;
}
if ((dcmp(p1.y - y) > 0 != dcmp(p2.y - y) > 0) && dcmp(x - (y - p1.y) * (p1.x - p2.x) / (p1.y - p2.y) - p1.x) < 0)
{
flag = !flag;
}
if (controlPoints > 0)
{
for (int fbi = 0; fbi < 4; fbi++)
{
// test the fault box for fault point
auto p = faultPointBox[fbi];
if ((dcmp(p1.y - p.y) > 0 != dcmp(p2.y - p.y) > 0) && dcmp(p.x - (p.y - p1.y) * (p1.x - p2.x) / (p1.y - p2.y) - p1.x) < 0)
{
boxPointFlags[fbi] = !boxPointFlags[fbi];
}
}
}
}
if (controlPoints > 0)
{
size_t hits = 0;
for (int i = 0; i < 4; i++)
{
if (boxPointFlags[i])
hits++;
}
flag = flag && (hits >= controlPoints);
};
return flag;
}
return false;
}
inline std::string str() const
{
std::stringstream ss;
ss << std::fixed << std::setprecision(4);
ss << "(" << x << ", " << y << ")";
return ss.str();
}
inline size_t hash() const
{
std::stringstream ss;
ss << std::fixed << std::setprecision(6) << x << "," << y;
std::string s = ss.str();
size_t h = std::hash<std::string>{}(s);
return h;
}
inline size_t hash2D() const
{
return hash();
}
friend std::ostream &operator<<(std::ostream &stream, const Point2D &point)
{
stream << point.str();
return stream;
}
};
inline double distance(const Point2D &p1, const Point2D &p2) { return p1.distance(p2); }
inline std::pair<double, double> line_slope_intercept(const Point2D &p1, const Point2D &p2)
{
if (abs(p2.x - p1.x) > 1e-6)
{
double slope = (p2.y - p1.y) / (p2.x - p1.x);
double intercept = p1.y - slope * p1.x;
return std::make_pair(slope, intercept);
}
else
return std::make_pair(INFINITY, INFINITY);
}
inline Point2D intersection_point(const std::pair<double, double> &l1_si, const std::pair<double, double> &l2_si)
{
double x = (l2_si.second - l1_si.second) / (l1_si.first - l2_si.first);
double y = l1_si.first * x + l1_si.second;
return Point2D(x, y);
}
struct AIS_EXPORT Point3D : public ais::Point2D
{
public:
double z{0};
Point3D() : z(std::nan("1")) {}
Point3D(const Point2D &p) : Point2D(p), z(INFINITY) {}
Point3D(const Point2D &p, double z) : Point2D(p), z(z) {}
Point3D(double x_, double y_) : Point2D(x_, y_), z(INFINITY) {}
Point3D(double x_, double y_, double z_) : Point2D(x_, y_), z(z_) {}
Point3D(const char *buffer)
{
char temp_buff[64] = { 0 };
std::string temp_str;
double ret[3] = { std::nan("1"), std::nan("1") , std::nan("1") };
int index = 0;
const char* buffIndex = buffer;
try
{
while (*buffIndex != 0x0)
{
const char* result = strchr(buffIndex, ',');
if (result != NULL)
{
memset(temp_buff, 0, 64);
strncpy(temp_buff, buffIndex, result - buffIndex);
temp_str = std::string(temp_buff);
ret[index++] = std::stod(temp_str);
buffIndex = result + 1;
}
else
{
if (index < 1)
// not a valid string
ret[0] = ret[1] = ret[2] = std::nan("1");
else
{
// finially part
memset(temp_buff, 0, 64);
strcpy(temp_buff, buffIndex);
temp_str = std::string(temp_buff);
ret[index++] = std::stod(temp_str);
}
break;
}
}
}
catch (const std::invalid_argument& e)
{
//std::cout << "Build point failed: " << str << std::endl;
ret[0] = ret[1] = ret[2] = std::nan("1");
}
x = ret[0]; y = ret[1]; z = ret[2];
}
inline double distance(const Point2D &p) const
{
double dx = x - p.x;
double dy = y - p.y;
return std::sqrt(dx * dx + dy * dy);
}
inline double distance(double px, double py) const
{
double dx = x - px;
double dy = y - py;
return std::sqrt(dx * dx + dy * dy);
}
inline double distance2D(const Point3D &p) const
{
return distance(p.x, p.y);
}
inline double distance(const Point3D &p) const
{
double dx = x - p.x;
double dy = y - p.y;
double dz = z - p.z;
// skip delta z while some z is nan or infinity
dz = std::isnan(dz) || std::isinf(dz) ? 0 : dz;
return std::sqrt(dx * dx + dy * dy + dz * dz);
}
inline bool operator<(const Point3D &rhs) const
{
return (x < rhs.x - 1e-6) || (utils::essentially_equal(x, rhs.x) && y < rhs.y);
}
inline bool operator==(const Point3D &rhs) const
{
// return utils::essentially_equal(x, rhs.x, std::numeric_limits<double>::epsilon()) &&
// utils::essentially_equal(y, rhs.y, std::numeric_limits<double>::epsilon()) &&
// utils::essentially_equal(z, rhs.z, std::numeric_limits<double>::epsilon());
return (std::abs(x - rhs.x) < 1e-6) && (std::abs(y - rhs.y) < 1e-6) && (std::abs(z - rhs.z) < 1e-6);
}
//============================================================================
/// linear interpolation z value for specified point which should be on the line of 2 points
///
/// @param points p1 and p2
///
/// @return double point z value
///
double linear_interpolation_z_value(const Point3D &p1, const Point3D &p2)
{
double ret = INFINITY;
if ((p1.distance(p2) / distance(p1) < 0.0001) || (p1.distance(p2) / distance(p2) < 0.0001))
// return infinity while the p1 and p2 are too close, but too far away target p
return ret;
if (in_line(p1, p2))
// p is in the statement of p1 and p2
ret = p1.z + (p2.z - p1.z) * distance(p1) / p1.distance(p2);
else if (in_line(p1, p2, false))
{
// p is not in the statement of p1 and p2
if (distance(p1) > distance(p2))
{
ret = p2.z - (p1.z - p2.z) * distance(p2) / p1.distance(p2);
}
else
{
ret = p1.z + (p1.z - p2.z) * distance(p1) / p1.distance(p2);
}
}
z = ret;
return ret;
}
//============================================================================
/// interpolation z value for this point by other 3 points
///
/// p1 p1
/// | \ / \
/// p-----+-----p3 or p2 --p--+
/// | / \ \
/// p2 \ \
/// \ p3
///
/// @param points p1 p2 and p3
///
/// @return double point z value
///
double point_interpolate(const Point3D &p1, const Point3D &p2, const Point3D &p3)
{
double ret = INFINITY;
// get the slope and intercept pairs for p1/p2 and p/p3
auto p12_ls = line_slope_intercept(p1, p2);
auto pp3_ls = line_slope_intercept(*this, p3);
if (abs(p12_ls.first - pp3_ls.first) < 1e-6)
{
// line p1/p2 and p/p3 have same slope, which means two line are parallel,
// that will NOT get the interpolate in this condition
return INFINITY;
}
if (std::isinf(p12_ls.first) || std::isinf(pp3_ls.first))
{
// one line is vertical line
if ((std::abs(p1.x - p2.x) < 1e-6) && (std::abs(p3.x - x) > 1e-6))
{
// line p1--p2 is vertical line
auto x = p1.x;
auto y = pp3_ls.first * x + pp3_ls.second;
Point3D pi{x, y, INFINITY};
pi.linear_interpolation_z_value(p1, p2);
ret = linear_interpolation_z_value(pi, p3);
}
else if ((std::abs(p1.x - p2.x) > 1e-6) && (std::abs(p3.x - x) < 1e-6))
{
// line p--p3 is vertical line
auto x = p3.x;
auto y = p12_ls.first * x + p12_ls.second;
Point3D pi{x, y, INFINITY};
pi.linear_interpolation_z_value(p1, p2);
ret = linear_interpolation_z_value(pi, p3);
}
}
else
{
// get the intersection point for line p1/p2 and p/p3, which is on both line
Point3D pi{intersection_point(p12_ls, pp3_ls)};
pi.linear_interpolation_z_value(p1, p2);
ret = linear_interpolation_z_value(pi, p3);
}
return ret;
}
//============================================================================
/// interpolation z value for this point by point with Gaussian operation
///
/// point
/// /
/// wGaussian = exp((x * x + y * y) / (- u * weight)<29><>
/// /
/// p
///
/// @param point source point
///
/// @return double interpolate point z value
///
inline double gaussian_interpolation_z_value(double pointDistance, double pointZ, size_t rows, size_t columns, size_t cornerWeight = 64)
{
double ret = INFINITY;
if (!std::isnan(pointZ))
{
double u = std::sqrt(rows * rows + columns * columns);
double weight = exp(pointDistance * pointDistance / (-u * cornerWeight));
ret = pointZ * weight;
}
return ret;
}
inline bool has_value() const
{
return Point2D::has_value() && (!std::isnan(z));
}
inline bool isNan() const
{
return std::isnan(z) || std::isnan(x) || std::isnan(y);
}
inline std::string str() const
{
std::stringstream ss;
ss << std::fixed << std::setprecision(4);
ss << "(" << x << ", " << y << ", " << z << ")";
return ss.str();
}
inline size_t hash2D() const
{
size_t h1 = std::hash<double>{}(x);
size_t h2 = std::hash<double>{}(y);
return h1 ^ (h2 << 1);
}
inline size_t hash() const
{
size_t h1 = std::hash<double>{}(x);
size_t h2 = std::hash<double>{}(y);
size_t h3 = std::hash<double>{}(z);
return h1 ^ (h2 << 1) ^ (h3 << 2);
}
friend std::ostream &operator<<(std::ostream &stream, const Point3D &point)
{
stream << point.str();
return stream;
}
};
using Point = AIS_EXPORT ais::Point2D;
using PointXYZ = AIS_EXPORT ais::Point3D;
} // namespace ais