使用boost :: karma来格式化纬度/经度字符串

use*_*573 5 c++ string-formatting boost-spirit coordinates

我需要将double值格式化为具有非常特定格式的坐标字符串,"DDMMSS.SSX"其中:

  • "DD"是完整的学位
  • "MM"是完整的分钟
  • "SS.SS"是分数的秒数
  • 取决于半球,"X"是"N"或"S"

字段需要用零填充.空间不能被接受.格式化示例如下:

47.2535 ==> "471512.45N"
-0.123345 ==> "000724.04S"
Run Code Online (Sandbox Code Playgroud)

我已设法创建以下程序来完成这项工作.不过我有一些问题:

  • 这条locls规则有更优雅的方式吗?它的目的是将绝对值存储到局部变量中value.是否有(希望更优雅)的方式来访问该fabs()功能?
  • 在我看来,_1(_1 = _val等)的赋值是不必要的,因为我在局部变量中有值value.但是,如果我删除这些作业,我得到的只是"000000.00N".
  • 这种格式化的"主力"是int_生成器,我在计算和转换原始数据后使用它value.有更好的方法吗?
  • 对于这类问题,通常有更好的解决方案吗?

我很乐意收到一些反馈意见

#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/bind.hpp>

namespace karma = boost::spirit::karma;

typedef std::back_insert_iterator<std::string> iterator_type;

struct genLongitude : karma::grammar<iterator_type, double()>
{
    genLongitude()
        :   genLongitude::base_type(start)
    {
        using karma::eps;
        using karma::int_;
        using karma::char_;
        using karma::_1;
        using karma::_val;
        using karma::right_align;
        using boost::phoenix::static_cast_;
        using boost::phoenix::ref;
        using boost::phoenix::if_;

        start = locls
                << degrees << minutes << seconds
                << ( eps(_val < 0.0) << char_('E') | char_('W')   );

        locls = eps[_1 = _val, if_(_val < 0.0) [ref(value) = - _val] .else_ [ref(value) = _val]];

        degrees = right_align(3,char_('0'))[int_[_1 = static_cast_<int>(ref(value))]]
                  << eps[ref(value) = (ref(value) - static_cast_<int>(ref(value))) * 60 ];

        minutes = right_align(2,char_('0'))[int_[_1 = static_cast_<int>(ref(value))]]
                  << eps[ref(value) = (ref(value) - static_cast_<int>(ref(value))) * 60 ];

        seconds = right_align(2,char_('0'))[int_[_1 = static_cast_<int>(ref(value))]]
                  << char_(".")
                  << eps[ref(value) = (ref(value) - static_cast_<int>(ref(value))) * 100 ]
                  << right_align(2,char_('0'))[int_[_1 = static_cast_<int>(ref(value))]];
    }

private:
    double value;

    karma::rule<iterator_type, double()>    start, locls, degrees, minutes, seconds;
};

