Why is boost multipolygon is rotated upside down?

I have the obtained the WKT string of a boost multipolygon which is constructed by union of boost polygons in a vector called vectorPolygons

std::vector<boost::geometry::Polygon> vectorPolygons;

After adding data in vectorPolygons below code computes its union and then extracts the WKT of the multi polygon

if (!vectorPolygons.empty())
{
    multi_polygon boost_multipolygon;   // Will store union of polygons

    // Create the union of all the polygons
    for (const boost::geometry::Polygon& p : vectorPolygons) {
        // add another polygon each iteration
        multi_polygon tmp_poly;
        boost::geometry::union_(boost_multipolygon, p, tmp_poly);
        boost_multipolygon= tmp_poly;
        boost::geometry::clear(tmp_poly);
    }

    std::string validity_reason;
    bool valid = boost::geometry::is_valid(boost_multipolygon, validity_reason);

    if (!valid)
    {
        boost::geometry::correct(boost_multipolygon);
    }

    std::stringstream ss;

    ss << boost::geometry::wkt(boost_multipolygon);
    std::string wkt = ss.str();

    
}

The WKT string comes out as below

MULTIPOLYGON(((40000 30000,40000 -0,30000 -0,30000 10000,20000 10000,20000 20000,10000 20000,0 20000,0 30000,40000 30000)))

When i display this multipolygon by using the code example below then I am not sure why its display is always rotated upside down. In reality it should have been exactly upside down of what it displays.

I have tried to use boost::geometry::correct on the boost multipolygon before extracting the WKT also but still the display is always rotated. What is that i am doing wrong?

Here is the code which is trying to display the WKT generated above and the output is upside down

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


#include <boost/geometry/geometries/adapted/boost_polygon.hpp>

#include <vector>


namespace boost {
    namespace geometry {
        typedef model::d2::point_xy<double> point;
        typedef model::polygon<point> polygon;
    }
}

using multi_polygon = boost::geometry::model::multi_polygon<boost::geometry::polygon>;
namespace bg = boost::geometry;

int main()
{
    // Specify the basic type
    typedef boost::geometry::model::d2::point_xy<double> point_type;

    multi_polygon b;
    boost::geometry::read_wkt("MULTIPOLYGON(((40000 30000,40000 -0,30000 -0,30000 10000,20000 10000,20000 20000,10000 20000,0 20000,0 30000,40000 30000)))", b);
    boost::geometry::correct(b);


    // Declare a stream and an SVG mapper
    std::ofstream svg("my_map.svg");
    boost::geometry::svg_mapper<point_type> mapper(svg, 500, 500);

    // Add geometries such that all these geometries fit on the map
    mapper.add(b);


    // Draw the geometries on the SVG map, using a specific SVG style

    mapper.map(b, "fill-opacity:0.3;fill:rgb(51,51,153);stroke:rgb(51,51,153);stroke-width:2");


    // Destructor of map will be called - adding </svg>
    // Destructor of stream will be called, closing the file

    return 0;
}

There is no "right side up". Cartesian coordinate systems are just that: coordinate systems. How you map them on a visual projection is your choice.

Here's the output on my system:

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <fstream>
#include <iostream>
namespace bg = boost::geometry;

int main()
{
    using Point = bg::model::d2::point_xy<double>;
    using Poly  = bg::model::polygon<Point>;

    bg::model::multi_polygon<Poly> p;
    bg::read_wkt("MULTIPOLYGON(((40 30,40 -0,30 -0,30 10,20 10,20 20,10 20,0 20,0 30,40 30)))",
                 p);

    {
        std::ofstream svg("my_map.svg");
        bg::svg_mapper<Point> mapper(svg, 400, 400);

        mapper.add(p);
        mapper.map(p, "fill-opacity:0.3;fill:rgb(51,51,153);stroke:rgb(51,51,153);stroke-width:1");
    }
}

Shows as

enter image description here

So you can see that axes grow down/to the right. This agrees with the first random online WKT renderer I could find:

enter image description here

By that count I'd rate the output as "correct".

Flipping It?

You could just manually flip the system:

for (auto& pt : make_iterator_range(bg::points_begin(p), bg::points_end(p))) {
    pt.x(pt.x() * -1);
    pt.y(pt.y() * -1);
}

Result

enter image description here

A more versatile approach is to use a transform that can scale, rotate and translate over arbitrary distances/amounts: https://www.boost.org/doc/libs/1_78_0/libs/geometry/doc/html/geometry/reference/algorithms/transform/transform_3_with_strategy.html

UPDATE

I have updated the example with actual data at coliru.stacked-crooked.com/a/2ba8df83a1ecce91 . The code you shared marks my input as invalid and boost::geometry::correct operates on it and that rotates the output. Is it not possible to retain the input so that output is not rotated? – Test 16 hours ago

Taking it point by point: "Is it not possible to retain the input" - yes, it is always possible to retain invalid input, and you should expect to retain invalid output.

However, you didn't say what was being corrected. I ran my code with your data:

POLYGON((30 10,30 -0,40 -0,40 10,30 10)): -100
Correcting source poly: Geometry has wrong orientation
Union: MULTIPOLYGON(((30 10,40 10,40 -0,30 -0,30 10))): 100
POLYGON((30 20,30 10,40 10,40 20,30 20)): -100
Correcting source poly: Geometry has wrong orientation
Union: MULTIPOLYGON(((40 -0,30 -0,30 10,30 20,40 20,40 -0))): 200
POLYGON((30 30,30 20,40 20,40 30,30 30)): -100
Correcting source poly: Geometry has wrong orientation
Union: MULTIPOLYGON(((40 -0,30 -0,30 20,30 30,40 30,40 -0))): 300
POLYGON((20 20,20 10,30 10,30 20,20 20)): -100
Correcting source poly: Geometry has wrong orientation
Union: MULTIPOLYGON(((30 20,30 30,40 30,40 -0,30 -0,30 10,20 10,20 20,30 20))): 400
POLYGON((20 30,20 20,30 20,30 30,20 30)): -100
Correcting source poly: Geometry has wrong orientation
Union: MULTIPOLYGON(((40 30,40 -0,30 -0,30 10,20 10,20 20,20 30,40 30))): 500
POLYGON((10 30,10 20,20 20,20 30,10 30)): -100
Correcting source poly: Geometry has wrong orientation
Union: MULTIPOLYGON(((40 30,40 -0,30 -0,30 10,20 10,20 20,10 20,10 30,40 30))): 600
POLYGON((0 30,0 20,10 20,10 30,0 30)): -100
Correcting source poly: Geometry has wrong orientation
Union: MULTIPOLYGON(((40 30,40 -0,30 -0,30 10,20 10,20 20,10 20,0 20,0 30,40 30))): 700
The important bit: "Correcting source poly: Geometry has wrong orientation".

In a way, your data is not invalid, it's just invalid for the chosen geometry type! You could simply change the orientation for your polygon type:

using Poly = bg::model::polygon<Point, false>;

Without any further changes you get: http://coliru.stacked-crooked.com/a/f02e56fc2402112d

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <fstream>
#include <iostream>
namespace bg = boost::geometry;

template <typename T> auto from_wkt(std::string const& wkt) {
    T result;
    bg::read_wkt(wkt, result);
    return result;
}

template <typename T> void check(T& geo, std::string_view label) {
    for (std::string reason; !bg::is_valid(geo, reason); bg::correct(geo)) {
        std::cout << "Correcting " << label << ": " << reason << "\n";
    }
}

int main()
{
    using Point = bg::model::d2::point_xy<double>;
    using Poly = bg::model::polygon<Point, false>;
    using MPoly = bg::model::multi_polygon<Poly>;

    std::vector vectorPolygons{
        from_wkt<Poly>(R"(POLYGON(( 30 10, 30 -0, 40 -0, 40 10, 30 10 )))"),
        from_wkt<Poly>(R"(POLYGON(( 30 20, 30 10, 40 10, 40 20, 30 20 )))"),
        from_wkt<Poly>(R"(POLYGON(( 30 30, 30 20, 40 20, 40 30, 30 30 )))"),
        from_wkt<Poly>(R"(POLYGON(( 20 20, 20 10, 30 10, 30 20, 20 20 )))"),
        from_wkt<Poly>(R"(POLYGON(( 20 30, 20 20, 30 20, 30 30, 20 30 )))"),
        from_wkt<Poly>(R"(POLYGON(( 10 30, 10 20, 20 20, 20 30, 10 30 )))"),
        from_wkt<Poly>(R"(POLYGON((  0 30,  0 20, 10 20, 10 30,  0 30 )))"),
    };

    MPoly union_poly; // will store union of polygons

    for (auto& p : vectorPolygons) {
        std::cout << bg::wkt(p) << ": " << bg::area(p) << "\n";
        check(p, "source poly");

        MPoly tmp;
        boost::geometry::union_(union_poly, p, tmp);
        union_poly.swap(tmp);

        //check(union_poly, "union");
        std::cout << "Union: " << bg::wkt(union_poly) << ": "
            << bg::area(union_poly) << "\n";
    }

    {
        std::ofstream svg("my_map.svg");
        bg::svg_mapper<Point> mapper(svg, 400, 400);

        mapper.add(union_poly);
        mapper.map(union_poly,
            "fill-opacity:0.1;fill:rgb(51,51,153);stroke:rgb(51,51,153);"
            "stroke-width:0");

        for (auto const& p : vectorPolygons) {
            mapper.add(p);
            mapper.map(
                p,
                "fill-opacity:0.3;fill:rgb(77,77,77);stroke:rgb(77,77,77);"
                "stroke-width:1;stroke-dasharray:1 2");
        }
    }
}

Printing

POLYGON((30 10,30 -0,40 -0,40 10,30 10)): 100
Union: MULTIPOLYGON(((30 10,30 -0,40 -0,40 10,30 10))): 100
POLYGON((30 20,30 10,40 10,40 20,30 20)): 100
Union: MULTIPOLYGON(((40 -0,40 20,30 20,30 10,30 -0,40 -0))): 200
POLYGON((30 30,30 20,40 20,40 30,30 30)): 100
Union: MULTIPOLYGON(((40 -0,40 30,30 30,30 20,30 -0,40 -0))): 300
POLYGON((20 20,20 10,30 10,30 20,20 20)): 100
Union: MULTIPOLYGON(((30 20,20 20,20 10,30 10,30 -0,40 -0,40 30,30 30,30 20))): 400
POLYGON((20 30,20 20,30 20,30 30,20 30)): 100
Union: MULTIPOLYGON(((40 30,20 30,20 20,20 10,30 10,30 -0,40 -0,40 30))): 500
POLYGON((10 30,10 20,20 20,20 30,10 30)): 100
Union: MULTIPOLYGON(((40 30,10 30,10 20,20 20,20 10,30 10,30 -0,40 -0,40 30))): 600
POLYGON((0 30,0 20,10 20,10 30,0 30)): 100
Union: MULTIPOLYGON(((40 30,0 30,0 20,10 20,20 20,20 10,30 10,30 -0,40 -0,40 30))): 700

Summary

Moral of the story: always check that your input data is valid.

Also, please note, NONE of these ever "rotated" the output. The output was simply undefined and not what you wanted (it was more akin to the "negative complement" of the shape you were looking for, but that was actually accidental and the result was unspecified because the input violated the pre-conditions).