C++ 助推点_圆以奇怪的形状出现

C++ 助推点_圆以奇怪的形状出现,c++,boost,gis,boost-geometry,C++,Boost,Gis,Boost Geometry,我正试图使用Boost的几何库在地球上创建一个半径为10米的多边形 这是我的建议 为了编译这个示例,我使用了最新的Clang和Boost 1.73.0 我首先在我的生产环境中发现了这个问题,即Clang12和Boost1.71.0 使用半径为1000m的圆和32个点可产生预期结果: 但是,将其缩小到10米会产生意想不到的结果: 我使用了一个工具来显示结果,并确认了其他可视化工具中的结果是相同的 这似乎是一个浮点舍入错误,但这里的所有内容都应该使用双精度浮点,而双精度浮点是有效的。计算似乎出了

我正试图使用Boost的几何库在地球上创建一个半径为10米的多边形

这是我的建议

为了编译这个示例,我使用了最新的Clang和Boost 1.73.0

我首先在我的生产环境中发现了这个问题,即Clang12和Boost1.71.0

使用半径为1000m的圆和32个点可产生预期结果:

但是,将其缩小到10米会产生意想不到的结果:

我使用了一个工具来显示结果,并确认了其他可视化工具中的结果是相同的

这似乎是一个浮点舍入错误,但这里的所有内容都应该使用双精度浮点,而双精度浮点是有效的。计算似乎出了问题

使用半径为0.0001时也会发生同样的情况

发生了什么,我应该手动计算圆吗

编辑1 如果使用
bg::area
来计算面积,则会变得更加奇怪。我试着在
点(4.9 52.1)
周围画一个半径为10米的圆圈,得到了25984.4米的面积。我在
点(4.9 52.1000001)
尝试了同样的方法,得到了-1122.14

请看以下游乐场:

编辑2 我发现显示多边形的问题与计算面积不正确的问题是分开的。事实上,显示问题是打印到标准输出时舍入的结果。通过提高小数精度或使用
std::fixed
,显示问题得以解决

std::cout << std::fixed << bg::wkt(result) << std::endl;

std::cout似乎确实存在准确性问题。我试着解决问题,但没有达到我想要的程度

BGL使用一些硬限定的
std::abs
std::acos
调用,这使得使用多精度类型变得很困难。我试着修补其中的一些,但拉比特洞太深了,一个下午都打不开

这是一个可能有助于进一步精确定位/调试/跟踪事物的测试平台。注意

  • 对于
    float
    而言,精度应确保库
    有效
    将报告由于尖峰而无效
  • long-double
    似乎做得很合理
然而,总体问题(缺乏控制/可预测性)仍然存在

imk_

#include <boost/geometry.hpp>
#include <iostream>

#ifdef TRY_BOOST_MULTIPRECISION
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/multiprecision/cpp_bin_float.hpp>
    namespace bmp = boost::multiprecision;
    using OctFloat    = bmp::cpp_bin_float_oct;
    using Decimal     = bmp::number<bmp::cpp_dec_float<50>,  bmp::et_off>;
    using LongDecimal = bmp::number<bmp::cpp_dec_float<100>, bmp::et_off>;

    namespace boost::multiprecision {
        inline auto mod(OctFloat    const& a, OctFloat    const& b) { return fmod(a, b); }
        inline auto mod(Decimal     const& a, Decimal     const& b) { return fmod(a, b); }
        inline auto mod(LongDecimal const& a, LongDecimal const& b) { return fmod(a, b); }
        inline auto abs(OctFloat    const& a) { return fabs(a); }
        inline auto abs(Decimal     const& a) { return fabs(a); }
        inline auto abs(LongDecimal const& a) { return fabs(a); }
    }

    namespace std { // sadly BG overqualifies std::abs in places
        inline auto abs(OctFloat    const& a) { return fabs(a); }
    }
#endif