int main()
{
    for(auto & value : std::vector<double>{ 47.25346, 13.984364, -0.1233453, -44.3 })
    {
        std::string generated;
        iterator_type outiter(generated);
        auto rv = karma::generate(outiter, genLatitude(), value);
        std::cout << "(" << rv << ") " << value << " ==> " << generated << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

更新: 为了完整性,这在任何示例(和答案)中修复实际上是微不足道的.纬度的格式是"DDMMSS.SSX",经度是"DDDMMSS.SSX".这是因为纬度范围是-90到+90,而经度是-180到+180.

seh*_*ehe 6

关注点分离.

你的语法已经变得一团糟,因为你试图将所有逻辑都放在一个地方,而这并不是真正的负担.

与此同时,您已使发电机成为有状态,这意味着性能也会下降.

相反,意识到你有一个数学变换(真正的价值) - >元组(度,分,秒,半球).让我们创建一个小帮手来模拟:

struct LatLongRep {
    bool _hemi; double _deg, _min, _sec;

    LatLongRep(double val) 
      : _hemi(0 < val),
        _min(60  * std::modf(std::abs(val), &_deg)),
        _sec(60  * std::modf(_min, &_min))
        { }
};
Run Code Online (Sandbox Code Playgroud)

现在,你可以有这样的规则:

karma::rule<iterator_type, LatLongRep()> latitude, longitude;
Run Code Online (Sandbox Code Playgroud)

而且他们很容易实现:

    latitude =
            right_align(3, '0') [ uint_ ] 
         << right_align(2, '0') [ uint_ ] 
         << right_align(5, '0') [ seconds ]
         << east_west;
Run Code Online (Sandbox Code Playgroud)

演示

所以整个程序变成:

Live On Coliru

#include <boost/spirit/include/karma.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <cmath>

namespace karma = boost::spirit::karma;

typedef std::back_insert_iterator<std::string> iterator_type;

struct LatLongRep {
    bool _hemi; double _deg, _min, _sec;

    LatLongRep(double val) 
      : _hemi(0 < val),
        _min(60  * std::modf(std::abs(val), &_deg)),
        _sec(60  * std::modf(_min, &_min))
        { }
};

BOOST_FUSION_ADAPT_STRUCT(LatLongRep, _deg, _min, _sec, _hemi)

struct genLatLong : karma::grammar<iterator_type, double()> {
    genLatLong() : genLatLong::base_type(start)
    {
        using namespace karma;

        east_west.add  (true, 'E')(false, 'W');
        north_south.add(true, 'N')(false, 'S');

        start    = latitude;

        latitude =
                right_align(3, '0') [ uint_ ] 
             << right_align(2, '0') [ uint_ ] 
             << right_align(5, '0') [ seconds ]
             << east_west;

        longitude =
                right_align(3, '0') [ uint_ ] 
             << right_align(2, '0') [ uint_ ] 
             << right_align(5, '0') [ seconds ]
             << north_south;
    }

private:
    struct secfmt : karma::real_policies<double> {
        unsigned precision(double)      const { return 2;    }
        bool     trailing_zeros(double) const { return true; }
    };
    karma::real_generator<double, secfmt>    seconds;

    karma::symbols<bool, char> east_west, north_south;
    karma::rule<iterator_type, double()>     start;
    karma::rule<iterator_type, LatLongRep()> latitude, longitude;
};

int main()
{
    genLatLong const gen;

    for(auto & value : std::vector<double>{ 47.25346, 13.984364, -0.1233453, -44.3 })
    {
        std::string generated;
        iterator_type outiter(generated);
        auto rv = karma::generate(outiter, gen, value);
        std::cout << "(" << std::boolalpha << rv << ") " << value << " ==> " << generated << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

打印

(true) 47.2535 ==> 0471512.46E
(true) 13.9844 ==> 0135903.71E
(true) -0.123345 ==> 0000724.04W
(true) -44.3 ==> 0441760.00W
Run Code Online (Sandbox Code Playgroud)

附加说明/技巧:

  • 使用派生的real_policynamed secfmt来格式化带有2个小数位的秒数; 看文档

  • 使用融合自适应来获得不过度LatLongRep使用语义动作和/或凤凰绑定的领域(参见教程示例).另见Boost Spirit:"语义行为是邪恶的"?

  • 用于karma::symbols<>格式化半球指标:

    karma::symbols<bool, char> east_west, north_south;
    east_west.add  (true, 'E')(false, 'W');
    north_south.add(true, 'N')(false, 'S');
    
    Run Code Online (Sandbox Code Playgroud)
  • 发电机结构现在不在循环中 - 这大大提高了速度

  • 使用所定义的纬度和经度作为读者的练习


seh*_*ehe 3

再考虑一下,我来回答一下

问: 对于此类问题,一般有更好的解决方案吗?

在这种情况下,使用 Boost Format 可能会更好。重用LatLongRep- 我的其他答案中的计算主力,您可以非常轻松地创建 IO 操纵器:

namespace manip {
    struct LatLongRepIO : LatLongRep {
        LatLongRepIO(double val, char const* choices) : LatLongRep(val), _display(choices) { }
      private:
        char const* _display;

        friend std::ostream& operator<<(std::ostream& os, LatLongRepIO const& llr) {
            return os << boost::format("%03d%02d%05.2f%c")
                        % llr._deg % llr._min % llr._sec 
                        % (llr._display[llr._hemi]);
        }
    };

    LatLongRepIO as_latitude (double val) { return { val, "WE" }; }
    LatLongRepIO as_longitude(double val) { return { val, "NS" }; }
}
Run Code Online (Sandbox Code Playgroud)

这放弃了 Boost Spirit、Phoenix 和 Fusion 的使用,使使用变得轻而易举:

int main() {
    using namespace helpers::manip;

    for(double value : { 47.25346, 13.984364, -0.1233453, -44.3 })
        std::cout << as_latitude(value) << "\t" << as_longitude(value) << "\n";
}
Run Code Online (Sandbox Code Playgroud)

演示版

#include <boost/format.hpp>
#include <cmath>

namespace helpers {
    struct LatLongRep {
        bool _hemi; double _deg, _min, _sec;

        LatLongRep(double val) 
          : _hemi(0 < val),
            _min(60  * std::modf(std::abs(val), &_deg)),
            _sec(60  * std::modf(_min, &_min))
        { }
    };

    namespace manip {
        struct LatLongRepIO : LatLongRep {
            LatLongRepIO(double val, char const* choices) : LatLongRep(val), _display(choices) { }
          private:
            char const* _display;

            friend std::ostream& operator<<(std::ostream& os, LatLongRepIO const& llr) {
                return os << boost::format("%03d%02d%05.2f%c")
                            % llr._deg % llr._min % llr._sec 
                            % (llr._display[llr._hemi]);
            }
        };

        LatLongRepIO as_latitude (double val) { return { val, "WE" }; }
        LatLongRepIO as_longitude(double val) { return { val, "NS" }; }
    }
}

#include <iostream>

int main() {
    using namespace helpers::manip;

    for(double value : { 47.25346, 13.984364, -0.1233453, -44.3 })
        std::cout << as_latitude(value) << "\t" << as_longitude(value) << "\n";
}
Run Code Online (Sandbox Code Playgroud)

印刷

0471512.46E  0471512.46S
0135903.71E  0135903.71S
0000724.04W  0000724.04N
0441760.00W  0441760.00N
Run Code Online (Sandbox Code Playgroud)