...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
One of the primary benefits of Boost.Geometry, and the reason for its fairly complex template-based implementation, is that it allows for integration with legacy classes/objects.
By defining the relationship between the Boost.Geometry concepts and an existing, legacy object model, the legacy objects can be used in place of Boost.Geometry's own geometry classes.
Boost.Geometry will then happliy read and write directly from and to the legacy object, treating it as a native Boost.Geometry object.
This means that one can adapt algorithms and methods from Boost.Geometry to any existing legacy geometry object model at a very small runtime cost, which is simply not possible with most geometry libraries, where one has to make an intermediate object specific to the geometry library one is using.
The following example will demonstrate the adaption process of a legacy geometry object model for use with Boost.Geometry.
class QPoint { public: double x; double y; QPoint(double x, double y) : x(x), y(y) {} }; class QLineString { public: bool cw; std::vector<QPoint*> points; }; class QRing { public: std::vector<QLineString*> lines; }; class QPolygon { public: QRing* exterior; std::vector<QRing*> interiors; };
The legacy object hierarcy is based on topology (e.g. two QRings might share one QLineString) instead of points directly (i.e. each object does not point directly to it's QPoints), and it also uses pointers for access.
This is the other common way to approach geometries, to enable e.g. shared boundaries between surfaces. Boost.Geometry's approach use simple features, and does not have shared geometries.
The mismatch in representation is fixed by creating a custom iterator, that exposes a Boost.Range of Points for every object. This way, Boost.Geometry's functions will operate on the QRing as if it was a collection of Points, which is a requirement.
The adaption of the QPoint is fairly straightforward, one just needs to implement the requirements.
Even though the geometries in our legacy object model use pointers of QPoints, Boost.Geometry automatically handles the conversion from pointers-to-Points to references-to-Points internally, so we do not have to convert them manually.
Alternatively, we can use the BOOST_GEOMETRY_REGISTER_POINT_2D(QPoint, double, cs::cartesian, x, y) helper macro, which does exactly the same as our manual adaption.
The sample code adapts QPoint to the Point Concept using specialization of the traits class.
The adaption of the QLineString is very simple on the surface, as it is just "a specialization of traits::tag defining linestring_tag as type". Alternatively, we can use the BOOST_GEOMETRY_REGISTER_LINESTRING(QLineString) helper macro, which does exactly the same as our manual adaption.
However, the LineString concept also requires that the collection of Points "must behave like a Boost.Range Random Access Range" and "the type defined by the metafunction range_value<...>::type must fulfill the Point Concept".
This means that we have to do two things:
This might look like a lot of work, but we are in luck: a std::vector is nearly a Boost.Range, and already iterate over pointers-to-QPoints, that are handled by Boost.Geometry. The code for making QLineString a Boost.Range is therefore fairly straightforward.
The adaption of the QRing is mostly equal to the QLineString in that there is a tag and a collection to iterate through. Alternatively, we can use the BOOST_GEOMETRY_REGISTER_RING(QRing) helper macro, which does exactly the same as our manual adaption.
However, the QRing expose pointers-to-QLineStrings, and not QPoints directly, which is required in the Ring concept, so it is not enough to trivially make the std::vector into a Boost.Range. We need to create a Boost.Iterator that expose QPoints, and because we are dealing with a legacy object model, we are not allowed to change the class definition.
The custom iterator that does this uses Boost.Iterator Facade, and is not very different from the example provided in Boost.Iterator's own documentation(link), except that our Boost.Range need to be random access.
Now, with the custom iterator made, we can define the Boost.Range that traverses through QPoints.
Adapting the QPolygon to the Polygon Concept is a little more involved than the other geometry types.
The only requirement that is not straightforward to adapt is the interior_rings' get method.
A Boost.Geometry Polygon operates on Ring objects, and unfortunately, Boost.Geometry does not automatically handle the conversion from pointers to references for Rings internally (only Points, as mentioned).
Therefore, we need to expose QRings instead of pointers-to-QRings for the interior Rings, which means a little more work than the pointers-to-QPoints for QLineString and QRing.
First, we create a Boost.Iterator Facade that returns QRing instead of pointer-to-QRing:
Now we have an iterator that can "convert" our pointer-to-QRing into QRing. However, the get method of the interior Rings must return a Boost.Range compatible object, which a plain PolygonRingIterator is not.
We need to define another Boost.Range, that can be constructed with PolygonRingIterators as arguments, and returned from the get method.
That's it! The methods of Boost.Geometry can now be used directly on instances of our legacy object model.