How to find two points that form closest distance between two rectangles?

折月煮酒 提交于 2021-02-17 05:05:24

问题


I'm trying to find algorithm that will find two points that represent closest distance between two rectangles. Like points C and J that form smallest distance on image below:

I'm trying to not reinvent the wheel here and use something that is already battletested like boost::geometry::distance, but it only return distance and not also the points.


回答1:


Not making things overly generic (by assuming floating point coordinates and cartesian coordinate system), here's an implementation of point-to-line-segment distance that returns the projected point as well as the distance:

struct DistancePoint {
    double distance;
    P projected_point;
};

template <typename Strategy = bg::strategy::distance::pythagoras<> >
DistancePoint point_to_segment(P const& p, P const& p1, P const& p2) {
    P v = p2, w = p;
    bg::subtract_point(v, p1);
    bg::subtract_point(w, p1);

    auto const c1 = bg::dot_product(w, v);
    if (c1 <= 0)  return { Strategy::apply(p, p1), p1 };

    auto const c2 = bg::dot_product(v, v);
    if (c2 <= c1) return { Strategy::apply(p, p2), p2 };

    P prj = p1;
    bg::multiply_value(v, c1/c2);
    bg::add_point(prj, v);

    return { Strategy::apply(p, prj), prj };
}

Now you can use it with your geometries. I didn't want to satisfy all the criteria for the distance-strategy concept, so you can't use the above with boost::geometry::distance.

But since your input geometries have low point counts, you might get away with "brute-forcing" (without requiring the library internal closest_feature selection):

R a = gen_rect(),
  b = gen_rect();

// make sure a and b don't overlap (distance > 0)
while (!bg::disjoint(a,b)) { b = gen_rect(); }

std::cout
    << wkt(a) << "\n"
    << wkt(b) << "\n"
    << bg::distance(a, b) << " apart\n";

DistancePoint nearest;
P const* which = nullptr;

for (auto& [a,b] : { std::tie(a,b), std::tie(b,a) } ) {
    auto segments = boost::make_iterator_range(bg::segments_begin(a), bg::segments_end(a));
    auto points   = boost::make_iterator_range(bg::points_begin(b), bg::points_end(b));

    for (auto&& pq : segments) {
        for (auto&& r : points) {
            auto d = point_to_segment(r, *pq.first, *pq.second);

            if (!which || d.distance < nearest.distance) {
                which = &r;
                nearest = d;
            }
        }
    }
}

std::cout << wkt(which) << " at " << nearest.distance << " from " << wkt(nearest.projected_point) << "\n";

DEMO

Live On Coliru

#include <boost/geometry.hpp>
#include <boost/geometry/util/range.hpp>
#include <boost/geometry/io/io.hpp>
#include <boost/geometry/algorithms/assign.hpp>
#include <boost/geometry/algorithms/convex_hull.hpp>
#include <boost/geometry/algorithms/point_on_surface.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/range/adaptors.hpp>
#include <iostream>
#include <fstream>

namespace bg = boost::geometry;
namespace bgm = boost::geometry::model;
using namespace boost::adaptors;
using bg::wkt;

using P = bgm::d2::point_xy<double>;
using B = bgm::box<P>;
using S = bgm::segment<P>;
using R = bgm::polygon<P>;

R gen_rect(); // generates a random rectangle

namespace {
    struct DistancePoint {
        double distance;
        P projected_point;
    };

    // after strategy::distance::projected_point<>
    template <typename Strategy = bg::strategy::distance::pythagoras<> >
    DistancePoint point_to_segment(P const& p, P const& p1, P const& p2) {
        P v = p2, w = p;
        bg::subtract_point(v, p1);
        bg::subtract_point(w, p1);

        auto const c1 = bg::dot_product(w, v);
        if (c1 <= 0)  return { Strategy::apply(p, p1), p1 };

        auto const c2 = bg::dot_product(v, v);
        if (c2 <= c1) return { Strategy::apply(p, p2), p2 };

        P prj = p1;
        bg::multiply_value(v, c1/c2);
        bg::add_point(prj, v);

        return { Strategy::apply(p, prj), prj };
    }
}

int main() {
    std::cout << std::setprecision(2);

    for (auto i = 0; i<10; ++i) {
        R a = gen_rect(),
          b = gen_rect();

        // make sure a and b don't overlap (distance > 0)
        while (!bg::disjoint(a,b)) { b = gen_rect(); }

        std::cout
            << wkt(a) << "\n"
            << wkt(b) << "\n"
            << bg::distance(a, b) << " apart\n";

        DistancePoint nearest;
        P const* which = nullptr;

        for (auto& [a,b] : { std::tie(a,b), std::tie(b,a) } ) {
            auto segments = boost::make_iterator_range(bg::segments_begin(a), bg::segments_end(a));
            auto points   = boost::make_iterator_range(bg::points_begin(b), bg::points_end(b));

            for (auto&& pq : segments) {
                for (auto&& r : points) {
                    auto d = point_to_segment(r, *pq.first, *pq.second);

                    if (!which || d.distance < nearest.distance) {
                        which = &r;
                        nearest = d;
                    }
                }
            }
        }

        std::cout << wkt(which) << " at " << nearest.distance << " from " << wkt(nearest.projected_point) << "\n";

        {
            std::ofstream svg("output" + std::to_string(i) + ".svg");
            boost::geometry::svg_mapper<P> mapper(svg, 400, 400, "style='fill-opacity:1;fill:rgb(255,255,255)'");
            mapper.add(a);
            mapper.add(b);
            S dline {*which, nearest.projected_point};
            mapper.add(dline);

            mapper.map(a, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2");
            mapper.map(b, "fill-opacity:0.5;fill:rgb(204,153,0);stroke:rgb(202,153,0);stroke-width:2");
            mapper.map(dline, "stroke-dasharray:1,1;stroke:rgb(255,0,0);stroke-width:1");
        }
    }
}

// details for generating the rectangles
#include <boost/geometry/strategies/transform/matrix_transformers.hpp>
#include <random>
std::mt19937 prng { std::random_device{}() };

static auto rand(double b, double e) { 
    return std::uniform_real_distribution<double>(b, e)(prng);
}

R gen_rect() {
    B initial {{0, 0}, { rand(0.1, 1), rand(0.1, 1) } };
    R raw, rect; // todo rotate and stuff
    bg::assign(raw, initial);

    using namespace bg::strategy::transform;
    auto rot   = rand(-M_PI, +M_PI);
    auto scale = rand(1, 3);
    auto x     = rand(-5, 5),
         y     = rand(-5, 5);

    matrix_transformer<double, 2, 2> xfrm(
         scale* cos(rot), scale*sin(rot), x,
         scale*-sin(rot), scale*cos(rot), y,
                       0,              0, 1);

    bg::transform(raw, rect, xfrm);
    bg::correct(rect);
    return rect;
}

This generates some randomized scenes like:



来源:https://stackoverflow.com/questions/62138176/how-to-find-two-points-that-form-closest-distance-between-two-rectangles

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!