2

我目前正在使用 boost::polygon::detail::resize() 函数将多边形的轮廓放大或缩小特定大小。这运行良好并给出了正确的结果。

现在在某些情况下,根据输入形状,生成的多边形(红色)包含彼此非常接近的矢量线(黑线):

多边形

我想要做的是在这样一个多边形的所有相邻线上有一个最小距离。对于此示例,这意味着需要修改生成的多边形并添加矢量线(蓝色),以替换其下方的红线。这当然会改变多边形的形状,但会使其符合最小距离规则。在上面给出的示例图片中,新的蓝线将具有所需最小距离的长度。

我的问题:是否有任何可用的增强功能可以完成这项工作?如果是:哪些以及如何使用它们?

谢谢!

4

1 回答 1

2

使用以下方法将您的图像转换为代码:

在此处输入图像描述

那是:

Point
    A(-25.74,    2.5),
    B( 14.96,  12.96),
    C( 31.07,  -2.16),
    D(    37, -27.74),
    E( 29.37, -51.63),
    F( 46.62, -25.48),
    G( 48.17,   3.63),
    H( 41.53,  20.31),
    I( 77.57,  32.04);

String ls{A, B, C, D, E, F, G, H, I};

使用

using Point  = bgm::d2::point_xy<double>;
using String = bgm::linestring<Point>;

让我们重现角度测量:

住在科利鲁

#include <boost/geometry.hpp>
#include <boost/geometry/views/segment_view.hpp>
#include <boost/geometry/geometries/point_xyz.hpp>
#include <iostream>
#include <fstream>

namespace bg = boost::geometry;
namespace bgm = bg::model;

using Point  = bgm::d2::point_xy<double>;
using String = bgm::linestring<Point>;
using bg::wkt;

static inline auto angle_rad(Point a, Point b) {
    bg::subtract_point(a, b);
    return atan2(a.y(), a.x());
}

int main() 
{
    // clang-format off
    Point
        A(-25.74,    2.5),
        B( 14.96,  12.96),
        C( 31.07,  -2.16),
        D(    37, -27.74),
        E( 29.37, -51.63),
        F( 46.62, -25.48),
        G( 48.17,   3.63),
        H( 41.53,  20.31),
        I( 77.57,  32.04);
    // clang-format on

    String ls{A, B, C, D, E, F, G, H, I};
    if (std::string reason; !bg::is_valid(ls, reason)) {
        std::cout << reason << "\n";
        bg::correct(ls);
    }

    std::cout << wkt(ls) << "\n";

    auto desc_p = [&ls](Point const& p) {
        static auto names = "ABCDEFGHIJ";
        assert(not std::less<>{}(&ls.back(), &p));
        assert(not std::less<>{}(&p, &ls.front()));

        return std::string(names + (&p - &ls.front()), 1);
    };
    auto desc = [=](auto it) {
        return desc_p(*it->first) + desc_p(*it->second);
    };

    assert(ls.size() > 1);
    auto a = bg::segments_begin(ls), b = std::next(a), e = bg::segments_end(ls);

    for (; b != e; ++a, ++b) {
        // segment pair *a and *b
        auto pointing_angle = [](auto it) {
            return angle_rad(*it->first, *it->second);
        };
        auto aa = pointing_angle(a);
        auto ab = pointing_angle(b);
        auto deg = [](auto rad) { return rad / M_PI * 180; };
        std::cout << "angle " << deg(fmod(ab - aa + 3 * M_PI, 2 * M_PI))
                  << " between "                         //
                  << desc(a) << " " << deg(aa) << " vs " //
                  << desc(b) << " " << deg(ab) << "\n";
    }

    {
        // Declare a stream and an SVG mapper
        std::ofstream svg("output.svg");
        bg::svg_mapper<Point> mapper(svg, 400, 400);

        // Add geometries such that all these geometries fit on the map
        mapper.add(ls);

        mapper.map(ls, "fill-opacity:0.3;fill:rgb(51,0,0);stroke:rgb(51,0,0);stroke-width:1");
        for (auto& p : ls)
            mapper.text(p, desc_p(p), "");
    }
}

印刷

LINESTRING(-25.74 2.5,14.96 12.96,31.07 -2.16,37 -27.74,29.37 -51.63,46.62 -25.48,48.17 3.63,41.53 20.31,77.57 32.04)
angle 122.402 between AB -165.587 vs BC 136.816
angle 146.236 between BC 136.816 vs CD 103.052
angle 149.236 between CD 103.052 vs DE 72.2875
angle 344.301 between DE 72.2875 vs EF -123.411
angle 210.363 between EF -123.411 vs FG -93.0479
angle 204.754 between FG -93.0479 vs GH -68.2934
angle 86.322 between GH -68.2934 vs HI -161.971

