123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- #include <iostream>
- #include <cstring>
- #include <fstream>
- #include <vector>
- #include <cmath>
- #include <ctime>
- #include <limits>
- #include <iomanip>
- #include <stdexcept>
- #include <algorithm>
- #include "XMLParser.h"
- #include "Arguments.h"
- // ----------------------------------------------------------------------------
- namespace gpxtools
- {
- class GPXGeoTag : public XMLParserHandler
- {
- public:
- // -- Constructor -----------------------------------------------------------
- GPXGeoTag() :
- _arguments("gpxgeotag [OPTION].. [GPX-FILE] [JPG-FILE]\nGeo tag the photos with the GPX-files.\n", "gpxgeotag v0.1",
- "Geo tag the photos with the GPX-files, based on the time of the photos and the geopositions in the GPX-files."),
- _seconds (_arguments, true, 's', "seconds", "SEC", "the seconds offset for the time of the photos", ""),
- _minutes (_arguments, true, 'm', "minutes", "MIN", "the minutes offset for the time of the photos", ""),
- _hours (_arguments, true, 'h', "hours", "HOURS", "the hours offset for the time of the photos", ""),
- _xmlParser(this),
- _offset(0),
- _jpegnames(),
- _gpxnames(),
- _segments(),
- _timeStr()
- {
- }
- // -- Deconstructor ---------------------------------------------------------
- virtual ~GPXGeoTag()
- {
- }
- // -- 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 (_gpxnames.empty())
- {
- return _xmlParser.parse(std::cin);
- }
- else
- {
- for (auto filename = _gpxnames.begin(); filename != _gpxnames.end(); ++filename)
- {
- if (!parseFile(*filename)) return false;
- }
- }
- return true;
- }
- // -- Check arguments ---------------------------------------------------------
- bool checkArguments(const std::vector<std::string> &filenames)
- {
- _offset = 0;
- if (!_seconds.value().empty())
- {
- try
- {
- _offset += std::stoi(_seconds.value());
- }
- catch (...)
- {
- std::cerr << "Invalid value for --" << _seconds.longOption() << " : " << _seconds.value() << std::endl;
- return false;
- }
- }
- if (!_minutes.value().empty())
- {
- try
- {
- _offset += (std::stoi(_minutes.value()) * 60);
- }
- catch (...)
- {
- std::cerr << "Invalid value for --" << _minutes.longOption() << " : " << _minutes.value() << std::endl;
- return false;
- }
- }
- if (!_hours.value().empty())
- {
- try
- {
- _offset += (std::stoi(_hours.value()) * 3600);
- }
- catch (...)
- {
- std::cerr << "Invalid value for --" << _hours.longOption() << " : " << _hours.value() << std::endl;
- return false;
- }
- }
- for (auto filename = filenames.begin(); filename != filenames.end(); ++filename)
- {
- std::size_t pos = filename->find_last_of('.');
- if (pos == std::string::npos)
- {
- std::cerr << "File " << *filename << " ignored" << std::endl;
- continue;
- }
- std::string extension = filename->substr(pos+1);
- std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
- if ((extension == "jpg") || (extension == "jpeg"))
- {
- _jpegnames.push_back(*filename);
- }
- else if (extension == "gpx")
- {
- _gpxnames.push_back(*filename);
- }
- else
- {
- std::cerr << "Unsupported file " << *filename << " ignored" << std::endl;
- }
- }
- if (_jpegnames.empty())
- {
- std::cerr << "No photo files present" << std::endl;
- return false;
- }
- return true;
- }
- // -- Perform the geo-tagging -----------------------------------------------
- void perform()
- {
- for (auto photo = _jpegnames.begin(); photo != _jpegnames.end(); ++photo)
- {
- time_t time = readExiv2Time(*photo) + _offset;
- if (time == time_t(-1))
- {
- std::cerr << "No Exif.Image.DateTime present in " << *photo << std::endl;
- continue;
- }
- double lat;
- double lon;
- if (!findGeoPosition(time, lat,lon))
- {
- std::cerr << "No GeoPosition found for " << *photo << std::endl;
- continue;
- }
- if (!updateExiv2GeoPosition(*photo, lat, lon))
- {
- std::cerr << "Unable to update the GeoPosition for " << *photo << std::endl;
- }
- std::cout << std::fixed << std::setprecision(5) << "GeoPosition (" << lat << "," << lon << ") set for " << *photo << std::endl;
- }
- }
- // -- 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 -----------------------------------------------------------------
- struct Point
- {
- Point() : _lat(0.0), _lon(0.0), _time(-1) {}
- double _lat;
- double _lon;
- time_t _time;
- };
- typedef std::vector<Point> Segment;
- typedef std::vector<Segment> Segments;
- // -- 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 processTimeStr(std::string &timeStr, time_t &time)
- {
- time = time_t(-1);
- XMLParser::trim(timeStr);
- if (timeStr.size() == 0) return;
- std::size_t index = timeStr.find(".");
- if (index != std::string::npos)
- {
- timeStr.erase(index, 4); // remove .000 millis
- }
- struct tm fields;
- memset(&fields, 0, sizeof(fields));
- #if __GLIBC__
- if (strptime(timeStr.c_str(), "%FT%T%z", &fields) == nullptr)
- #else
- if (strptime(timeStr.c_str(), "%Y-%m-%dT%TZ", &fields) == nullptr)
- #endif
- {
- std::cout << "Failed to convert: " << timeStr << std::endl;
- return;
- }
- time = mktime(&fields) - ::timezone;
- }
- time_t readExiv2Time(const std::string &filename)
- {
- const std::string Exiv2key = "Exif.Image.DateTime";
- std::string command = "exiv2 -pt -g " + Exiv2key + " print " + filename;
- //std::cout << "Fetch: " << command << std::endl;
- FILE *file = nullptr;
- if ((file = popen(command.c_str(), "r")) == nullptr)
- {
- std::cerr << "Unable to start " << command << ", error: " << errno << std::endl;
- return time_t(-1);
- }
- char buffer[512];
- while (fgets(buffer, sizeof(buffer), file) != nullptr)
- {
- std::string line(buffer);
- std::size_t index = 0;
- std::string key = readWord(line, index, ' ');
- readWord(line, index, ' '); // Type
- readWord(line, index, ' '); // Length
- std::string value = readWord(line, index, '\n');
- if ((key == Exiv2key) && (!value.empty()))
- {
- struct tm fields;
- memset(&fields, 0, sizeof(fields));
- if (strptime(value.c_str(), "%Y:%m:%d %T", &fields) != nullptr)
- {
- return mktime(&fields);
- }
- }
- }
- return time_t(-1);
- }
- std::string readWord(const std::string &line, std::size_t &index, char ch)
- {
- while ((index < line.size()) && (::isspace(line[index])))
- {
- index++;
- }
- std::string word;
- while ((index < line.size()) && (line[index] != ch))
- {
- word += line[index++];
- }
- return word;
- }
- bool findGeoPosition(time_t time, double &lat, double &lon)
- {
- for (auto segment = _segments.begin(); segment != _segments.end(); ++segment)
- {
- if (segment->empty()) continue;
- if ((time < segment->front()._time) || (time > segment->back()._time)) continue;
- auto last = segment->begin();
- for (auto point = segment->begin(); point != segment->end(); ++point)
- {
- if (time == point->_time)
- {
- lat = point->_lat;
- lon = point->_lon;
- return true;
- }
- if (time < point->_time)
- {
- lat = last->_lat + (point->_lat - last->_lat) * (double(time - last->_time) / double(point->_time - last->_time));
- lon = last->_lon + (point->_lon - last->_lon) * (double(time - last->_time) / double(point->_time - last->_time));
- return true;
- }
- last = point;
- }
- }
- return false;
- }
- bool updateExiv2GeoPosition(const std::string &photo, double lat, double lon)
- {
- std::string command = "exiv2 ";
- const std::string NS = (lat < 0 ? "S" : "N");
- command += "-M \"set Exif.GPSInfo.GPSLatitude " + deg2string(std::abs(lat)) + "\" ";
- command += "-M \"set Exif.GPSInfo.GPSLatitudeRef " + NS + "\" ";
- const std::string WE = (lon < 0 ? "W" : "E");
- command += "-M \"set Exif.GPSInfo.GPSLongitude " + deg2string(std::abs(lon)) + "\" ";
- command += "-M \"set Exif.GPSInfo.GPSLongitudeRef " + WE + "\" ";
- command += photo;
- // std::cout << "Update: " << command << std::endl;
- return (system(command.c_str()) == 0);
- }
- std::string deg2string(double position)
- {
- double degrees = 0.0;
- double minutes = 0.0;
- double seconds = 0.0;
- position = modf(position, °rees);
- position *= 60.0;
- position = modf(position, &minutes);
- position *= (60.0 * 100.0);
- modf(position, &seconds);
- return std::to_string(int(degrees)) + "/1 " + std::to_string(int(minutes)) + "/1 " + std::to_string(int(seconds)) + "/100";
- }
- public:
- // -- Callbacks -------------------------------------------------------------
- virtual void startElement(const std::string &path, const std::string &name, const Attributes &attributes)
- {
- if (path == "/gpx/trk/trkseg")
- {
- _segments.push_back(Segment());
- }
- else if (path == "/gpx/trk/trkseg/trkpt")
- {
- Point point;
- if (getDoubleAttribute(attributes, "lat", point._lat) &&
- getDoubleAttribute(attributes, "lon", point._lon))
- {
- _segments.back().push_back(point);
- }
- }
- else if (path == "/gpx/trk/trkseg/trkpt/time")
- {
- // <time>2012-12-03T13:13:38Z</time>
- _timeStr.clear();
- }
- }
- virtual void text(const std::string &path, const std::string &text)
- {
- if (path == "/gpx/trk/trkseg/trkpt/time")
- {
- _timeStr.append(text);
- }
- }
- virtual void endElement(const std::string &path, const std::string &)
- {
- if (path == "/gpx/trk/trkseg/trkpt/time")
- {
- processTimeStr(_timeStr, _segments.back().back()._time);
- }
- }
- private:
- // -- Members ---------------------------------------------------------------
- arg::Arguments _arguments;
- arg::Argument _seconds;
- arg::Argument _minutes;
- arg::Argument _hours;
- XMLParser _xmlParser;
- int _offset;
- std::vector<std::string> _jpegnames;
- std::vector<std::string> _gpxnames;
- Segments _segments;
- std::string _timeStr;
- };
- }
- // -- Main program ------------------------------------------------------------
- int main(int argc, char *argv[])
- {
- gpxtools::GPXGeoTag gpxGeoTag;
- if (gpxGeoTag.processArguments(argc, argv))
- {
- gpxGeoTag.perform();
- return 0;
- }
- return 1;
- }
|