使用 boost::karma 格式化 latitude/longitude 字符串
using boost::karma to format latitude/longitude strings
我需要将 double
值格式化为具有非常特定格式的坐标字符串,"DDMMSS.SSX"
其中:
- "DD"是满度
- "MM"是整分钟
- "SS.SS"是带小数的秒数
- "X" 是 "N" 或 "S" 取决于半球
字段需要用零填充。不能接受空格。格式示例如下:
47.2535 ==> "471512.45N"
-0.123345 ==> "000724.04S"
我已经成功创建了以下程序来完成这项工作。但是我有一些问题:
locls
规则是否有更优雅的方式?它的目的是将绝对值存储到局部变量value
中。是否有(希望更优雅)访问 fabs()
函数的方法?
- 在我看来,对
_1
(_1 = _val
等)的赋值是不必要的,因为我在局部变量 value
中有值。但是,如果我删除这些分配,我得到的只是 "000000.00N"
。
- 这个格式的"workhorse"就是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;
}
}
更新:
只是为了完整起见,在任何示例(和答案)中修复这实际上都是微不足道的
纬度的格式是"DDMMSS.SSX"
,经度是"DDDMMSS.SSX"
。这是因为纬度的范围是-90到+90,而经度是-180到+180。
关注点分离。
你的语法已经变得一团糟,因为你试图将所有逻辑都塞到一个地方,而这并不能真正负担得起。
与此同时,您已将生成器设为有状态,这意味着性能也会下降。
相反,意识到您有一个数学变换(实值)-> 元组(度、分、秒、半球)。让我们创建一个小助手来建模:
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))
{ }
};
现在,您可以有这样的规则:
karma::rule<iterator_type, LatLongRep()> latitude, longitude;
而且它们的实现很简单:
latitude =
right_align(3, '0') [ uint_ ]
<< right_align(2, '0') [ uint_ ]
<< right_align(5, '0') [ seconds ]
<< east_west;
演示
所以整个程序变成:
#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;
}
}
版画
(true) 47.2535 ==> 0471512.46E
(true) 13.9844 ==> 0135903.71E
(true) -0.123345 ==> 0000724.04W
(true) -44.3 ==> 0441760.00W
其他notes/tricks:
使用派生的 real_policy
命名为 secfmt
以 2 位小数格式化秒;参见 documentation
使用融合自适应获取LatLongRep
的字段,而无需过多使用语义动作and/or Phoenix 绑定(参见tutorial example). See also Boost Spirit: "Semantic actions are evil"?
使用karma::symbols<>
格式化半球指标:
karma::symbols<bool, char> east_west, north_south;
east_west.add (true, 'E')(false, 'W');
north_south.add(true, 'N')(false, 'S');
发电机构造现在脱离循环 - 这大大提高了速度
同时使用定义的纬度和经度留作 reader
的练习
想一想,我来回答
Q. is there generally a better solution for this kind of problem?
在这方面,使用 Boost 格式可能会更好。重用 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" }; }
}
这完全放弃了 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";
}
演示
#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";
}
版画
0471512.46E 0471512.46S
0135903.71E 0135903.71S
0000724.04W 0000724.04N
0441760.00W 0441760.00N
我需要将 double
值格式化为具有非常特定格式的坐标字符串,"DDMMSS.SSX"
其中:
- "DD"是满度
- "MM"是整分钟
- "SS.SS"是带小数的秒数
- "X" 是 "N" 或 "S" 取决于半球
字段需要用零填充。不能接受空格。格式示例如下:
47.2535 ==> "471512.45N"
-0.123345 ==> "000724.04S"
我已经成功创建了以下程序来完成这项工作。但是我有一些问题:
locls
规则是否有更优雅的方式?它的目的是将绝对值存储到局部变量value
中。是否有(希望更优雅)访问fabs()
函数的方法?- 在我看来,对
_1
(_1 = _val
等)的赋值是不必要的,因为我在局部变量value
中有值。但是,如果我删除这些分配,我得到的只是"000000.00N"
。 - 这个格式的"workhorse"就是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;
}
}
更新:
只是为了完整起见,在任何示例(和答案)中修复这实际上都是微不足道的
纬度的格式是"DDMMSS.SSX"
,经度是"DDDMMSS.SSX"
。这是因为纬度的范围是-90到+90,而经度是-180到+180。
关注点分离。
你的语法已经变得一团糟,因为你试图将所有逻辑都塞到一个地方,而这并不能真正负担得起。
与此同时,您已将生成器设为有状态,这意味着性能也会下降。
相反,意识到您有一个数学变换(实值)-> 元组(度、分、秒、半球)。让我们创建一个小助手来建模:
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))
{ }
};
现在,您可以有这样的规则:
karma::rule<iterator_type, LatLongRep()> latitude, longitude;
而且它们的实现很简单:
latitude =
right_align(3, '0') [ uint_ ]
<< right_align(2, '0') [ uint_ ]
<< right_align(5, '0') [ seconds ]
<< east_west;
演示
所以整个程序变成:
#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;
}
}
版画
(true) 47.2535 ==> 0471512.46E
(true) 13.9844 ==> 0135903.71E
(true) -0.123345 ==> 0000724.04W
(true) -44.3 ==> 0441760.00W
其他notes/tricks:
使用派生的
real_policy
命名为secfmt
以 2 位小数格式化秒;参见 documentation使用融合自适应获取
LatLongRep
的字段,而无需过多使用语义动作and/or Phoenix 绑定(参见tutorial example). See also Boost Spirit: "Semantic actions are evil"?使用
karma::symbols<>
格式化半球指标:karma::symbols<bool, char> east_west, north_south; east_west.add (true, 'E')(false, 'W'); north_south.add(true, 'N')(false, 'S');
发电机构造现在脱离循环 - 这大大提高了速度
同时使用定义的纬度和经度留作 reader
的练习
想一想,我来回答
Q. is there generally a better solution for this kind of problem?
在这方面,使用 Boost 格式可能会更好。重用 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" }; }
}
这完全放弃了 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";
}
演示
#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";
}
版画
0471512.46E 0471512.46S
0135903.71E 0135903.71S
0000724.04W 0000724.04N
0441760.00W 0441760.00N