// ***************************************************************** -*- C++ -*- // geotag.cpp, $Rev: 2286 $ // Sample program to read gpx files and update the images with GPS tags // Work in progress - doesn't do anything yet! #include #include #include #include #include #include #include #include #include "expat.h" #include #include using namespace std; #ifndef lengthof #define lengthof(x) (sizeof(*x)/sizeof(x)) #endif #ifndef nil #define nil NULL #endif #ifndef _MAX_PATH #define _MAX_PATH 1024 #endif #ifdef _MSC_VER #include bool realpath(const char* file,char* path,int path_l=_MAX_PATH); bool realpath(const char* file,char* path,int path_l) { GetFullPathName(file,path_l,path,NULL); return true; } #if _MSC_VER < 1400 #define strcpy_s(d,l,s) strcpy(d,s) #define strcat_s(d,l,s) strcat(d,s) #endif #else #include #include #include #endif // prototypes int getFileType(const char* path ); int getFileType(std::string& path); string getExifTime(const time_t t); time_t parseTime(const char* ,bool bAdjust=true); int timeZoneAdjust(); // Command-line parser class Options { public: bool verbose; Options() { verbose = false; } virtual ~Options() {} ; } ; enum { resultOK=0 , resultSyntaxError , resultSelectFailed }; enum // keyword indices { kwHELP = 0 , kwVERSION , kwVERBOSE , kwADJUST , kwMAX // manages keyword array , kwNEEDVALUE // bogus keywords for error reporting , kwSYNTAX // -- ditto -- , kwNOVALUE = -kwVERBOSE // keywords <= kwNOVALUE are flags (no value needed) }; // file types supported enum { typeUnknown = 0 , typeDirectory = 1 , typeImage = 2 , typeXML = 3 , typeFile = 4 , typeDoc = 5 , typeCode = 6 , typeMax = 7 }; // Position (from gpx file) class Position { public: Position(time_t time,double lat,double lon,double ele) : time_(time),lon_(lon),lat_(lat),ele_(ele) {}; Position() { time_=0 ; lat_=0.0 ; lon_=0.0 ; ele_=0.0 ; }; virtual ~Position() {} ; // copy constructor Position(const Position& o) : time_(o.time_),lon_(o.lon_),lat_(o.lat_),ele_(o.ele_) {}; // methods bool good() { return time_ || lon_ || lat_ || ele_ ; } std::string getTimeString() { if ( times_.empty() ) times_ = getExifTime(time_) ; return times_; } time_t getTime() { return time_ ; } // data private: time_t time_; double lon_ ; double lat_ ; double ele_ ; std::string times_; // public static data public: static int adjust_; static time_t timeDiffMax; } ; // globals typedef std::map TimeDict_t; typedef std::map::iterator TimeDict_i; typedef std::vector strings_t; TimeDict_t gTimeDict ; strings_t gFiles; int Position::adjust_ = timeZoneAdjust(); time_t Position::timeDiffMax = 120 ; /////////////////////////////////////////////////////////// // UserData - used by XML Parser class UserData { public: UserData() : indent(0),count(0),nTrkpt(0),bTime(false),bEle(false) {}; virtual ~UserData() {} ; // public data members int indent; size_t count ; Position now ; Position prev; int nTrkpt; bool bTime ; bool bEle ; double ele; double lat; double lon; std::string xmlt; std::string exift; time_t time; // static public data memembers }; // XML Parser Callbacks static void startElement(void* userData, const char* name, const char** atts ) { UserData* me = (UserData*) userData; //for ( int i = 0 ; i < me->indent ; i++ ) printf(" "); //printf("begin %s\n",name); me->bTime = strcmp(name,"time")==0; me->bEle = strcmp(name,"ele")==0; if ( strcmp(name,"trkpt")==0 ) { me->nTrkpt++; while ( *atts ) { const char* a=atts[0]; const char* v=atts[1]; if ( !strcmp(a,"lat") ) me->lon = atof(v); if ( !strcmp(a,"lon") ) me->lat = atof(v); atts += 2 ; } } me->count++ ; me->indent++ ; } static void endElement(void* userData, const char* name) { UserData* me = (UserData*) userData; me->indent-- ; if ( strcmp(name,"trkpt")==0 ) { me->nTrkpt--; me->now = Position(me->time,me->lat,me->lon,me->ele) ; // printf("lat,lon = %f,%f ele = %f xml = %s exif = %s\n",me->lat,me->lon,me->ele,me->xmlt.c_str(),me->exift.c_str()); // if we have a good previous position // add missed entries to timedict if ( me->prev.good() && (me->now.getTime() - me->prev.getTime()) < Position::timeDiffMax ) { time_t missed = me->prev.getTime() ; while ( ++missed < me->now.getTime() ) gTimeDict[missed] = me->prev ; // Position(missed,me->lat,me->lon,me->ele) ; } // remember our location and put it in gTimeDict gTimeDict[me->time] = me->now ; me->prev = me->now ; } } void charHandler(void* userData,const char* s,int len) { UserData* me = (UserData*) userData; if ( me->nTrkpt == 1 ) { char buffer[100]; int l_max = 98 ; // lengthof(buffer) -2 ; if ( me->bTime && len > 5 ) { if ( len < l_max ) { memcpy(buffer,s,len); buffer[len]=0; char* b = buffer ; while ( *b == ' ' && b < buffer+len ) b++ ; me->xmlt = b ; me->time = parseTime(me->xmlt.c_str()); me->exift = getExifTime(me->time); } me->bTime=false; } if ( me->bEle && len > 5 ) { if ( len < l_max ) { memcpy(buffer,s,len); buffer[len]=0; char* b = buffer ; while ( *b == ' ' && b < buffer+len ) b++ ; me->ele = atof(b); } me->bEle=false; } } } /////////////////////////////////////////////////////////// // Time Functions time_t parseTime(const char* arg,bool bAdjust) { time_t result = 0 ; try { //559 rmills@rmills-imac:~/bin $ exiv2 -pa ~/R.jpg | grep -i date //Exif.Image.DateTime Ascii 20 2009:08:03 08:58:57 //Exif.Photo.DateTimeOriginal Ascii 20 2009:08:03 08:58:57 //Exif.Photo.DateTimeDigitized Ascii 20 2009:08:03 08:58:57 //Exif.GPSInfo.GPSDateStamp Ascii 21 2009-08-03T15:58:57Z // if ( strstr(arg,":") || strstr(arg,"-") ) { int YY,MM,DD,HH,mm,SS ; char a,b,c,d,e ; sscanf(arg,"%d%c%d%c%d%c%d%c%d%c%d",&YY,&a,&MM,&b,&DD,&c,&HH,&d,&mm,&e,&SS); struct tm T; #if 0 int tm_sec; /* seconds (0 - 60) */ int tm_min; /* minutes (0 - 59) */ int tm_hour; /* hours (0 - 23) */ int tm_mday; /* day of month (1 - 31) */ int tm_mon; /* month of year (0 - 11) */ int tm_year; /* year - 1900 */ int tm_wday; /* day of week (Sunday = 0) */ int tm_yday; /* day of year (0 - 365) */ int tm_isdst; /* is summer time in effect? */ char *tm_zone; /* abbreviation of timezone name */ long tm_gmtoff; /* offset from UTC in seconds */ #endif memset(&T,sizeof(T),0); T.tm_min = mm ; T.tm_hour = HH ; T.tm_sec = SS ; if ( bAdjust ) T.tm_sec += Position::adjust_ ; T.tm_year = YY -1900 ; T.tm_mon = MM -1 ; T.tm_mday = DD ; result = mktime(&T); } } catch ( ... ) {}; return result ; } // West of GMT is negative (PDT = Pacific Daylight = -07:00 == -25200 seconds int timeZoneAdjust() { time_t now = time(NULL); struct tm local = *localtime(&now) ; #if defined(_MSC_VER) TIME_ZONE_INFORMATION TimeZoneInfo; GetTimeZoneInformation( &TimeZoneInfo ); int offset = - (((int)TimeZoneInfo.Bias + (int)TimeZoneInfo.DaylightBias) * 60); #elif defined(__CYGWIN__) struct tm lcopy = *localtime(&now); time_t gmt = timegm(&lcopy) ; // timegm modifies lcopy, so don't use local int offset = (int) ( ((long signed int) gmt) - ((long signed int) now) ) ; #else int offset = local.tm_gmtoff ; #endif #if 0 // debugging code struct tm utc = *gmtime(&now); printf("utc : offset = %6d dst = %d time = %s", 0 ,utc .tm_isdst, asctime(&utc )); printf("local: offset = %6d dst = %d time = %s", offset,local.tm_isdst, asctime(&local)); printf("timeZoneAdjust = %6d\n",offset); #endif return offset ; } string getExifTime(const time_t t) { static char result[100]; strftime(result,sizeof(result),"%Y-%m-%d %H:%M:%S",localtime(&t)); return result ; } std::string makePath(std::string dir,std::string file) { return dir + std::string(EXV_SEPERATOR_STR) + file ; } const char* makePath(const char* dir,const char* file) { static char result[_MAX_PATH] ; std::string r = makePath(std::string(dir),std::string(file)); strcpy(result,r.c_str()); return result; } // file utilities bool readDir(const char* path) { bool bResult = false; #ifdef _MSC_VER DWORD attrs = GetFileAttributes(path); bool bOKAttrs = attrs != INVALID_FILE_ATTRIBUTES; bool bIsDir = (attrs & FILE_ATTRIBUTE_DIRECTORY) ? true : false ; if( bOKAttrs && bIsDir ) { bResult = true ; char search[_MAX_PATH+10]; strcpy_s(search,_MAX_PATH,path); strcat_s(search,_MAX_PATH,"\\*"); WIN32_FIND_DATA ffd; HANDLE hFind = FindFirstFile(search, &ffd); BOOL bGo = hFind != INVALID_HANDLE_VALUE; if ( bGo ) { while ( bGo ) { if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { // _tprintf(TEXT(" %s \n"), ffd.cFileName); } else { std::string pathName = makePath(path,std::string(ffd.cFileName)); if ( getFileType(pathName) == typeImage ) { gFiles.push_back( pathName ); } } bGo = FindNextFile(hFind, &ffd) != 0; } CloseHandle(hFind); } } #else DIR* dir = opendir (path); if (dir != NULL) { bResult = true; struct dirent* ent; // print all the files and directories within directory while ((ent = readdir (dir)) != NULL) { printf ("%s\n", ent->d_name); gFiles.push_back( makePath(path,ent->d_name) ) ; } closedir (dir); } #endif return bResult ; } inline size_t sip(FILE* f,char* buffer,size_t max_len,size_t len) { while ( !feof(f) && len < max_len && buffer[len-1] != '>') buffer[len++] = fgetc(f); return len; } bool readXML(const char* path) { FILE* f = fopen(path,"r"); XML_Parser parser = XML_ParserCreate(NULL); bool bResult = f && parser ; if ( bResult ) { char buffer[8*1024]; UserData me ; XML_SetUserData (parser, &me); XML_SetElementHandler (parser, startElement, endElement); XML_SetCharacterDataHandler(parser,charHandler); // a little sip at the data size_t len = fread(buffer,1,sizeof(buffer)-100,f); const char* lead = "readMetadata(); ExifData &exifData = image->exifData(); bResult = !exifData.empty(); } } catch ( ... ) {}; return bResult ; } time_t readImageTime(const char* path) { using namespace Exiv2; time_t result = 0 ; const char* dateStrings[] = { "Exif.Photo.DateTimeOriginal" , "Exif.Photo.DateTimeDigitized" , "Exif.Image.DateTime" , NULL }; const char* ds = dateStrings[0] ; while ( !result && ds++ ) { try { Image::AutoPtr image = ImageFactory::open(path); if ( image.get() ) { image->readMetadata(); ExifData &exifData = image->exifData(); // printf("%s => %s\n",(ds-1), exifData[ds-1].toString().c_str()); result = parseTime(exifData[ds-1].toString().c_str(),false); } } catch ( ... ) {}; } return result ; } bool sin(const char* s,const char** a) { bool bResult = false ; int i = 0 ; while ( !bResult && a[i]) { bResult = stricmp(s,a[i])==0; i++; } return bResult; } int readFile(const char* path) { FILE* f = fopen(path,"r"); int nResult = f ? typeFile : typeUnknown; if ( f ) { const char* docs[] = { ".doc",".txt", nil }; const char* code[] = { ".cpp",".h" ,".pl" ,".py" ,".pyc", nil }; const char* ext = strstr(path,"."); if ( ext ) { if ( sin(ext,docs) ) nResult = typeDoc; if ( sin(ext,code) ) nResult = typeCode; } // fseek(f,0L,SEEK_END); // count = ftell(f); } if ( f ) fclose(f) ; return nResult ; } int getFileType(std::string& path) { return getFileType(path.c_str()); } int getFileType(const char* path) { return readXML (path) ? typeXML : readDir (path) ? typeDirectory : readImage(path) ? typeImage : readFile (path) ; } int version(const char* program) { printf("%s: %s %s\n",program,__DATE__,__TIME__); return 0; } int help(const char* program,char const* words[],int nWords) { printf("usage: %s",program); for ( int i = 0 ; i < nWords ; i++ ) { if ( words[i] ) printf(" -%s%s",words[i],i>(-kwNOVALUE)?" value":""); } printf(" path+\n"); return 0; } int compare(const char* a,const char* b) { int result=*a && *b; while ( result && *a && *b) { char A=*a++; char B=*b++; result=tolower(A)==tolower(B); } return result; } int find(const char* arg,char const* words[],int nWords) { int result=0; int count =0; for ( int i = 0 ; i < nWords ; i++) { int j = 0 ; while ( arg[j] == '-' ) j++; if ( ::compare(arg+j,words[i]) ) { result = i ; count++; } } return count==1?result:kwSYNTAX; } int main(int argc, char* const argv[]) { int result=0; const char* program = argv[0]; const char* types[typeMax]; types[typeUnknown ] = "unknown"; types[typeDirectory] = "directory"; types[typeImage ] = "image"; types[typeXML ] = "xml"; types[typeDoc ] = "doc"; types[typeCode ] = "code"; types[typeFile ] = "file"; char const* keywords[kwMAX]; memset(keywords,0,sizeof(keywords)); keywords[kwHELP] = "help"; keywords[kwVERSION] = "version"; keywords[kwVERBOSE] = "verbose"; keywords[kwADJUST] = "adjust" ; Options options ; if ( argc < 2 ) { ::help(program,keywords,kwMAX); return result ; } for ( int a = 0 ; !result && a < 2 ; a++ ) { // a = 0 is a dry run if ( a && options.verbose ) { int tzadjust = Position::adjust_; printf("tzadjust seconds = %d HH:MM = %c%02d:%02d [-/+ = West/East of UTC]\n",tzadjust,tzadjust<0?'-':'+',abs(tzadjust/3600),tzadjust%3600); for ( TimeDict_i it = gTimeDict.begin() ; it != gTimeDict.end() ; it++) { // printf("time = %s\n",it->second.getTimeString().c_str()); // ,gTimeDict[*it].sTime } for ( unsigned i = 0 ; i < gFiles.size() ; i++ ) { printf("image[%s%d] = %s\n",i>9?"":" ",i,gFiles[i].c_str()); } } for ( int i = 1 ; !result && i < argc ; i++ ) { const char* arg = argv[i++]; const char* value = argv[i ]; int ivalue = ::atoi(value?value:"0"); int key = ::find(arg,keywords,kwMAX); int needv = key < kwMAX && key > (-kwNOVALUE); if (!needv ) i--; if ( needv && !value ) key = kwNEEDVALUE; switch ( key ) { case kwHELP : if ( a ) { ::help(program,keywords,kwMAX) ; } break; case kwVERSION : if ( a ) { ::version(program) ; } break; case kwADJUST : if ( a ) { Position::adjust_=ivalue ; } break; case kwVERBOSE : options.verbose = true ; break; case kwNEEDVALUE: fprintf(stderr,"error: %s requires a value\n",arg); result = resultSyntaxError ; break ; default : { int type = getFileType(arg) ; if ( !a && options.verbose ) printf("%s %s",arg,types[type]) ; if ( type == typeImage ) { time_t t = readImageTime(arg) ; if ( t ) printf(" %ld %s",(long int)t,asctime(localtime(&t))); char path[_MAX_PATH]; realpath(arg,path); gFiles.push_back(path); } if ( type == typeUnknown ) { fprintf(stderr,"error: illegal syntax %s\n",arg) ; result = resultSyntaxError ; break ; } } } } } return result ; }