What define's Boost's svg_mapper scaling and translation?

Viewed 59

This code:

#include <fstream>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>

namespace bg = boost::geometry;

int main()
{
    std::ofstream svg ( "test.svg" );
    boost::geometry::svg_mapper<bg::model::d2::point_xy<double>, true, double> mapper ( svg, 6000, 3000 );

    bg::model::polygon<bg::model::d2::point_xy<double>> square{
        {{0, 0}, {0, 1000}, {1000, 1000}, {1000, 0}, {0, 0}}};
    const std::string style{"fill-opacity:1.0;fill:rgb(128,128,128);stroke:rgb(0,0,0);stroke-width:5"};

    mapper.add ( square );
    mapper.map ( square, style, 1.0 );
}

Produces this svg:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g fill-rule="evenodd"><path d="M 1500,3000 L 1500,0 L 4500,0 L 4500,3000 L 1500,3000 z " style="fill-opacity:1.0;fill:rgb(128,128,128);stroke:rgb(0,0,0);stroke-width:5"/></g>
</svg>

The following conversions happen from the input polygon to the mapped svg geometries:

(0, 0) -> (1500,3000)
(0, 1000) -> (1500,0)
(1000, 1000) -> (4500,0)
(1000, 0) -> (4500,3000)
(0, 0) -> (1500,3000)

Staring at it a bit you see there is some transformation applied, something like this:

  • +1500 in x
  • +3000 in y
  • 3x scale in x
  • -3x scale in y

My question is - What drives that transformation and can I prevent it? And if I can't prevent it, can I retrieve it or calculate it myself?

Reason being is I'm producing many complex SVG's and would like them to all be in the same frame. So if there is a circle at pixels (10,10) in one, I would like all the images to be of the same size with the circle in the exact same location. I tried to accomplish this with viewBox but the scaling and translation was too hard to predict to keep the images consistent.

1 Answers

svg_mapper calculates a bounding box from all add-ed geometries.

Then, a map_transformer is used to scale down to the desired width/height.

Contrary to what you might expect, add doesn't do anything besides expanding the bounding box. Likewise, after the first map call, no other add has any effect on the bounding-box used for the transformations.

In other words, you can use some kind of fixed bounding box, add only that, and then map your geometries into that "canvas":

Demo

#include <fstream>
#include <iostream>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>

namespace bg = boost::geometry;
using V      = /*long*/ double;
using P      = bg::model::d2::point_xy<V>;
using B      = bg::model::box<P>;

int main()
{
    auto verify = [](auto& g) {
        if (std::string r; !bg::is_valid(g, r)) {
            std::cout << "Correcting " << r << "\n";
            bg::correct(g);
        }
    };

    V side = 1000;
    bg::model::polygon<P> square{
        {{0, 0}, {0, side}, {side, side}, {side, 0}, {0, 0}},
    };

    verify(square);

    std::array steps {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,};
    for (unsigned i = 0; i < steps.size(); ++i) {
        {
            std::ofstream svg("test" + std::to_string(i) + ".svg");
            bg::svg_mapper<P, true, V> mapper(svg, 400, 400);

            auto clone = square;
            V    ofs   = (steps[i] / 5. - 1.0) * side;
            for (auto& p : boost::make_iterator_range(bg::points_begin(clone), bg::points_end(clone)))
                bg::add_point(p, P{ofs, ofs});
            std::cout << i << ": " << bg::wkt(square) << " " << bg::wkt(clone) << "\n";

            mapper.add(B{{-side, -side}, {2 * side, 2 * side}});
            //mapper.add(square); // no effect, already within bounding box
            //mapper.add(clone);  // no effect, already within bounding box
            mapper.map(square, "fill-opacity:0.1;fill:rgb(128,0,0)", 1.0);
            mapper.map(clone,  "fill-opacity:0.1;fill:rgb(0,0,128)", 1.0);
        }
    }
}

Which creates a series of svgs that I can show as a poor man's animation to show that the positioning of the square is constant:

enter image description here

Related