template <typename F, typename DegreeOrRadian>
void do_test(int n, F offset = {}) {
    namespace bg = boost::geometry;
    std::cout << "----- " << __PRETTY_FUNCTION__ << " n:" << n << " offset: " << offset << " ----\n";
    bg::model::point<F, 2, bg::cs::geographic<bg::degree> > Amsterdam { 4.9, 52.1 + offset };
    typedef bg::model::point<F, 2, bg::cs::geographic<DegreeOrRadian> > point;

    // Declare the geographic_point_circle strategy (with n points)
    // Default template arguments (taking Andoyer strategy)
    bg::strategy::buffer::geographic_point_circle<> point_strategy(n);

    // Declare the distance strategy (one kilometer, around the point, on Earth)
    bg::strategy::buffer::distance_symmetric<F> distance_strategy(10.0);

    // Declare other necessary strategies, unused for point
    bg::strategy::buffer::join_round    join_strategy;
    bg::strategy::buffer::end_round     end_strategy;
    bg::strategy::buffer::side_straight side_strategy;

    // Declare/fill a point on Earth, near Amsterdam
    point p;
    bg::convert(Amsterdam, p);

    // Create the buffer of a point on the Earth
    bg::model::multi_polygon<bg::model::polygon<point> > result;
    bg::buffer(p, result,
                distance_strategy, side_strategy,
                join_strategy, end_strategy, point_strategy);

    std::string reason;
    is_valid(result, reason);
    //std::cout << "result: " << wkt(result) << "\n";
    std::cout << reason << "\n";
    std::cout << "result: " << (bg::is_simple(result)?"simple":"compound") << "\n";

    auto area = bg::area(result);

    std::cout << "reference: " << bg::dsv(Amsterdam)  << std::endl;
    std::cout << "point: " << bg::dsv(p)  << std::endl;
    std::cout << "area: " <<  area << " m²" << std::endl;
}

int main() {
    for (long double offset : { 0.l/*, 1e-7l*/ }) {
        for (int n : { 36 }) {
            do_test<float,       boost::geometry::degree>(n, offset);
            do_test<double,      boost::geometry::degree>(n, offset);
            do_test<long double, boost::geometry::degree>(n, offset);

            do_test<float,       boost::geometry::radian>(n, offset);
            do_test<double,      boost::geometry::radian>(n, offset);
            do_test<long double, boost::geometry::radian>(n, offset);

            // not working yet
            //do_test<OctFloat,    boost::geometry::radian>(n, offset);
            //do_test<Decimal,     boost::geometry::degree>();
            //do_test<LongDecimal, boost::geometry::degree>();
        }
    }
}
在我的机器上



CharStyle超过处理时间

据我所知,有两个不准确的来源,面积算法和点上的地理缓冲区算法

对于前者提出了一个解决方案。使用此修复程序,上述错误函数(godbolt.org/z/sTGqKK)返回的相对错误小于1%。下面的代码通过使用策略扩展了这一点

#include <boost/geometry.hpp>
#include <cmath>
#include <iostream>
 
template <typename CT>
void error_function(CT area, CT theoreticalArea)
{
    std::cout << "area: " <<  area << " m², ";
    std::cout << "error: " <<  area - theoreticalArea << " m²,\t";
    std::cout << "normalised error: " <<  fabs(100 * (area - theoreticalArea)
        / theoreticalArea) << "%" << std::endl;
}

template <typename F, typename DegreeOrRadian>
void do_test(int n, F radius, F offset = {}) {
    namespace bg = boost::geometry;

    std::cout
        << "----- " << __PRETTY_FUNCTION__
        << " n:" << n << " radius:" << radius << " offset:" << offset
        << " ----\n";

    bg::model::point<F, 2, bg::cs::geographic<bg::degree> > Amsterdam { 4.9, 52.1 + offset };
    typedef bg::model::point<F, 2, bg::cs::geographic<DegreeOrRadian> > point;

    // Declare the geographic_point_circle strategy (with n points)
    // Default template arguments (taking Andoyer strategy)
    bg::strategy::buffer::geographic_point_circle<> point_strategy(n);

    // Declare the distance strategy (ten metres, around the point, on Earth)
    bg::strategy::buffer::distance_symmetric<F> distance_strategy(radius);

    // Declare other necessary strategies, unused for point
    bg::strategy::buffer::join_round    join_strategy;
    bg::strategy::buffer::end_round     end_strategy;
    bg::strategy::buffer::side_straight side_strategy;

    // Declare/fill a point on Earth, near Amsterdam
    point p;
    bg::convert(Amsterdam, p);

    // Create the buffer of a point on the Earth
    bg::model::multi_polygon<bg::model::polygon<point> > result;
    bg::buffer(p, result,
                distance_strategy, side_strategy,
                join_strategy, end_strategy, point_strategy);

    auto area = bg::area(result);
    auto areat = bg::area(result,bg::strategy::area::geographic<bg::strategy::thomas>());
    auto areav = bg::area(result,bg::strategy::area::geographic<bg::strategy::vincenty>());
    auto areak = bg::area(result,bg::strategy::area::geographic<bg::strategy::karney>());

    // Assumes that the Earth is flat, which it clearly is.
    // A = n/2 * R^2 * sin(2*pi/n) where R is the circumradius
    auto theoreticalArea = n * radius * radius * std::sin(2.0 * 3.142 / n) / 2.0;

    std::cout << "reference: " << bg::dsv(Amsterdam)  << std::endl;
    std::cout << "point: " << bg::dsv(p)  << std::endl;
    std::cout << "radius: " <<  radius << " m" << std::endl;
    error_function(area, theoreticalArea);
    error_function(areat, theoreticalArea);
    error_function(areav, theoreticalArea);
    error_function(areak, theoreticalArea);
}

