4

I have a legacy class hierarchy which I can not modify. Because of requirements of an external library, I need to define Boost.Ranges for the Line and Ring, where both only expose the points in a single run (i.e. it should, both for Line and Ring, be a Boost.Range of Points).

Pseudo-code to illustrate:

Line l1 = Line{{1.0,2.0},{3.0,4.0},{5.0,6.0}} // init Line with three Points
Line l2 = Line{{7.0,8.0},{9.0,10.0},{11.0,12.0}} // init Line with three Points

auto lit = boost::begin(l1); // points to the Point{1.0,2.0}
++lit; // points to the Point{3.0,4.0}

Ring r1 = Ring{l1,l2} // init Ring with two Lines

auto rit = boost::begin(r1); // points to the Point{1.0,2.0}
++rit; // points to the Point{3.0,4.0}
++rit; // points to the Point{5.0,6.0}
++rit; // points to the Point{7.0,8.0}
++rit; // points to the Point{9.0,10.0}
// etc...

The Line is easy, since the Points are stored directly (I have done this successfully with Boost.Range see the example). However, I don't know how to do this with Ring, since I need to get to each line's points directly.

class Point 
{
  public:
  double x, y;
}

class Line
{
  public:
  std::vector<Point> points;
}

class Ring
{
  public:
  std::vector<Line> lines;
}
meastp
  • 682
  • 1
  • 7
  • 15
  • 1
    Can the range by forward traversal, or does it need to be random access? – Emile Cormier Feb 12 '12 at 20:01
  • @EmileCormier According to the documentation of Boost.Geometry, "it must behave like a Boost.Range Random Access Range" [see here](http://www.boost.org/doc/libs/1_48_0/libs/geometry/doc/html/geometry/reference/concepts/concept_ring.html) – meastp Feb 13 '12 at 07:36
  • @EmileCormier From the [`boost::iterator_facade`](http://www.boost.org/doc/libs/release/libs/iterator/doc/iterator_facade.html) link that you provided, it looks like support for random access requires adding a definition for `distance_to`, and replacing `boost::forward_traversal_tag` with the `boost::random_access_traversal_tag`. Is this correct? – meastp Feb 13 '12 at 08:08
  • 1
    You also need to implement `decrement` and `advance`. The requirements are summarized here: http://www.boost.org/doc/libs/release/libs/iterator/doc/iterator_facade.html#iterator-facade-requirements `distance_to` and `advance` will be tricky to implement, but should be doable. – Emile Cormier Feb 13 '12 at 16:42
  • 1
    It could be that the particular Boost.Geometry algorithms you use will only do a simple forward traversal. If Boost.Geometry does not do any concept checking, you might be able to get away with just implementing your custom iterator as forward traversal or bidirectional. Also note that `distance_to` and `advance` will not be of constant time complexity for your Ring. If the Boost.Geometry algorithms you use does a lot of random-access indexing or jumping around, the algorithms may be slow compared to using a real, flat 1D range of Points. – Emile Cormier Feb 13 '12 at 17:14
  • I tried the forward iterator. Currently, I'm only using the WKT extension, i.e. writing the geometry objects to WKT, and it worked for my simple test case. I'll just leave the random access part alone until it is required. Thanks! :) – meastp Feb 14 '12 at 07:42

1 Answers1

7

You need to extend Boost.Range so that it recognizes Ring as a valid range. But before you can do that, you need to define a custom iterator that flattens a vector< vector<T> > into a 1D range.

This example uses Method 2 to extend Boost.Range. It also uses boost::iterator_facade to facilitate writing a custom iterator, and assumes that the iterator only needs to support forward traversal.

#include <iostream>
#include <vector>
#include <boost/assign/std/vector.hpp> // for 'operator+=()'
#include <boost/foreach.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/range.hpp>

struct Point
{
    Point(double x, double y) : x(x), y(y) {}
    double x, y;
};

struct Line {std::vector<Point> points;};

struct Ring {std::vector<Line> lines;};


/* Custom iterator type that flattens a 2D array into a 1D array */
template <class I, // Line iterator type
          class R  // Point reference type
         >
class RingIteratorImpl : public boost::iterator_facade<
        RingIteratorImpl<I,R>, Point, boost::forward_traversal_tag, R>
{
public:
    RingIteratorImpl() : lineIter_(0), pointIndex_(0) {}

    explicit RingIteratorImpl(I lineIter)
    :   lineIter_(lineIter), pointIndex_(0) {}

private:
    friend class boost::iterator_core_access;

    void increment()
    {
        ++pointIndex_;
        if (pointIndex_ >= lineIter_->points.size())
        {
            ++lineIter_;
            pointIndex_ = 0;
        }
    }

    bool equal(const RingIteratorImpl& other) const
    {
        return (lineIter_ == other.lineIter_) &&
               (pointIndex_ == other.pointIndex_);
    }

    R dereference() const {return lineIter_->points[pointIndex_];}

    I lineIter_;
    size_t pointIndex_;
};

typedef RingIteratorImpl<std::vector<Line>::iterator, Point&> RingIterator;
typedef RingIteratorImpl<std::vector<Line>::const_iterator, const Point&>
        ConstRingIterator;

namespace boost
{
    // Specialize metafunctions. We must include the range.hpp header.
    // We must open the 'boost' namespace.

    template <>
    struct range_mutable_iterator<Ring> { typedef RingIterator type; };

    template<>
    struct range_const_iterator<Ring> { typedef ConstRingIterator type; };

} // namespace 'boost'


// The required Range functions. These should be defined in the same namespace
// as Ring.

inline RingIterator range_begin(Ring& r)
    {return RingIterator(r.lines.begin());}

inline ConstRingIterator range_begin(const Ring& r)
    {return ConstRingIterator(r.lines.begin());}

inline RingIterator range_end(Ring& r)
    {return RingIterator(r.lines.end());}

inline ConstRingIterator range_end(const Ring& r)
    {return ConstRingIterator(r.lines.end());}


int main()
{
    Line l1, l2;
    Ring ring;

    {
        using namespace boost::assign; // bring 'operator+=()' into scope
        typedef Point P;
        l1.points += P(1.1,1.2), P(1.3,1.4), P(1.5,1.6);
        l2.points += P(2.1,2.2), P(2.3,2.4), P(2.5,2.6);
        ring.lines += l1, l2;
    }

    // Boost Foreach treats ring as a Boost Range.
    BOOST_FOREACH(Point p, ring)
    {
        std::cout << "(" << p.x << ", " << p.y << ") ";
    }
    std::cout << "\n";
}

I get the following output:

(1.1, 1.2) (1.3, 1.4) (1.5, 1.6) (2.1, 2.2) (2.3, 2.4) (2.5, 2.6) 
Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
  • Note: With C++11 and vector initialization via initializer lists, `boost::assign` is no longer required. `BOOST_FOREACH` can also be replaced by C++11 range-based for loops. – Emile Cormier Jun 27 '17 at 20:13