HdLibMath.cpp 10 KB
Newer Older
oscar's avatar
oscar committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246

#include "HdLibMath.h"

#include <algorithm>

#include "CoordSys.h"

using namespace jf;

/**
 * 判断一个点 是否在左右边界点所组成的区域内
 *
 * @param ptPoint
 * @param cvctvdLeftEdgeGeo
 * @param cvctvdRightEdgeGeo
 * @param isReverseFlag  左右边界点是否是逆向存储 逆向存储则为true
 * @return
 */
bool HdLibMath::IsInRecArea(const Point &ptPoint, const std::vector<std::vector<double>> &cvctvdLeftEdgeGeo, const std::vector<std::vector<double>> &cvctvdRightEdgeGeo, bool isReverseFlag) {
    int nRectCnt = std::min(cvctvdLeftEdgeGeo.size(), cvctvdRightEdgeGeo.size()) - 1;
    int nTriCnt = std::abs((int)cvctvdLeftEdgeGeo.size() - (int)cvctvdRightEdgeGeo.size());

    bool bIsInArea = false;

    XYZ xyz0 = P2XYZ(ptPoint);

    // 先将edge分成一个个的小矩形, 然后柴分成两个三角形
    for (int i = 0; (i < nRectCnt) && (false == bIsInArea); ++i) {
        if (!isReverseFlag) {
            XYZ xyz1 = P2XYZ(cvctvdLeftEdgeGeo[i]);
            XYZ xyz2 = P2XYZ(cvctvdLeftEdgeGeo[i + 1]);
            XYZ xyz3 = P2XYZ(cvctvdRightEdgeGeo[i + 1]);
            XYZ xyz4 = P2XYZ(cvctvdRightEdgeGeo[i]);
            bool bIsInArea1 = IsInTriangle(xyz1, xyz2, xyz4, xyz0);
            bool bIsInArea2 = IsInTriangle(xyz2, xyz3, xyz4, xyz0);
            if (bIsInArea1 || bIsInArea2) return true;
        } else {
            XYZ xyz1 = P2XYZ(cvctvdLeftEdgeGeo[cvctvdLeftEdgeGeo.size() - 1 - i]);
            XYZ xyz2 = P2XYZ(cvctvdLeftEdgeGeo[cvctvdLeftEdgeGeo.size() - 1 - 1 - i]);
            XYZ xyz3 = P2XYZ(cvctvdRightEdgeGeo[i + 1]);
            XYZ xyz4 = P2XYZ(cvctvdRightEdgeGeo[i]);
            bool bIsInArea1 = IsInTriangle(xyz1, xyz2, xyz4, xyz0);
            bool bIsInArea2 = IsInTriangle(xyz2, xyz3, xyz4, xyz0);
            if (bIsInArea1 || bIsInArea2) return true;
        }
    }
    //如果左右边界点的数量不同,则点数多的一方都和少的一方的最后一点连接
    if (nTriCnt != 0) {
        const std::vector<std::vector<double>> &cvctvdLongerEdgeGeo = cvctvdLeftEdgeGeo.size() > cvctvdRightEdgeGeo.size() ? cvctvdLeftEdgeGeo : cvctvdRightEdgeGeo;
        const std::vector<std::vector<double>> &cvctvdShotterEdgeGeo = cvctvdLeftEdgeGeo.size() < cvctvdRightEdgeGeo.size() ? cvctvdLeftEdgeGeo : cvctvdRightEdgeGeo;
        for (int i = nRectCnt; i < nRectCnt + nTriCnt; ++i) {
            if (!isReverseFlag) {
                XYZ xyzA = P2XYZ(cvctvdShotterEdgeGeo[nRectCnt]);
                XYZ xyzB = P2XYZ(cvctvdLongerEdgeGeo[i]);
                XYZ xyzC = P2XYZ(cvctvdLongerEdgeGeo[i + 1]);
                bIsInArea = IsInTriangle(xyzA, xyzB, xyzC, xyz0);
                if (bIsInArea) return true;
            } else {
                if (cvctvdLeftEdgeGeo.size() < cvctvdRightEdgeGeo.size()) {
                    XYZ xyzA = P2XYZ(cvctvdShotterEdgeGeo[0]);
                    XYZ xyzB = P2XYZ(cvctvdLongerEdgeGeo[i]);
                    XYZ xyzC = P2XYZ(cvctvdLongerEdgeGeo[i + 1]);
                    bIsInArea = IsInTriangle(xyzA, xyzB, xyzC, xyz0);
                    if (bIsInArea) return bIsInArea;
                } else {
                    XYZ xyzA = P2XYZ(cvctvdShotterEdgeGeo[nRectCnt]);
                    XYZ xyzB = P2XYZ(cvctvdLongerEdgeGeo[cvctvdLongerEdgeGeo.size() - 1 - i]);
                    XYZ xyzC = P2XYZ(cvctvdLongerEdgeGeo[cvctvdLongerEdgeGeo.size() - 1 - i - 1]);
                    bIsInArea = IsInTriangle(xyzA, xyzB, xyzC, xyz0);
                    if (bIsInArea) return bIsInArea;
                }
            }
        }
    }

    return bIsInArea;
}

