我使用boost
几何库来存储多边形,我想在控制台上打印不同的多边形来调试我的几何图形,如矩形,n点多边形。在Linux中有没有可以这样做的库?谢谢!
答案 0 :(得分:1)
为了纯粹的乐趣,我在昨天(Draw Line using c++ without graphics)修改了Canvas示例并使用了一些Boost Geometry支持。
您可以像以下一样使用它:
int main() {
using manip::as_geo;
Polygon poly;
bg::read_wkt("POLYGON((0 0,0 7,4 2,2 0,0 0))", poly);
std::cout << as_geo(poly);
// a polygon with a hole
std::cout << as_geo<Polygon>("POLYGON((0 0,0 7,4 2,2 0,0 0) (1.5 2.5, 1.5 3.0, 2.5 3.0, 2.5 2.5, 1.5 2.5))");
// custom canvas size and glyphs
std::cout << as_geo<60, 30>(poly, '@', '%');
}
这是一个通用的x / y对类,包含许多转换和基本算术运算(用于缩放,偏移,逻辑 - 物理转换):
namespace Utility {
template <typename T> struct Vec2 {
T x, y;
Vec2(T x = {}, T y = {}) : x(x), y(y) {}
template <typename U, typename V> Vec2(U const& x, V const& y) : x(x), y(y) {}
template <typename U> Vec2(Vec2<U> const& rhs) : Vec2(rhs.x, rhs.y) {}
template <typename U> Vec2& operator=(Vec2<U> const& rhs) {
return *this = {rhs.x, rhs.y};
}
#define VEC_OPS VEC_DECLARE_OP(*) VEC_DECLARE_OP(/) VEC_DECLARE_OP(+) VEC_DECLARE_OP(-)
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2<R> operator op(Vec2<U> const& rhs) const { return {x op rhs.x, y op rhs.y}; }
VEC_OPS
#undef VEC_DECLARE_OP
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2<R> operator op(U const& rhs) const { return {x op rhs, y op rhs}; }
VEC_OPS
#undef VEC_DECLARE_OP
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2& operator op##=(U const& rhs) { return operator=((*this) op rhs); }
VEC_OPS
#undef VEC_DECLARE_OP
private:
friend std::ostream& operator<<(std::ostream& os, Vec2 const& xy) {
return os << "{" << xy.x << "," << xy.y << "}";
}
};
}
让我们从定义一些有用的构建块开始:
using Physical = Utility::Vec2<int>;
using Logical = Utility::Vec2<double>;
using Scale = Utility::Vec2<double>;
struct Extents {
Logical TopLeft, BottomRight;
void normalize() {
if (TopLeft.y < BottomRight.y) std::swap(TopLeft.y, BottomRight.y);
if (TopLeft.x > BottomRight.x) std::swap(TopLeft.x, BottomRight.x);
}
auto height() const { return std::abs(TopLeft.y - BottomRight.y); }
auto width() const { return std::abs(BottomRight.x - TopLeft.x); }
friend std::ostream& operator<<(std::ostream& os, Extents const& e) {
return os << "{" << e.TopLeft << " - " << e.BottomRight << "; " << e.width() << "×" << e.height() << "}";
}
};
现在我们可以继续使用画布,主要是the same as in the previous answer:
template <int Columns = 100, int Rows = 50>
struct BasicCanvas {
using Line = std::array<char, Columns>;
using Screen = std::array<Line, Rows>;
static constexpr size_t rows() { return Rows; }
static constexpr size_t columns() { return Columns; }
BasicCanvas(Logical origin = {Columns/2, Rows/2}, Scale scale = {1.0, 1.0}) : origin(origin), scale(scale) {
clear();
}
BasicCanvas(Extents extents) {
extents.normalize();
using Utility::Vec2;
scale = Vec2{extents.width(), extents.height()} / Vec2{Columns, Rows};
origin = { -extents.TopLeft.x, -extents.BottomRight.y };
clear();
}
Screen screen;
Logical origin;
Scale scale; // physical * scale = logical
Logical to_logical(Physical const& phys) const { return (phys * scale) - origin; }
Physical to_physical(Logical const& log) const { return (log + origin) / scale; }
Extents extents() const { return { to_logical({ 0, Rows }), to_logical({ Columns, 0}) }; }
friend std::ostream& operator<<(std::ostream& os, BasicCanvas const& c) {
for (auto& line : c.screen) {
os.write(line.data(), line.size()) << "\n";
}
return os;
}
Line& operator[](size_t y) { return screen.at(screen.size()-(y+1)); }
Line const& operator[](size_t y) const { return screen.at(screen.size()-(y+1)); }
char& operator[](Physical coord) { return operator[](coord.y).at(coord.x); }
char const& operator[](Physical coord) const { return operator[](coord.y).at(coord.x); }
void clear(char filler = '.') {
Line empty;
std::fill(empty.begin(), empty.end(), filler);
std::fill(screen.begin(), screen.end(), empty);
}
void axes() {
Physical phys_org = to_physical({0,0});
auto const y_shown = (phys_org.x >= 0 && phys_org.x < Columns);
auto const x_shown = (phys_org.y >= 0 && phys_org.y < Rows);
if (y_shown)
for (auto& line : screen)
line.at(phys_org.x) = '|';
if (x_shown) {
auto& y_axis = operator[](phys_org.y);
for (auto& cell : y_axis)
cell = '-';
if (y_shown)
y_axis.at(phys_org.x) = '+';
}
}
template <typename F>
void plot(F f) {
for (size_t x_tick = 0; x_tick < Columns; ++x_tick) {
auto x = to_logical({ x_tick, 0 }).x;
auto y = f(x);
auto y_ = derivative(f, x, scale.x/2);
size_t y_tick = to_physical({x, y}).y;
if (y_tick < Rows)
operator[]({x_tick, y_tick}) = line_glyph(y_);
}
}
private:
template <typename F>
auto derivative(F const& f, double x, double dx = 0.01) {
return (f(x+dx)-f(x-dx))/(2*dx);
}
char line_glyph(double tangent) {
auto angle = atan(tangent);
while (angle < 0)
angle += 2*M_PI;
int angle_index = 2.0 * angle / atan(1);
return R"(--/||\--)"[angle_index % 8];
}
};
注意现在包含一些操作(
axes()
和plot()
,这些操作并非严格要求。
为了解耦,让我们在课外定义绘图操作。
出于本演示的目的,我只实现了一个操作
fill(geo, filler_char)
,这意味着我们现在可以处理平面。可以类似于上面的plot
操作添加线段和线串,但我建议查看Bresenham Algorithm以避免次优结果。
让我们介绍Boost Geometry:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/io/io.hpp>
namespace bg = boost::geometry;
using Point = bg::model::d2::point_xy<double, bg::cs::cartesian>;
using Polygon = bg::model::polygon<Point>;
using Box = bg::model::box<Point>;
我们将使用Box
来检测我们即将绘制的几何体的边界矩形。这样我们就不必担心缩放几何体以适应画布。
假设我们有画布设置,我们可以简单地将fill
实现为:
template <typename Canvas, typename G>
void fill(Canvas& canvas, G const& geo, char filler = '*') {
for (size_t x_tick = 0; x_tick < canvas.columns(); ++x_tick) {
for (size_t y_tick = 0; y_tick < canvas.rows(); ++y_tick) {
Physical phys { x_tick, y_tick };
Logical log = canvas.to_logical(phys);
if (bg::within(Point(log.x, log.y), geo))
canvas[{x_tick, y_tick}] = filler;
}
}
}
注意:目前尚未考虑性能因素
要像我们开始使用的样本一样将它们粘合在一起,我们创建一个IO manipulator来处理
template <typename Geo, int Columns = 100, int Rows = 50>
struct geo_manip {
Geo _geo_or_ref;
char _filler, _bg;
friend std::ostream& operator<<(std::ostream& os, geo_manip const& gm) {
Box bounding;
bg::envelope(gm._geo_or_ref, bounding);
BasicCanvas<Columns, Rows> canvas(Extents {
{bounding.max_corner().x(), bounding.max_corner().y()},
{bounding.min_corner().x(), bounding.min_corner().y()}});
canvas.clear(gm._bg);
fill(canvas, gm._geo_or_ref, gm._filler);
os << "Canvas extents: " << canvas.extents() << "\n";
os << "Canvas origin:" << canvas.origin << " scale:" << canvas.scale << "\n";
return os << canvas;
}
};
便利函数as_geo
可以轻松创建操纵器,既可以用于现有几何图形(通过引用),也可以解析WKT片段:
template <int Columns = 100, int Rows = 50, typename Geo>
geo_manip<Geo const&, Columns, Rows> as_geo(Geo const& geo, char filler = '*', char background = ' ') {
return {geo, filler, background};
}
template <typename Geo = Polygon, int Columns = 100, int Rows = 50>
geo_manip<Geo, Columns, Rows> as_geo(std::string const& wkt, char filler = '*', char background = ' ') {
Geo geo;
bg::read_wkt(wkt, geo);
return {geo, filler, background};
}
<强> Live On Coliru 强>
#include <iostream>
#include <array>
#include <limits>
#include <cmath>
namespace Utility {
template <typename T> struct Vec2 {
T x, y;
Vec2(T x = {}, T y = {}) : x(x), y(y) {}
template <typename U, typename V> Vec2(U const& x, V const& y) : x(x), y(y) {}
template <typename U> Vec2(Vec2<U> const& rhs) : Vec2(rhs.x, rhs.y) {}
template <typename U> Vec2& operator=(Vec2<U> const& rhs) {
return *this = {rhs.x, rhs.y};
}
#define VEC_OPS VEC_DECLARE_OP(*) VEC_DECLARE_OP(/) VEC_DECLARE_OP(+) VEC_DECLARE_OP(-)
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2<R> operator op(Vec2<U> const& rhs) const { return {x op rhs.x, y op rhs.y}; }
VEC_OPS
#undef VEC_DECLARE_OP
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2<R> operator op(U const& rhs) const { return {x op rhs, y op rhs}; }
VEC_OPS
#undef VEC_DECLARE_OP
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2& operator op##=(U const& rhs) { return operator=((*this) op rhs); }
VEC_OPS
#undef VEC_DECLARE_OP
private:
friend std::ostream& operator<<(std::ostream& os, Vec2 const& xy) {
return os << "{" << xy.x << "," << xy.y << "}";
}
};
}
using Physical = Utility::Vec2<int>;
using Logical = Utility::Vec2<double>;
using Scale = Utility::Vec2<double>;
struct Extents {
Logical TopLeft, BottomRight;
void normalize() {
if (TopLeft.y < BottomRight.y) std::swap(TopLeft.y, BottomRight.y);
if (TopLeft.x > BottomRight.x) std::swap(TopLeft.x, BottomRight.x);
}
auto height() const { return std::abs(TopLeft.y - BottomRight.y); }
auto width() const { return std::abs(BottomRight.x - TopLeft.x); }
friend std::ostream& operator<<(std::ostream& os, Extents const& e) {
return os << "{" << e.TopLeft << " - " << e.BottomRight << "; " << e.width() << "×" << e.height() << "}";
}
};
template <int Columns = 100, int Rows = 50>
struct BasicCanvas {
using Line = std::array<char, Columns>;
using Screen = std::array<Line, Rows>;
static constexpr size_t rows() { return Rows; }
static constexpr size_t columns() { return Columns; }
BasicCanvas(Logical origin = {Columns/2, Rows/2}, Scale scale = {1.0, 1.0}) : origin(origin), scale(scale) {
clear();
}
BasicCanvas(Extents extents) {
extents.normalize();
using Utility::Vec2;
scale = Vec2{extents.width(), extents.height()} / Vec2{Columns, Rows};
origin = { -extents.TopLeft.x, -extents.BottomRight.y };
clear();
}
Screen screen;
Logical origin;
Scale scale; // physical * scale = logical
Logical to_logical(Physical const& phys) const { return (phys * scale) - origin; }
Physical to_physical(Logical const& log) const { return (log + origin) / scale; }
Extents extents() const { return { to_logical({ 0, Rows }), to_logical({ Columns, 0}) }; }
friend std::ostream& operator<<(std::ostream& os, BasicCanvas const& c) {
for (auto& line : c.screen) {
os.write(line.data(), line.size()) << "\n";
}
return os;
}
Line& operator[](size_t y) { return screen.at(screen.size()-(y+1)); }
Line const& operator[](size_t y) const { return screen.at(screen.size()-(y+1)); }
char& operator[](Physical coord) { return operator[](coord.y).at(coord.x); }
char const& operator[](Physical coord) const { return operator[](coord.y).at(coord.x); }
void clear(char filler = '.') {
Line empty;
std::fill(empty.begin(), empty.end(), filler);
std::fill(screen.begin(), screen.end(), empty);
}
void axes() {
Physical phys_org = to_physical({0,0});
auto const y_shown = (phys_org.x >= 0 && phys_org.x < Columns);
auto const x_shown = (phys_org.y >= 0 && phys_org.y < Rows);
if (y_shown)
for (auto& line : screen)
line.at(phys_org.x) = '|';
if (x_shown) {
auto& y_axis = operator[](phys_org.y);
for (auto& cell : y_axis)
cell = '-';
if (y_shown)
y_axis.at(phys_org.x) = '+';
}
}
template <typename F>
void plot(F f) {
for (size_t x_tick = 0; x_tick < Columns; ++x_tick) {
auto x = to_logical({ x_tick, 0 }).x;
auto y = f(x);
auto y_ = derivative(f, x, scale.x/2);
size_t y_tick = to_physical({x, y}).y;
if (y_tick < Rows)
operator[]({x_tick, y_tick}) = line_glyph(y_);
}
}
private:
template <typename F>
auto derivative(F const& f, double x, double dx = 0.01) {
return (f(x+dx)-f(x-dx))/(2*dx);
}
char line_glyph(double tangent) {
auto angle = atan(tangent);
while (angle < 0)
angle += 2*M_PI;
int angle_index = 2.0 * angle / atan(1);
return R"(--/||\--)"[angle_index % 8];
}
};
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/io/io.hpp>
namespace bg = boost::geometry;
using Point = bg::model::d2::point_xy<double, bg::cs::cartesian>;
using Polygon = bg::model::polygon<Point>;
using Box = bg::model::box<Point>;
template <typename Canvas, typename G>
void fill(Canvas& canvas, G const& geo, char filler = '*') {
for (size_t x_tick = 0; x_tick < canvas.columns(); ++x_tick) {
for (size_t y_tick = 0; y_tick < canvas.rows(); ++y_tick) {
Physical phys { x_tick, y_tick };
Logical log = canvas.to_logical(phys);
if (bg::within(Point(log.x, log.y), geo))
canvas[phys] = filler;
}
}
}
namespace manip {
template <typename Geo, int Columns = 100, int Rows = 50>
struct geo_manip {
Geo _geo_or_ref;
char _filler, _bg;
friend std::ostream& operator<<(std::ostream& os, geo_manip const& gm) {
Box bounding;
bg::envelope(gm._geo_or_ref, bounding);
BasicCanvas<Columns, Rows> canvas(Extents {
{bounding.max_corner().x(), bounding.max_corner().y()},
{bounding.min_corner().x(), bounding.min_corner().y()}});
canvas.clear(gm._bg);
fill(canvas, gm._geo_or_ref, gm._filler);
os << "Canvas extents: " << canvas.extents() << "\n";
os << "Canvas origin:" << canvas.origin << " scale:" << canvas.scale << "\n";
return os << canvas;
}
};
template <int Columns = 100, int Rows = 50, typename Geo>
geo_manip<Geo const&, Columns, Rows> as_geo(Geo const& geo, char filler = '*', char background = ' ') {
return {geo, filler, background};
}
template <typename Geo = Polygon, int Columns = 100, int Rows = 50>
geo_manip<Geo, Columns, Rows> as_geo(std::string const& wkt, char filler = '*', char background = ' ') {
Geo geo;
bg::read_wkt(wkt, geo);
return {geo, filler, background};
}
}
int main() {
using manip::as_geo;
Polygon poly;
bg::read_wkt("POLYGON((0 0,0 7,4 2,2 0,0 0))", poly);
std::cout << as_geo(poly);
// a polygon with a hole
std::cout << as_geo<Polygon>("POLYGON((0 0,0 7,4 2,2 0,0 0) (1.5 2.5, 1.5 3.0, 2.5 3.0, 2.5 2.5, 1.5 2.5))");
// custom canvas size and glyphs
std::cout << as_geo<60, 30>(poly, '@', '%');
}
输出也在coliru上。