int main() {
    double offset = 1e-7;
    int n = 8;

    do_test<double,      boost::geometry::degree>(n, 10.);
    do_test<long double, boost::geometry::degree>(n, 10.);

    do_test<double,      boost::geometry::radian>(n, 10.);
    do_test<long double, boost::geometry::radian>(n, 10.);

    do_test<double,      boost::geometry::degree>(n, 10., offset);
    do_test<long double, boost::geometry::degree>(n, 10., offset);

    do_test<double,      boost::geometry::degree>(n, 1000.);
    do_test<double,      boost::geometry::degree>(n, 1000., offset);

    do_test<double,      boost::geometry::degree>(n, 1.);
    do_test<long double, boost::geometry::degree>(n, 1.);
}
一些评论:

  • 使用不同的策略(即在boost geometry中执行地理计算的算法)控制算法的精度和性能
  • 地理缓冲区中仍然存在一个问题,请随时在github上提交一个问题以保持跟踪
  • “理论面积”仅适用于小面积,随着面积的增长,预计助推几何算法将比该面积更精确
  • 地球不是平的;)
有关boost::geometry::buffer工作原理的更多详细信息:如果我阅读,我认为我看不到双精度的实际数字,但单精度精度精度列出了最差情况1.64-2.37m精度。如果你将半径为10m的圆除以这种精度,就像在整数网格(10m/~2m=5)上画一个r=5的圆。这可以解释你展示的形状。“分辨率对于GPS来说已经足够了”这一点几乎不相关,因为你也不能用GPS精确地勾勒出一个半径为10米的圆。@sehe是的,我们看到的几乎就像它显示一个浮动一样。double的机器epsilon是2.22e-16,根据我们的研究,它的刻度精度应该远小于1mm(精确到2.471304e-11米)。到目前为止,这是一个勇敢的努力,感谢您的研究。正如您所说,它在编译器资源管理器上超时,所以我需要进一步研究您的测试结果以进行验证。然而,我已经尝试在我的生产环境中使用了
longdouble
,尽管如此,我仍然得到了各种各样的领域。然而,我注意到的一件事——一条线索——是圆多边形的内存表示似乎是正确的,但WKT输出却不是。我手工绘制了多边形结果的外环(num points=8),它确实是一个八角形。但没有解释区域问题。您可能也想在邮件列表中发布@adamwulkiewicz@barendgehrels@mloskot和其他人在那里通常反应很快,拥有GIS技能。我不了解这些投影/坐标系统是如何实现的。我验证了你的发现,并编写了一个错误函数来显示这些区域有多远。现在只是等待邮件列表的回复。我们已经为此提交了一份错误报告,因为我认为这是一个可以避免的暗流:问题是,如果你在AARC64(比如智能手机,甚至是新的M1苹果Mac)上编译,就不可能得到好的结果:
long double
似乎不受支持。感谢更新。我同意这似乎不太理想。该票将有助于未来的访客/参考!Vissarion干得好@请注意,
bg::convert
不会在单位之间进行转换,因此使用弧度的示例无法正常工作。您可以使用
bg::transform
。看见
#include <boost/geometry.hpp>
#include <cmath>
#include <iostream>
 
template <typename CT>
void error_function(CT area, CT theoreticalArea)
{
    std::cout << "area: " <<  area << " m², ";
    std::cout << "error: " <<  area - theoreticalArea << " m²,\t";
    std::cout << "normalised error: " <<  fabs(100 * (area - theoreticalArea)
        / theoreticalArea) << "%" << std::endl;
}