bool HdLibMath::IsInTriangle(const Point &cptPoint, const Point &cptA, const Point &cptB, const Point &cptC) {
    //类定义:二维向量
    struct Vector2d {
        double x_;
        double y_;

        Vector2d(double x, double y) : x_(x), y_(y) {}
        Vector2d() : x_(0), y_(0) {}

        //二维向量叉乘, 叉乘的结果其实是向量,方向垂直于两个向量组成的平面,这里我们只需要其大小和方向
        double CrossProduct(const Vector2d vec) { return x_ * vec.y_ - y_ * vec.x_; }

        //二维向量点积
        double DotProduct(const Vector2d vec) { return x_ * vec.x_ + y_ * vec.y_; }

        //二维向量减法
        Vector2d Minus(const Vector2d vec) const { return Vector2d(x_ - vec.x_, y_ - vec.y_); }
    };

    XYZ xyzX = P2XYZ(cptPoint);
    XYZ xyzA = P2XYZ(cptA);
    XYZ xyzB = P2XYZ(cptB);
    XYZ xyzC = P2XYZ(cptC);

    // Vector2d PA = Vector2d(cptA.lon, cptA.lat).Minus(Vector2d(cptPoint.lon, cptPoint.lat));
    // Vector2d PB = Vector2d(cptB.lon, cptB.lat).Minus(Vector2d(cptPoint.lon, cptPoint.lat));
    // Vector2d PC = Vector2d(cptC.lon, cptC.lat).Minus(Vector2d(cptPoint.lon, cptPoint.lat));
    Vector2d PA = Vector2d(xyzA.x, xyzA.y).Minus(Vector2d(xyzX.x, xyzX.y));
    Vector2d PB = Vector2d(xyzB.x, xyzB.y).Minus(Vector2d(xyzX.x, xyzX.y));
    Vector2d PC = Vector2d(xyzC.x, xyzC.y).Minus(Vector2d(xyzX.x, xyzX.y));
    double t1 = PA.CrossProduct(PB);
    double t2 = PB.CrossProduct(PC);
    double t3 = PC.CrossProduct(PA);
    return t1 * t2 >= 0 && t1 * t3 >= 0;
}

bool HdLibMath::IsInTriangle(const XYZ &cxyz1, const XYZ &cxyz2, const XYZ &cxyz3, const XYZ &cxyz0) {
    // 链接:https://leetcode-cn.com/circle/discuss/7OldE4/
    auto func_Product = [](const XYZ &cxyz1, const XYZ &cxyz2, const XYZ &cxyz3) {
        //首先根据坐标计算p1p2和p1p3的向量,然后再计算叉乘
        // p1p2 向量表示为 (p2.x-p1.x,p2.y-p1.y)
        // p1p3 向量表示为 (p3.x-p1.x,p3.y-p1.y)
        return (cxyz2.x - cxyz1.x) * (cxyz3.y - cxyz1.y) - (cxyz2.y - cxyz1.y) * (cxyz3.x - cxyz1.x);
    };

    //保证p1,p2,p3是逆时针顺序
    if (func_Product(cxyz1, cxyz2, cxyz3) < 0) return IsInTriangle(cxyz1, cxyz3, cxyz2, cxyz0);
    if (func_Product(cxyz1, cxyz2, cxyz0) > 0 && func_Product(cxyz2, cxyz3, cxyz0) > 0 && func_Product(cxyz3, cxyz1, cxyz0) > 0) return true;
    return false;
}

/**
 * 求一个点 在点序列中的 垂点
 * @param ptPoint 输入的某一个点
 * @param vctvdGeoLink 点序列
 * @param ptOutPoint 输出的垂足点坐标
 * @param dOutDistance 垂线长度
 * @param nOutNxtIndex 如果将垂足点放入到点序列中,则这个垂足点在序列中的Index
 * @return
 */
