I need to format double
values into coordinate strings that have a very specific format, "DDMMSS.SSX"
where:
- "DD" is the full degrees
- "MM" is the full minutes
- "SS.SS" is the seconds with fraction
- "X" is either "N" or "S" depending on hemisphere
The fields need to be padded with zeroes. Spaces cannot be accepted. Examples for the formatting is as follows:
47.2535 ==> "471512.45N"
-0.123345 ==> "000724.04S"
I have managed to create the following program that does the job. However I have some questions:
- is there a more elegant way for the
locls
rule? It's purpose is to store the absolute value into the local variablevalue
. Is there a (hopefully more elegant) way to access thefabs()
function? - In my opinion the assignments to
_1
(_1 = _val
etc.) are unnecessary since I have the value in the local variablevalue
. However if I remove these assignments, all I get is"000000.00N"
. - the "workhorse" of this formatting is the int_ generator, which I use after calculating and casting the original
value
. Is there a better approach? - is there generally a better solution for this kind of problem?
I'd be glad for some feedback
#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;
}
}
Update:
Just for completeness, this is actually trivial to fix in any of the examples (and answers)
The format of the Latitude is "DDMMSS.SSX"
, the Longitude is "DDDMMSS.SSX"
. This is because range of the latitude is -90 to +90 while the longitude is -180 to +180.
Giving it some more thought, let me answer
In this you may be better off with Boost Format. Reusing
LatLongRep
- the calculation work-horse from my other answer, you can create IO manipulators really easily:This forgoes the use of Boost Spirit, Phoenix and Fusion alltogether, and makes usage a breeze:
DEMO
Prints
Separation of concerns.
Your grammar has become a mess because you're trying to stuff all logic in one place, that doesn't really afford it.
Meanwhile you've made the generator stateful, meaning that performance is down as well.
Instead, realize you have a mathematical transformation (real value) -> tuple(degrees, minutes, seconds, hemisphere). Let's create a tiny helper to model that:
Now, you can have rules like this:
And they're trivially implemented:
Demo
So the whole program becomes:
Live On Coliru
Prints
Additional notes/tricks:
using the derived
real_policy
namedsecfmt
to format the seconds with 2 decimal places; see documentationusing fusion adaptation to get the fields of
LatLongRep
without excessive use of semantic actions and/or Phoenix binds (see tutorial example). See also Boost Spirit: "Semantic actions are evil"?use of
karma::symbols<>
to format the hemisphere indicator:the generator construction is now out of the loop - which improves speed considerably
using both latitude and longitude as defined is left as an exercise for the reader