template <typename F, typename DegreeOrRadian>
void do_test(int n, F radius, F offset = {}) {
    namespace bg = boost::geometry;

    std::cout
        << "----- " << __PRETTY_FUNCTION__
        << " n:" << n << " radius:" << radius << " offset:" << offset
        << " ----\n";

    bg::model::point<F, 2, bg::cs::geographic<bg::degree> > Amsterdam { 4.9, 52.1 + offset };
    typedef bg::model::point<F, 2, bg::cs::geographic<DegreeOrRadian> > point;

    // Declare the geographic_point_circle strategy (with n points)
    // Default template arguments (taking Andoyer strategy)
    bg::strategy::buffer::geographic_point_circle<> point_strategy(n);

    // Declare the distance strategy (ten metres, around the point, on Earth)
    bg::strategy::buffer::distance_symmetric<F> distance_strategy(radius);

    // Declare other necessary strategies, unused for point
    bg::strategy::buffer::join_round    join_strategy;
    bg::strategy::buffer::end_round     end_strategy;
    bg::strategy::buffer::side_straight side_strategy;

    // Declare/fill a point on Earth, near Amsterdam
    point p;
    bg::convert(Amsterdam, p);

    // Create the buffer of a point on the Earth
    bg::model::multi_polygon<bg::model::polygon<point> > result;
    bg::buffer(p, result,
                distance_strategy, side_strategy,
                join_strategy, end_strategy, point_strategy);

    auto area = bg::area(result);
    auto areat = bg::area(result,bg::strategy::area::geographic<bg::strategy::thomas>());
    auto areav = bg::area(result,bg::strategy::area::geographic<bg::strategy::vincenty>());
    auto areak = bg::area(result,bg::strategy::area::geographic<bg::strategy::karney>());

    // Assumes that the Earth is flat, which it clearly is.
    // A = n/2 * R^2 * sin(2*pi/n) where R is the circumradius
    auto theoreticalArea = n * radius * radius * std::sin(2.0 * 3.142 / n) / 2.0;

    std::cout << "reference: " << bg::dsv(Amsterdam)  << std::endl;
    std::cout << "point: " << bg::dsv(p)  << std::endl;
    std::cout << "radius: " <<  radius << " m" << std::endl;
    error_function(area, theoreticalArea);
    error_function(areat, theoreticalArea);
    error_function(areav, theoreticalArea);
    error_function(areak, theoreticalArea);
}

