-2

I am using the boost geometry library to store polygons and I want to print different polygons on the console to debug my geometries like rectangle, n point polygon. Is there any library to do so in Linux? Thanks!

dda
  • 6,030
  • 2
  • 25
  • 34
EmptyData
  • 2,386
  • 2
  • 26
  • 42
  • Look here for inspiration https://stackoverflow.com/questions/47753700/draw-line-using-c-without-graphics/47758148#47758148 – sehe Dec 13 '17 at 08:33

1 Answers1

1

For sheer fun, I adapted that Canvas example from yesterday (Draw Line using c++ without graphics) with some Boost Geometry support.

You can use it like:

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, '@', '%');
}

Which renders the first polygon as enter image description here

The second polygon has a rectangular inner ring and shows as enter image description here

The third option uses a custom canvas size and drawing glyphs: enter image description here

Implementation

Utility Vec2

There's a generic x/y pair class with many conversions and basic arithmetic operations (for scaling, offsetting, logical-physical conversions):

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 << "}";
    }
};
}

Canvas Type

Let's start with defining some useful building blocks:

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() << "}";
    }
};

Now we can go ahead with the canvas, largely 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];
    }

};

Note This now contains some operations (axes() and plot() that aren't strictly required for the purpose of this answer.

Supporting Boost Geometry

In the interest of de-coupling, let's define the drawing operations out-of-class.

For the purpose of this demonstration I have only implemented an operation fill(geo, filler_char), which implies we can handle planar surfaces for now. Line-segments and linestrings could be added similarly to the plot operation above, but I suggest to look at the Bresenham Algorithm to avoid sub-optimal results.

Let's introduce 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>;

We are going to use Box to detect a bounding rectangle for the geometry we're about to draw. This way we don't have to worry about scaling the geometry to fit the canvas.

Assuming we have the canvas set-up, we can implement fill quite simply as:

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;
        }
    }
}

Note: no performance considerations have been taken into account for now

A Stream IO Manipulator

To glue it all together like in the sample we started with, we create an IO manipulator that takes care of

  1. calculating the bounding box
  2. instantiating a canvas with sufficient extent to fit the bounding box
  3. draw the background
  4. fill the geometry
  5. optionally parses the geometry right from WKT, in which case the caller must specify the type to deserialize
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;
    }
};

The convenience function as_geo makes it easy to create the manipulator, either for existing Geometries (by reference) or parsing a WKT fragment:

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};
}

Full Live Demo

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, '@', '%');
}

The output is also live on coliru.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Out of the box, have you looked at https://github.com/awulkiew/graphical-debugging? – sehe Dec 13 '17 at 22:53