写作的副作用是output.svg包含在此处输入图像描述

执行截断

检测到锐角,我们可以检查腿之间的距离超过 所需的截止长度min_distance

auto seg_angle = [](auto it) {
    return angle_rad(*it->first, *it->second);
};
auto rel      = fmod(seg_angle(b) - seg_angle(a) + 3 * M_PI, 2 * M_PI);
auto inner    = fabs(2 * M_PI - rel);
bool is_sharp = inner < M_PI / 2;

if (is_sharp) {
    std::cout << " ---- sharp angle, min_distance: " << min_distance
              << " (" << color << ")\n";
    auto deg = [](auto rad) { return rad / M_PI * 180; };
    std::cout << "angle " << deg(inner) << " between " << desc(a) << " and " << desc(b) << "\n";

    auto length_a = bg::length(*a);
    auto length_b = bg::length(*b);
    std::cout << "len(" << desc(a) << "): " << length_a << "\n";
    std::cout << "len(" << desc(b) << "): " << length_b << "\n";
    std::cout << "distance(" << desc(P1) << " - " << desc(P3) << "): " << bg::distance(P1, P3) << "\n";

    auto cutoff = min_distance / tan(inner);
    std::cout << "cutoff: " << cutoff << "\n";

让我们创建一个助手来手动插入截止点:

static inline String do_cutoff(String s, long double amount)
{
    assert(s.size() >= 2);
    auto delta = s[1];
    bg::subtract_point(delta, s[0]);

    auto l = bg::length(s);
    if (l > 0) {
        bg::multiply_value(delta, std::min(amount, l) / l);
        bg::add_point(s[0], delta);
    }

    return s;
}

现在我们可以使用它来获得新的中点而不是 P2:

    String fs = do_cutoff({P2, P1}, cutoff);
    String ss = do_cutoff({P2, P3}, cutoff);
    std::reverse(fs.begin(), fs.end());

    // assert that end points didn't change
    assert(bg::equals(fs.front(), P1));
    assert(bg::equals(ss.back(), P3));

现在出现了两条腿都太短而无法截断的退化情况,这意味着中点将消失:

    if (bg::length(*a) < cutoff && bg::length(*b) < cutoff) {
        // both legs vanish, connect P1 to P3 directly
        auto it = ls.begin() + index_of_p2;
        std::cout << "Dropping " << desc(*it) << " (" << desc(P2) << " "
                  << wkt(P2) << ")\n";
        ls.erase(it);

        // note that distance (P1, P3) might still exceed min_distance
        // behaviour here is not specified in question

        break; // invalidated the loop iterators!
    }

接下来,可能有一条腿消失了:

    if (bg::length(*a) < cutoff) { // P1 P2 P3 becomes P1 P3' P3
        update(P2, ss.front());
    } else if (bg::length(*b) < cutoff) { // P1 P2 P3 becomes P1 P1' P3
        update(P2, fs.back());
    } else {

到目前为止,一切都很好。如果没有一条腿消失,我们需要通过引入一个额外的点来切断尖点(并更新原来的中点):

        update(P2, ss.front());

        // adding a point (invalidating the segment iterators)
        std::cout << "Adding " << wkt(fs.back()) << " as X "
                  << " before  " << desc(P2) << "\n";

        names.insert(names.begin() + index_of_p2, "X");
        auto added = ls.insert(ls.begin() + index_of_p2, fs.back());
        assert(bg::equals(*added, fs.back()));

        break; // invalidated the loop iterators!
    }

现场演示

住在科利鲁

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xyz.hpp>
#include <boost/geometry/views/segment_view.hpp>
#include <fstream>
#include <iostream>

namespace bg = boost::geometry;
namespace bgm = bg::model;

using Point   = bgm::d2::point_xy<double>;
using String  = bgm::linestring<Point>;
using bg::wkt;

static inline auto angle_rad(Point a, Point b) {
    bg::subtract_point(a, b);
    return atan2(a.y(), a.x());
}

static inline String do_cutoff(String s, long double amount)
{
    assert(s.size() >= 2);
    auto delta = s[1];
    bg::subtract_point(delta, s[0]);

    auto l = bg::length(s);
    if (l > 0) {
        bg::multiply_value(delta, std::min(amount, l) / l);
        bg::add_point(s[0], delta);
    }

    return s;
}

struct Test {
    String                   ls;
    std::vector<std::string> names;
    double const             min_distance;
    std::string              color, dash_pattern, other_style;

    std::string desc(Point const& p) const
    {
        return names.at(&p - &ls.front());
    };
    std::string desc(bg::segment_iterator<String const> it) const
    {
        return desc(*it->first) + desc(*it->second);
    };

    void perform_cutoff();
};

void Test::perform_cutoff()
{
    if (min_distance == 0)
        return; // keep original

    assert(ls.size() > 1);
    auto a = bg::segments_begin(ls), b = std::next(a), e = bg::segments_end(ls);

    for (; b != e; ++a, ++b) {
        // segments a,b form a joint, let's call it P1 P2 P3
        auto& P1 = *a->first;
        auto& P2 = *a->second;
        auto& P3 = *b->second;
        // keep in mind a->second and b->first alias the same point here
        assert(a->second == b->first);

        auto seg_angle = [](auto it) {
            return angle_rad(*it->first, *it->second);
        };
        auto rel      = fmod(seg_angle(b) - seg_angle(a) + 3 * M_PI, 2 * M_PI);
        auto inner    = fabs(2 * M_PI - rel);
        bool is_sharp = inner < M_PI / 2;

        if (is_sharp) {
            std::cout << " ---- sharp angle, min_distance: " << min_distance
                      << " (" << color << ")\n";
            auto deg = [](auto rad) { return rad / M_PI * 180; };
            std::cout << "angle " << deg(inner) << " between " << desc(a) << " and " << desc(b) << "\n";

            auto length_a = bg::length(*a);
            auto length_b = bg::length(*b);
            std::cout << "len(" << desc(a) << "): " << length_a << "\n";
            std::cout << "len(" << desc(b) << "): " << length_b << "\n";
            std::cout << "distance(" << desc(P1) << " - " << desc(P3) << "): " << bg::distance(P1, P3) << "\n";

            auto cutoff = min_distance / tan(inner);
            std::cout << "cutoff: " << cutoff << "\n";

            String fs = do_cutoff({P2, P1}, cutoff);
            String ss = do_cutoff({P2, P3}, cutoff);
            std::reverse(fs.begin(), fs.end());

            // assert that end points didn't change
            assert(bg::equals(fs.front(), P1));
            assert(bg::equals(ss.back(), P3));

            std::cout << wkt(ls) << "\n";
            auto update = [this](Point const& p, Point const& value) {
                std::cout << "Updating " << desc(p) << " from " << wkt(p)
                          << " to " << wkt(value) << "\n";
                bg::assign(const_cast<Point&>(p), value);
                names[&p - &ls.front()] += "'";
            };

            // For modifying we need the index to the middle point:
            auto const index_of_p2 = &P2 - &ls.front();

            if (bg::length(*a) < cutoff && bg::length(*b) < cutoff) {
                // both legs vanish, connect P1 to P3 directly
                auto it = ls.begin() + index_of_p2;
                std::cout << "Dropping " << desc(*it) << " (" << desc(P2) << " "
                          << wkt(P2) << ")\n";
                ls.erase(it);

                // note that distance (P1, P3) might still exceed min_distance
                // behaviour here is not specified in question

                break; // invalidated the loop iterators!
            }

            if (bg::length(*a) < cutoff) { // P1 P2 P3 becomes P1 P3' P3
                update(P2, ss.front());
            } else if (bg::length(*b) < cutoff) { // P1 P2 P3 becomes P1 P1' P3
                update(P2, fs.back());
            } else {
                update(P2, ss.front());

                // adding a point (invalidating the segment iterators)
                std::cout << "Adding " << wkt(fs.back()) << " as X "
                          << " before  " << desc(P2) << "\n";

                names.insert(names.begin() + index_of_p2, "X");
                auto added = ls.insert(ls.begin() + index_of_p2, fs.back());
                assert(bg::equals(*added, fs.back()));

                break; // invalidated the loop iterators!
            }
        }
    }
}
int main() 
{
    // clang-format off
    Point
        A(-25.74,    2.5),
        B( 14.96,  12.96),
        C( 31.07,  -2.16),
        D(    37, -27.74),
        E( 29.37, -51.63),
        F( 46.62, -25.48),
        G( 48.17,   3.63),
        H( 41.53,  20.31),
        I( 77.57,  32.04);
    // clang-format on

    {
        String const original_ls{A, B, C, D, E, F, G, H, I};

        if (std::string reason; !bg::is_valid(original_ls, reason)) {
            std::cout << reason << "\n";
            return 1;
        }
    }

    Test testcases[]{
        {
            {A, B, C, D, E, F, G, H, I},
                {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"},
                0, // keep original
                "grey", "1 1",
        },
            {
                {A, B, C, D, E, F, G, H, I},
                {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"},
                5,
                "green", "2 1",
            },
            {
                {A, B, C, D, E, F, G, H, I},
                {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"},
                8,
                "red", "1 2",
            },
            {
                {A, B, C, D, E, F, G, H, I},
                {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"},
                12,
                "blue", "2 3",
            },
    };

    std::ofstream svg("output.svg");
    bg::svg_mapper<Point> mapper(svg, 400, 400);

    for (auto& c : testcases) {
        c.perform_cutoff();
        std::cout << wkt(c.ls) << "\n";

        mapper.add(c.ls);
        mapper.map(
                c.ls,
                "stroke:" + c.color + ";stroke-width:1;stroke-dasharray:" + c.dash_pattern);
        for (auto& p : c.ls)
            mapper.text(p, c.desc(p), "fill:" + c.color + ";font-size:x-small");
    }
}

印刷

LINESTRING(-25.74 2.5,14.96 12.96,31.07 -2.16,37 -27.74,29.37 -51.63,46.62 -25.48,48.17 3.63,41.53 20.31,77.57 32.04)
 ---- sharp angle, min_distance: 5 (green)
angle 15.6986 between DE and EF
len(DE): 25.0789
len(EF): 31.3271
distance(D - F): 9.8819
cutoff: 17.7897
LINESTRING(-25.74 2.5,14.96 12.96,31.07 -2.16,37 -27.74,29.37 -51.63,46.62 -25.48,48.17 3.63,41.53 20.31,77.57 32.04)
Updating E from POINT(29.37 -51.63) to POINT(39.1658 -36.7802)
Adding POINT(34.7824 -34.6836) as X  before  E'
LINESTRING(-25.74 2.5,14.96 12.96,31.07 -2.16,37 -27.74,34.7824 -34.6836,39.1658 -36.7802,46.62 -25.48,48.17 3.63,41.53 20.31,77.57 32.04)
 ---- sharp angle, min_distance: 8 (red)
angle 15.6986 between DE and EF
len(DE): 25.0789
len(EF): 31.3271
distance(D - F): 9.8819
cutoff: 28.4636
LINESTRING(-25.74 2.5,14.96 12.96,31.07 -2.16,37 -27.74,29.37 -51.63,46.62 -25.48,48.17 3.63,41.53 20.31,77.57 32.04)
Updating E from POINT(29.37 -51.63) to POINT(45.0432 -27.8703)
LINESTRING(-25.74 2.5,14.96 12.96,31.07 -2.16,37 -27.74,45.0432 -27.8703,46.62 -25.48,48.17 3.63,41.53 20.31,77.57 32.04)
 ---- sharp angle, min_distance: 12 (blue)
angle 15.6986 between DE and EF
len(DE): 25.0789
len(EF): 31.3271
distance(D - F): 9.8819
cutoff: 42.6953
LINESTRING(-25.74 2.5,14.96 12.96,31.07 -2.16,37 -27.74,29.37 -51.63,46.62 -25.48,48.17 3.63,41.53 20.31,77.57 32.04)
Dropping E (E POINT(29.37 -51.63))
LINESTRING(-25.74 2.5,14.96 12.96,31.07 -2.16,37 -27.74,46.62 -25.48,48.17 3.63,41.53 20.31,77.57 32.04)

结果output.svg

在此处输入图像描述

结论

希望这会有所帮助。

请注意,代码在编写时有限制,因为为简单起见,我使用了 segment_iterator.

  • 由于段迭代器循环,目前只能处理第一个尖角,除非可以在不使迭代器无效的情况下处理它们。您要么必须重复循环,要么根据索引重写循环,而不是使用segment_iterators.

  • 同样由于分段迭代器,我采用了一种丑陋const_cast的方式来更新现有点,而不是通过几何界面。这不会通过我自己的代码审查 :)

  • 最后,即使距离 P1-P3 直接超过了,也没有任何决定 min_distance(您没有在问题中指定任何关于这种可能性的内容)。


于 2021-11-06T05:44:25.620 回答