int main() {
    double offset = 1e-7;
    int n = 8;

    do_test<double,      boost::geometry::degree>(n, 10.);
    do_test<long double, boost::geometry::degree>(n, 10.);

    do_test<double,      boost::geometry::radian>(n, 10.);
    do_test<long double, boost::geometry::radian>(n, 10.);

    do_test<double,      boost::geometry::degree>(n, 10., offset);
    do_test<long double, boost::geometry::degree>(n, 10., offset);

    do_test<double,      boost::geometry::degree>(n, 1000.);
    do_test<double,      boost::geometry::degree>(n, 1000., offset);

    do_test<double,      boost::geometry::degree>(n, 1.);
    do_test<long double, boost::geometry::degree>(n, 1.);
}
----- void do_test(int, F, F) [with F = double; DegreeOrRadian = boost::geometry::degree] n:8 radius:10 offset:0 ----
reference: (4.9, 52.1)
point: (4.9, 52.1)
radius: 10 m
area: 281.272 m²,   error: -1.59991 m²,     normalised error: 0.565596%
area: 282.843 m²,   error: -0.0284134 m²,   normalised error: 0.0100446%
area: 281.749 m²,   error: -1.12206 m²,     normalised error: 0.396666%
area: 282.843 m²,   error: -0.028415 m²,    normalised error: 0.0100452%
----- void do_test(int, F, F) [with F = long double; DegreeOrRadian = boost::geometry::degree] n:8 radius:10 offset:0 ----
reference: (4.9, 52.1)
point: (4.9, 52.1)
radius: 10 m
area: 283.57 m²,    error: 0.698736 m²,     normalised error: 0.247015%
area: 282.843 m²,   error: -0.0284201 m²,   normalised error: 0.010047%
area: 283.568 m²,   error: 0.696594 m²,     normalised error: 0.246258%
area: 282.843 m²,   error: -0.0284255 m²,   normalised error: 0.0100489%
----- void do_test(int, F, F) [with F = double; DegreeOrRadian = boost::geometry::radian] n:8 radius:10 offset:0 ----
reference: (4.9, 52.1)
point: (4.9, 52.1)
radius: 10 m
area: 282.715 m²,   error: -0.156633 m²,    normalised error: 0.0553726%
area: 282.843 m²,   error: -0.0286857 m²,   normalised error: 0.0101409%
area: 280.578 m²,   error: -2.29311 m²,     normalised error: 0.810656%
area: 282.843 m²,   error: -0.0286896 m²,   normalised error: 0.0101423%
----- void do_test(int, F, F) [with F = long double; DegreeOrRadian = boost::geometry::radian] n:8 radius:10 offset:0 ----
reference: (4.9, 52.1)
point: (4.9, 52.1)
radius: 10 m
area: 283.135 m²,   error: 0.263058 m²,     normalised error: 0.0929955%
area: 282.843 m²,   error: -0.0287086 m²,   normalised error: 0.010149%
area: 283.164 m²,   error: 0.292786 m²,     normalised error: 0.103505%
area: 282.843 m²,   error: -0.0287018 m²,   normalised error: 0.0101466%
----- void do_test(int, F, F) [with F = double; DegreeOrRadian = boost::geometry::degree] n:8 radius:10 offset:1e-07 ----
reference: (4.9, 52.1)
point: (4.9, 52.1)
radius: 10 m
area: 281.749 m²,   error: -1.12206 m²,     normalised error: 0.396666%
area: 282.843 m²,   error: -0.0283973 m²,   normalised error: 0.010039%
area: 281.749 m²,   error: -1.12206 m²,     normalised error: 0.396666%
area: 282.843 m²,   error: -0.0284534 m²,   normalised error: 0.0100588%
----- void do_test(int, F, F) [with F = long double; DegreeOrRadian = boost::geometry::degree] n:8 radius:10 offset:1e-07 ----
reference: (4.9, 52.1)
point: (4.9, 52.1)
radius: 10 m
area: 283.569 m²,   error: 0.697826 m²,     normalised error: 0.246694%
area: 282.843 m²,   error: -0.0284078 m²,   normalised error: 0.0100427%
area: 283.568 m²,   error: 0.696529 m²,     normalised error: 0.246235%
area: 282.843 m²,   error: -0.0283946 m²,   normalised error: 0.010038%
----- void do_test(int, F, F) [with F = double; DegreeOrRadian = boost::geometry::degree] n:8 radius:1000 offset:0 ----
reference: (4.9, 52.1)
point: (4.9, 52.1)
radius: 1000 m
area: 2.82843e+06 m²,   error: -284.28 m²,  normalised error: 0.0100498%
area: 2.82843e+06 m²,   error: -284.27 m²,  normalised error: 0.0100494%
area: 2.82843e+06 m²,   error: -284.259 m², normalised error: 0.0100491%
area: 2.82843e+06 m²,   error: -284.27 m²,  normalised error: 0.0100494%
----- void do_test(int, F, F) [with F = double; DegreeOrRadian = boost::geometry::degree] n:8 radius:1000 offset:1e-07 ----
reference: (4.9, 52.1)
point: (4.9, 52.1)
radius: 1000 m
area: 2.82843e+06 m²,   error: -284.372 m², normalised error: 0.010053%
area: 2.82843e+06 m²,   error: -284.27 m²,  normalised error: 0.0100494%
area: 2.82843e+06 m²,   error: -284.282 m², normalised error: 0.0100499%
area: 2.82843e+06 m²,   error: -284.27 m²,  normalised error: 0.0100494%
----- void do_test(int, F, F) [with F = double; DegreeOrRadian = boost::geometry::degree] n:8 radius:1 offset:0 ----
reference: (4.9, 52.1)
point: (4.9, 52.1)
radius: 1 m
area: 2.81749 m²,   error: -0.0112205 m²,   normalised error: 0.396663%
area: 2.8285 m²,    error: -0.000219998 m², normalised error: 0.0077773%
area: 2.83391 m²,   error: 0.0051987 m²,    normalised error: 0.183783%
area: 2.82848 m²,   error: -0.000234082 m², normalised error: 0.00827521%
----- void do_test(int, F, F) [with F = long double; DegreeOrRadian = boost::geometry::degree] n:8 radius:1 offset:0 ----
reference: (4.9, 52.1)
point: (4.9, 52.1)
radius: 1 m
area: 2.83535 m²,   error: 0.00663946 m²,   normalised error: 0.234717%
area: 2.82844 m²,   error: -0.000278463 m², normalised error: 0.00984417%
area: 2.83392 m²,   error: 0.005205 m²,     normalised error: 0.184006%
area: 2.82842 m²,   error: -0.000294424 m², normalised error: 0.0104084%