定义一下,什么是“点到折线的最近距离”:
如果折线是由无线细分的点构成的,能找到一个点,与待求点的距离最近,这个最近距离是所需要的。
但是实际上,一条折线总是由有限的点构成的,计算与构成折线的点的最近距离,并不意味着就是点到折线的最近距离。
这时候,一种方式是对折线进行根据精度要求进行密集采样,计算出最近的点的距离;
另外,就是求取到构成折线的线段的最近的距离。
为了效率考虑,往往用第二种。
第二种方法也有它的问题:点到所有的线段都会有投影距离,投影距离最小的未必是对的,因为投影点可能在线段外部。甚至这个点到所有线段的投影点,都会在线段的外部,而且是很常见的。
所以,又需要去做进一步的处理:如果能找到投影点在线段内部的,那么点与这个投影点的距离就是所求的数值了;如果没有找到,且待求点确实是在折线内部,那么就要去判断投影点,与哪个线段的端点距离最小,就这个作为返回,如果要精细,就可以在这个线段与相邻线段,进行密集采样得到一系列点,找出最近的点的距离;如果待求点在外部,那么就找最近的线段的投影距离(实际上可以归纳到第二种情况)。
在应用中,是为了求取两条基本平行的折线的间隔距离,此时,可以结合线段的平行情况来去计算,而不是盲目的计算点到折线的距离。
一段测试代码:
// 返回点到折线上的投影点
std::tuple<Point3D, double, size_t> Polyline3D::GetProjectedPoint(
const Point3D& point) const {
bool is_within = this->IsWithin(point);
if (is_within) {
bool inside_line_seg = false;
auto pt = point.ToBoostPoint3D();
Point3D projectedPoint;
double minDistance = std::numeric_limits<double>::max();
size_t choose_ln_seg_idx = 0;
Point3D all_projectedPoint;
double all_minPrjDistance = std::numeric_limits<double>::max();
double to_end_point_min_distance = std::numeric_limits<double>::max();
size_t choose_ln_seg_idx_outside = 0;
for (size_t i = 0; i < polyline_.size() - 1; ++i) {
const auto& startPoint = polyline_[i];
const auto& endPoint = polyline_[i + 1];
LineSegment3D ln(Point3D(bg::get<0>(startPoint), bg::get<1>(startPoint), bg::get<2>(startPoint)),
Point3D(bg::get<0>(endPoint), bg::get<1>(endPoint), bg::get<2>(endPoint)));
auto pjp = ln.GetProjectedPoint(point);
double distance = point.Distance(pjp);
if (ln.IsInside(point)) {
inside_line_seg = true;
// 更新最小距离和投影点
if (distance < minDistance) {
choose_ln_seg_idx = i;
minDistance = distance;
projectedPoint = pjp;
}
} else {
double each_min_dist = ln.GetMinDistance(point);
if (each_min_dist < to_end_point_min_distance) {
all_minPrjDistance = distance;
all_projectedPoint = pjp;
to_end_point_min_distance = each_min_dist;
choose_ln_seg_idx_outside = i;
}
}
}
if (inside_line_seg) {
return std::make_tuple(projectedPoint, minDistance, choose_ln_seg_idx);
} else {
return std::make_tuple(all_projectedPoint, all_minPrjDistance, choose_ln_seg_idx_outside);
}
//
} else {
auto sp = (polyline_[0]);
auto ep = polyline_.back();
Point3D spt(bg::get<0>(sp), bg::get<1>(sp), bg::get<2>(sp));
Point3D ept(bg::get<0>(ep), bg::get<1>(ep), bg::get<2>(ep));
LineSegment3D ln;
size_t ass_ln_seg_idx = 0;
if (spt.Distance(point) < ept.Distance(point)) {
GetFirstLineSegment(ln);
} else {
GetLastLineSegment(ln);
ass_ln_seg_idx = polyline_.size() - 1;
}
auto prj = ln.GetProjectedPoint(point);
auto dist = point.Distance(prj);
return std::make_tuple(prj, dist, ass_ln_seg_idx);
}
}