使用以下方法将您的图像转换为代码:
那是:
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
(您没有在问题中指定任何关于这种可能性的内容)。