123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- #include <iostream>
- #include <cstring>
- #include <fstream>
- #include <vector>
- #include <cmath>
- #include <ctime>
- #include <limits>
- #include <iomanip>
- #include <stdexcept>
- #include "XMLRawParser.h"
- #include "Arguments.h"
- #include "Algorithm.h"
- // ----------------------------------------------------------------------------
- namespace gpxtools
- {
- class GPXSplit : public XMLParserHandler
- {
- public:
- // -- Constructor -----------------------------------------------------------
- GPXSplit() :
- _arguments("gpxsplit [OPTION].. [FILE]\nSplit the track segments in a GPX-file.\n", "gpxsplit v0.1",
- "Split the track segments in a GPX-file in multiple track segments based on distance or time and display the resulting GPX-file on standard output."),
- _analyse (_arguments, true, 'a', "analyse", "analyse the file for splitting"),
- _metres (_arguments, true, 'd', "distance", "METRES", "split based on a distance", ""),
- _timestamp(_arguments, true, 't', "time", "YYYY-MM-DD HH:MM:SS", "split based on a timestamp", ""),
- _seconds (_arguments, true, 's', "seconds", "SEC", "split based on a time difference in seconds", ""),
- _minutes (_arguments, true, 'm', "minutes", "MIN", "split based on a time difference in minutes", ""),
- _hours (_arguments, true, 'h', "hours", "HOURS", "split based on a time difference in hours", ""),
- _distance(0),
- _duration(0),
- _time(time_t(-1)),
- _xmlParser(this),
- _TrkNr(0),
- _TrkSegNr(0),
- _inTrkSeg(false)
- {
- }
- // -- Deconstructor ---------------------------------------------------------
- virtual ~GPXSplit()
- {
- }
- // -- Parse arguments -------------------------------------------------------
- bool processArguments(int argc, char *argv[])
- {
- std::vector<std::string> filenames;
- if (!_arguments.parse(argc,argv, filenames))
- {
- return false;
- }
- else if (!checkArguments(filenames))
- {
- return false;
- }
- else if (filenames.empty())
- {
- return _xmlParser.parse(std::cin);
- }
- else
- {
- return parseFile(filenames.front());
- }
- }
- // -- Check arguments ---------------------------------------------------------
- bool checkArguments(const std::vector<std::string> &filenames)
- {
- if (filenames.size() > 1)
- {
- std::cerr << "Too many input files." << std::endl;
- return false;
- }
- if (!_metres.value().empty())
- {
- try
- {
- _distance = std::stoi(_metres.value());
- if (_distance < 0)
- {
- std::cerr << "Invalid value for --" << _metres.longOption() << " : " << _metres.value() << std::endl;
- return false;
- }
- }
- catch(...)
- {
- std::cerr << "Invalid value for --" << _metres.longOption() << " : " << _metres.value() << std::endl;
- return false;
- }
- }
- if (!_timestamp.value().empty())
- {
- struct tm fields = {0};
- if (strptime(_timestamp.value().c_str(), "%Y-%m-%d %H:%M:%S", &fields) == nullptr)
- {
- std::cerr << "Invalid value for --" << _timestamp.longOption() << " : " << _timestamp.value() << std::endl;
- return false;
- }
- _time = mktime(&fields);
- if (_time == (time_t)-1)
- {
- std::cerr << "Invalid time for --" << _timestamp.longOption() << " : " << _timestamp.value() << std::endl;
- return false;
- }
- }
- _duration = 0;
- if (!_seconds.value().empty())
- {
- try
- {
- _duration = std::stoi(_seconds.value());
- if (_duration < 0)
- {
- std::cerr << "Invalid value for --" << _seconds.longOption() << " : " << _seconds.value() << std::endl;
- return false;
- }
- }
- catch(...)
- {
- std::cerr << "Invalid value for --" << _seconds.longOption() << " : " << _seconds.value() << std::endl;
- return false;
- }
- }
- if (!_minutes.value().empty())
- {
- try
- {
- int minutes = std::stoi(_minutes.value());
- if (minutes < 0)
- {
- std::cerr << "Invalid value for --" << _minutes.longOption() << " : " << _minutes.value() << std::endl;
- return false;
- }
- _duration += minutes * 60;
- }
- catch(...)
- {
- std::cerr << "Invalid value for --" << _minutes.longOption() << " : " << _minutes.value() << std::endl;
- return false;
- }
- }
- if (!_hours.value().empty())
- {
- try
- {
- int hours = std::stoi(_hours.value());
- if (hours < 0)
- {
- std::cerr << "Invalid value for --" << _hours.longOption() << " : " << _hours.value() << std::endl;
- return false;
- }
- _duration += hours * 3600;
- }
- catch(...)
- {
- std::cerr << "Invalid value for --" << _hours.longOption() << " : " << _hours.value() << std::endl;
- return false;
- }
- }
- return true;
- }
- // -- Parse a file ----------------------------------------------------------
- bool parseFile(const std::string &filename)
- {
- bool ok =false;
- std::ifstream file(filename);
- if (file.is_open())
- {
- ok = _xmlParser.parse(file);
- file.close();
- }
- else
- {
- std::cerr << "Unable to open: " << filename << std::endl;
- }
- return ok;
- }
- private:
- // -- Types -----------------------------------------------------------------
- enum ChunkType { TEXT, POINT };
- struct Chunk
- {
- Chunk()
- {
- clear(TEXT);
- }
- void clear(ChunkType type)
- {
- _type = type;
- _text.clear();
- _lat = 0.0;
- _lon = 0.0;
- _distance = -1.0;
- _timeStr.clear();
- _time = -1.0;
- }
- ChunkType _type;
- std::string _text;
- double _lat;
- double _lon;
- double _distance;
- std::string _timeStr;
- time_t _time;
- };
- // -- Methods ---------------------------------------------------------------
- static bool getDoubleAttribute(const Attributes &atts, const std::string &key, double &value)
- {
- auto iter = atts.find(key);
- if (iter == atts.end()) return false;
- if (iter->second.empty()) return false;
- try
- {
- value = std::stod(iter->second);
- return true;
- }
- catch(...)
- {
- return false;
- }
- }
- void store(const std::string &text)
- {
- if (_inTrkSeg)
- {
- _current._text.append(text);
- }
- else if (!_analyse.active())
- {
- std::cout << text;
- }
- }
- void processTimeStr(std::string &timeStr, time_t &time)
- {
- time = time_t(-1);
- XMLRawParser::trim(timeStr);
- if (timeStr.size() == 0) return;
- struct tm fields = {0};
- if (strptime(timeStr.c_str(), "%Y-%m-%dT%TZ", &fields) == nullptr) return;
- time = mktime(&fields);
- }
- void analyseChunks()
- {
- int trkPtNr = 0;
- Chunk previous;
- for (auto iter = _chunks.begin(); iter != _chunks.end(); ++iter)
- {
- if (iter->_type == POINT)
- {
- enum {NONE, DISTANCE, TIME, DURATION} reason = NONE;
- trkPtNr++;
- if (_distance > 0 && iter->_distance > _distance)
- {
- reason = DISTANCE;
- }
- else if (_time > 0 && previous._time > 0 && iter->_time > 0 && previous._time <= _time && iter->_time > _time)
- {
- reason = TIME;
- }
- else if (_duration > 0 && previous._time > 0 && iter->_time > 0 && (iter->_time - previous._time) > _duration)
- {
- reason = DURATION;
- }
- if (reason != NONE)
- {
- if (_analyse.active())
- {
- std::cout << "Track: " << _TrkNr << " Segment: " << _TrkSegNr << " Point: " << trkPtNr << " is split on ";
- switch(reason)
- {
- case DISTANCE:
- std::cout << "distance: " << iter->_distance << "m (" << previous._lat << ',' << previous._lon << ") and (" << iter->_lat << ',' << iter->_lon << ")" << std::endl;
- break;
- case TIME:
- std::cout << "time: " << previous._timeStr << " and " << iter->_timeStr << std::endl;
- break;
- case DURATION:
- std::cout << "time duration: " << previous._timeStr << " and " << iter->_timeStr << std::endl;
- break;
- }
- }
- else
- {
- std::cout << _endTrkSeg;
- std::cout << _startTrkSeg;
- }
- }
- previous = *iter;
- }
- if (!_analyse.active())
- {
- std::cout << iter->_text;
- }
- }
- }
- public:
- // -- Callbacks -------------------------------------------------------------
- virtual void unhandled(const std::string &path, const std::string &text)
- {
- store(text);
- }
- virtual void startElement(const std::string &path, const std::string &text, const std::string &, const Attributes &attributes)
- {
- if (path == "/gpx/trk")
- {
- _TrkNr++;
- _TrkSegNr = 0;
- }
- else if (path == "/gpx/trk/trkseg")
- {
- _startTrkSeg = text;
- _chunks.clear();
- _current.clear(TEXT);
- _previous.clear(TEXT);
- _inTrkSeg = true;
- _TrkSegNr++;
- }
- else if (path == "/gpx/trk/trkseg/trkpt")
- {
- if (!_current._text.empty())
- {
- _chunks.push_back(_current);
- }
- _current.clear(TEXT);
- if (getDoubleAttribute(attributes, "lat", _current._lat) &&
- getDoubleAttribute(attributes, "lon", _current._lon))
- {
- _current._type = POINT;
- if (_previous._type == POINT)
- {
- _current._distance = gpx::calcDistance(_previous._lat, _previous._lon, _current._lat, _current._lon);
- }
- }
- }
- else if (path == "/gpx/trk/trkseg/trkpt/time")
- {
- // <time>2012-12-03T13:13:38Z</time>
- _current._timeStr.clear();
- }
- store(text);
- }
- virtual void text(const std::string &path, const std::string &text)
- {
- if (path == "/gpx/trk/trkseg/trkpt/time")
- {
- _current._timeStr.append(text);
- }
- store(text);
- }
- virtual void endElement(const std::string &path, const std::string &text, const std::string &)
- {
- store(text);
- if (path == "/gpx/trk/trkseg")
- {
- _endTrkSeg = text;
- if (!_current._text.empty())
- {
- _chunks.push_back(_current);
- }
- analyseChunks();
- _inTrkSeg = false;
- }
- else if (path == "/gpx/trk/trkseg/trkpt")
- {
- _chunks.push_back(_current);
- if (_current._type == POINT)
- {
- _previous = _current;
- }
- _current.clear(TEXT);
- }
- else if (path == "/gpx/trk/trkseg/trkpt/time")
- {
- processTimeStr(_current._timeStr, _current._time);
- }
- }
- private:
- // -- Members ---------------------------------------------------------------
- arg::Arguments _arguments;
- arg::Argument _analyse;
- arg::Argument _metres;
- arg::Argument _timestamp;
- arg::Argument _seconds;
- arg::Argument _minutes;
- arg::Argument _hours;
- XMLRawParser _xmlParser;
- int _distance;
- int _duration;
- time_t _time;
- int _TrkNr;
- int _TrkSegNr;
- bool _inTrkSeg;
- std::string _startTrkSeg;
- std::string _endTrkSeg;
- Chunk _current;
- Chunk _previous;
- std::vector<Chunk> _chunks;
- };
- }
- // -- Main program ------------------------------------------------------------
- int main(int argc, char *argv[])
- {
- gpxtools::GPXSplit gpxSplit;
- return gpxSplit.processArguments(argc, argv) ? 0 : 1;
- }
|