bool HdLibMath::GetPerpendicularFoot(const Point &ptPoint, const std::vector<std::vector<double>> &vctvdGeoLink, Point &ptOutPoint, double &dOutDistance, int &nOutNxtIndex) {
    std::vector<XYZ> xyzs{vctvdGeoLink.size(), {0, 0, 0}};
    std::transform(vctvdGeoLink.begin(), vctvdGeoLink.end(), xyzs.begin(), [](const std::vector<double> &p) { return P2XYZ(p); });

    XYZ inxyz = P2XYZ(ptPoint);
    bool have_per = false;
    Point per_p;
    double per_d = std::numeric_limits<double>::max();
    int nNxtIndex = 0;
    for (int i = 0, j = 1; j < xyzs.size(); ++i, ++j) {
        XYZ p1 = xyzs[i];
        XYZ p2 = xyzs[j];
        XYZ pp1 = inxyz;
        XYZ pp2(pp1.x + (p1.y - p2.y), pp1.y - (p1.x - p2.x), inxyz.z);

        // 跨立实验
        bool valid = (Cross(p2 - pp1, pp2 - pp1) * Cross(pp2 - pp1, p1 - pp1) > 0);
        if (!valid) continue;

        double newDist = abs(Cross(p2 - pp1, p1 - pp1) / (p1 - p2).length_xy());
        if (newDist < per_d) {
            per_d = newDist;
            nNxtIndex = j;

            double a = p2.y - p1.y;
            double b = p1.x - p2.x;
            double c = p2.x * p1.y - p1.x * p2.y;
            double m = pp1.x;
            double n = pp1.y;
            XYZ out_xyz((b * b * m - a * b * n - a * c) / (a * a + b * b), (a * a * n - a * b * m - b * c) / (a * a + b * b), (vctvdGeoLink[i][2] + vctvdGeoLink[j][2]) / 2);
            per_p = XYZ2P(out_xyz);
            have_per = true;
        }
    }
    // if (!ret)
    // {
    Point near_p;
    double near_d = std::numeric_limits<double>::max();
    for (const XYZ &p : xyzs) {
        double d = DXYZ(p, inxyz);
        if (d <= near_d) {
            near_p = Point(XYZ2P(p));
            near_d = d;
        }
    }
    // }
    // assert(ret);
    if (!have_per || (have_per && per_d > near_d * 5)) {
        ptOutPoint = near_p;
        dOutDistance = near_d;
    } else {
        ptOutPoint = per_p;
        dOutDistance = per_d;
    }
    // 垂足在vctvdGeoLink之外,或者所给点就是垂足
    if (nNxtIndex == 0) {
        double dMinDistance = std::numeric_limits<double>::max();
        double dCurDis = 0.0;
        int nNearestNeighborIndex = 0;
        for (int i = 0; i < vctvdGeoLink.size(); ++i) {
            dCurDis = PointsDis(ptOutPoint, vctvdGeoLink.at(i));
            if (dMinDistance > dCurDis) {
                dMinDistance = dCurDis;
                nNearestNeighborIndex = i;
                ptOutPoint = vctvdGeoLink.at(i);
            }
        }
        // 垂足点在vctvdGeoLink的开头
        if (nNearestNeighborIndex == 0) {
            nNxtIndex = 1;
        } else {
            nNxtIndex = nNearestNeighborIndex;
        }
    }
    nOutNxtIndex = nNxtIndex;
    return true;
}

/**
 * 求垂足坐标(不返回序列Index位置)
 * @return
 */
bool HdLibMath::GetPerpendicularFoot(const Point &ptPoint, const std::vector<std::vector<double>> &vctvdGeoLink, Point &ptOutPoint, double &dOutDistance) {
    int nNxtIndex = 0;
    return HdLibMath::GetPerpendicularFoot(ptPoint, vctvdGeoLink, ptOutPoint, dOutDistance, nNxtIndex);
}
/**
 * 求某个点 距离 某点序列最近的点的距离
 * @return 返回距离最近的点在序列中的Index和坐标值
 */
bool HdLibMath::GetNearestPointFromArray(const Point &ptPoint, const std::vector<std::vector<double>> &vctvdGeoLink, Point &ptOutPoint, double &dOutDistance, int &nOutIndex) {
    Point ptTmpPoint = {};
    double dMinDistance = std::numeric_limits<double>::max();
    int nTmpIndex = 0;
    for (size_t i = 0; i < vctvdGeoLink.size(); ++i) {
        Point ptGeo = Point(vctvdGeoLink[i]);
        double d = PointsDis(ptGeo, ptPoint);
        if (d < dMinDistance) {
            ptTmpPoint = ptGeo;
            dMinDistance = d;
            nTmpIndex = i;
        }
    }
    ptOutPoint = ptTmpPoint;
    dOutDistance = dMinDistance;
    nOutIndex = nTmpIndex;
    return true;
}