Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using boost::karma to format latitude/longitude strings

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 variable value. Is there a (hopefully more elegant) way to access the fabs() function?
  • In my opinion the assignments to _1 (_1 = _val etc.) are unnecessary since I have the value in the local variable value. 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.

like image 615
user23573 Avatar asked Sep 15 '15 06:09

user23573


1 Answers

Giving it some more thought, let me answer

Q. is there generally a better solution for this kind of problem?

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:

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" }; }
}

This forgoes the use of Boost Spirit, Phoenix and Fusion alltogether, and makes usage a breeze:

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";
}

DEMO

#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";
}

Prints

0471512.46E  0471512.46S
0135903.71E  0135903.71S
0000724.04W  0000724.04N
0441760.00W  0441760.00N
like image 60
sehe Avatar answered Nov 12 '22 10:11

sehe