### GeotagDbase.sml ### Requires version 2006:73 of the TNT products ### Randy Smith ### MicroImages, Inc. ### 19 January 2007 ### Sample script to demonstrate geotagging a database table using a single GPS log file. ### Source table must contain a string field with date in form "month/day/year" ### and a string field with time in form "hour:min:sec". User-defined functions ### getDate() and getTime() can be modified to accommodate other date or time formats. ### Coordinate fields "Latitude", "Longitude", and "Elevation" are added to a copy of ### the input database table that is written to the same database object as the source table. ############## Global variable declarations #################### class DATABASE dbSource; # source database object class DBTABLEINFO tblSource, tblDest; # source and destination tables class GPSDBASE gpsdbase; # class for determining location based on Date/Time from # any source and one or more GPS track logs. string srctablename$; # name of selected source database table string srcdatefld$, srctimefld$; # names of selected date and time fields in the source table string desttablename$; # name designated for the new database table string gpsfilename$; # name of the selected GPS log file class DBEDITOR dbSourceView, dbDestView; numeric dbOpen; # flag to indicate if source database has been opened numeric destTblOpen, srcTblOpen; # flags to indicate if tabular view of table has been opened # variables related to the script dialog string xml$; # string containing the dialog specification in XML class XMLDOC dlgdoc; # class instance for the XML document specifying the dialog class XMLNODE gpsdlgnode; # class instance for the dialog's node in the parsed XML structure numeric err; # error value checked at stages of processing the dialog specification class GUI_DLG gpsdialog; # internal class instance for the script dialog # handles for the various controls in the script dialog class GUI_CTRL_LABEL tblLabel; class GUI_CTRL_PUSHBUTTON openSrcBtn, tblBtn, dateBtn, timeBtn, logBtn, newTblBtn; class GUI_CTRL_PUSHBUTTON runBtn, testBtn, exitBtn; class GUI_CTRL_EDIT_STRING dbText, tblText, dateText, timeText, logText, newTblText, statusText; ################################################################## ############### Procedures and Functions ######################### ################################################################## ######################################################## # Function to get date string from a field in the source database # and parse it to get year, month, and day. # Returns the Julian date (numeric value). # # Modify this function to fit the date format in your source table. func getDate(numeric recnum, class DBTABLEINFO tblSource, string srcdatefld$) { local class DATETIME dateDT; # structure to store and convert date/time information local string date$; local numeric year, month, day; # get string from designated date field in source table date$ = TableReadFieldStr(tblSource, srcdatefld$, recnum); # parse date string to get year, month, and day year = StrToNum( GetToken(date$, "/", 3) ); month = StrToNum( GetToken(date$, "/", 1) ); day = StrToNum( GetToken(date$, "/", 2) ); # set date in DATETIME class and return value dateDT.SetDate(year, month, day); return dateDT.GetDateJulian(); } # end of func getDate ########################################################## # Function to get time string from a field in the source database # and parse it to get hour, min, and sec. # Returns the time as seconds since midnight (numeric valus). # # Modify this function to fit the time format in your source table. func getTime(numeric recnum, var class DBTABLEINFO tblSource, string srctimefld$) { local class DATETIME timeDT; # structure to store and convert date/time information local string time$; local numeric hour, min, tsec; # get string from designated time field in source table time$ = TableReadFieldStr(tblSource, srctimefld$, recnum); # parse time string to get hour, min, and seconds hour = StrToNum( GetToken(time$, ":", 1) ); min = StrToNum( GetToken(time$, ":", 2) ); tsec = StrToNum( GetToken(time$, ":", 3) ); # set time in DATETIME class to be returned timeDT.SetTime(hour, min, tsec); return timeDT.GetTimeSecondsSinceMidnight(1); } # end of func getTime ################################################################## # Procedure called when exiting. Called by Exit button on dialog. proc ExitScript() { if (srcTblOpen) then DBEditorCloseTable(dbSourceView, srctablename$); if (destTblOpen) then DBEditorCloseTable(dbDestView, desttablename$); if (dbOpen) then CloseDatabase(dbSource); print("Closing..."); gpsdialog.Close(0); } ############################################################# # Procedure called when the dialog opens; sets status prompt. proc OnDlgOpen() { statusText = gpsdialog.GetCtrlByID("statusText"); statusText.SetValueStr("Select source database."); } ################################################################### # Procedure to get source database object. # Called by Source Database button on dialog proc GetSourceDatabase () { local class RVC_OBJITEM dbItem; # RVC item for source database object local class FILEPATH dbFilepath; # filepath of the source database object local class RVC_DESCRIPTOR dbDescript; # RVC descriptor of source database (works with long object names) local string srcfilename$; # name of the Project File containing the source database object local string srcobjname$; # name of source database object local numeric err; ##################################################################### statusText = gpsdialog.GetCtrlByID("statusText"); ############### delete then when OnOpen() is fixed. # open pop-up dialog to select the source database object err = DlgGetObject("Choose standalone database object:", "Database", dbItem, "ExistingOnly"); if (err < 0) # if not database object selected { statusText.SetValueStr("No source database selected; please select one."); } else # database object selected { dbFilepath = dbItem.GetFilePath(); # get filepath from database's RVC_OBJITEM srcfilename$ = dbFilepath; dbDescript = dbItem.GetDescriptor(); # get RVC descriptor of the database object srcobjname$ = dbDescript.GetFullName(); # get name of source database object (works with long names) # get handle for text control used to show database object name and write name to it dbText = gpsdialog.GetCtrlByID("dbText"); dbText.SetValueStr(sprintf("%s\%s\n", srcfilename$, srcobjname$) ); dbSource = OpenDatabase(srcfilename$, srcobjname$); # open the source database dbOpen = 1; # set flag to indicate database has been opened # get handle for next pushbutton control and enable it tblBtn = gpsdialog.GetCtrlByID("tblBtn"); tblBtn.SetEnabled(1); # update status prompt statusText.SetValueStr("Select source table."); } } # end proc GetSourceDatabase ################################################## # Procedure to get source table from open database # Called by Source Table button on dialog proc GetSourceTable () { local numeric err; # open popup dialog to select the source table from the database err = PopupSelectTable(dbSource, srctablename$); if (err < 1) # no table was selected { statusText.SetValueStr("No table selected; please select a source table."); } else # table was selected { # get handle for text control used to show source table name and write name to it tblText = gpsdialog.GetCtrlByID("tblText"); tblText.SetValueStr(srctablename$); tblSource = DatabaseGetTableInfo(dbSource, srctablename$); # get DBTABLEINFO from source table # get handle for the View Table pushbutton control and enable it openSrcBtn = gpsdialog.GetCtrlByID("openSrcTblView"); openSrcBtn.SetEnabled(1); # get handle for next pushbutton control and enable it dateBtn = gpsdialog.GetCtrlByID("dateBtn"); dateBtn.SetEnabled(1); # update status prompt statusText.SetValueStr("Select field containing date string."); } } # end func GetSourceTable ####################################################### # Procedure to open a tabular view of the source table # Called by Open View button on dialog proc OpenSourceTblView () { dbSourceView = DBEditorCreate(dbSource); srcTblOpen = 1; DBEditorOpenTabularView(dbSourceView, srctablename$); } #################################################### # Procedure to get Date field from source table # Called by Date Field button on dialog proc GetDateField () { numeric err; local class DBFIELDINFO dateFld; # open popup dialog to select field in source table err = PopupSelectTableField(dbSource, srctablename$, srcdatefld$); if (err < 1) then # no field was selected statusText.SetValueStr("No date field selected."); else # field was selected { dateFld = FieldGetInfoByName(tblSource, srcdatefld$); # get info for selected field to check type if (dateFld.Type <> "string") then # if not a string field statusText.SetValueStr("Selected field must be type string."); else # selected field is a string field { # get handle for text control used to show field name and write name to it dateText = gpsdialog.GetCtrlByID("dateText"); dateText.SetValueStr(srcdatefld$); # get handle for next pushbutton control and enable it timeBtn = gpsdialog.GetCtrlByID("timeBtn"); timeBtn.SetEnabled(1); # update status prompt statusText.SetValueStr("Select field containing time string."); } } } # end proc GetDateField #################################################### # Procedure to get Time field from source table # Called by Time Field button on dialog proc GetTimeField () { numeric err; local class DBFIELDINFO timeFld; # open popup dialog to select field in source table err = PopupSelectTableField(dbSource, srctablename$, srctimefld$); if (err < 1) then # no field was selected statusText.SetValueStr("No time field selected."); else # field was selected timeFld = FieldGetInfoByName(tblSource, srctimefld$); # get info for selected field to check type if (timeFld.Type <> "string") # if not a string field statusText.SetValueStr("Selected field must be type string."); else # selected field is a string field { # get handle for text control used to show field name and write name to it timeText = gpsdialog.GetCtrlByID("timeText"); timeText.SetValueStr(srctimefld$); # get handle for next pushbutton control and enable it logBtn =gpsdialog.GetCtrlByID("logBtn"); logBtn.SetEnabled(1); # update status prompt statusText.SetValueStr("Select GPS log file."); } } # end proc GetTimeField ####################################################### # Procedure to get GPS log # Called by GPS Log button on dialog proc GetGPSlog () { # open popup dialog to choose GPS log file gpsfilename$ = GetInputFileName("", "Select GPS log file in NMEA or MicroImages format:", ""); if (gpsfilename$ == "") then # no log file selected statusText.SetValueStr("No GPS log file selected."); else # a log file was selected { if (gpsdbase.ReadLog(gpsfilename$) < 1) then # log file not read statusText.SetValueStr("Log file must be text in NMEA or MicroImages format."); else # log file is successfully read { # get handle for text control used to show log file name and write name to it logText = gpsdialog.GetCtrlByID("logText"); logText.SetValueStr(gpsfilename$); # get handles for label and text control for new table name and enable them tblLabel = gpsdialog.GetCtrlByID("tblLabel"); tblLabel.SetEnabled(1); newTblText = gpsdialog.GetCtrlByID("newTblText"); newTblText.SetEnabled(1); # update status prompt statusText.SetValueStr("Enter name for output table and press [Tab] or [Enter]."); } } } # end proc GetGPSlog ####################################################### # Procedure to set name for new output table. # Called when the New Table Name editstring control is activated by Return/Enter key. proc SetOutputTableName () { # get handles for controls to be enabled if non-empty string was entered runBtn = gpsdialog.GetCtrlByID("runBtn"); testBtn = gpsdialog.GetCtrlByID("testBtn"); # get handle for the text control and get the string from it to be checked and used newTblText = gpsdialog.GetCtrlByID("newTblText"); desttablename$ = newTblText.GetValueStr(); if (desttablename$ <> "") # table name string not empty { runBtn.SetEnabled(1); # enable Run button testBtn.SetEnabled(1); # enable Test button # update status prompt statusText.SetValueStr("Set options and press [Test] or [Run]."); } else # string is empty statusText.SetValueStr("Please enter name for output table."); } # end proc SetOutputTableName ####################################################### # Procedure to get processing options from the dialog. # Called by Run() and Test() procedures proc GetOptions (var numeric gpsTimeOffset, var numeric maxTimeDiff, var string method$) { # get gps offset time from dialog local numeric adjHour = gpsdialog.GetCtrlByID("adjHour").GetValueNum(); local numeric adjMin = gpsdialog.GetCtrlByID("adjMin").GetValueNum(); local numeric adjSec = gpsdialog.GetCtrlByID("adjSec").GetValueNum(); if (adjHour < 0) # check if hour is negative value and change sign for min and sec { adjMin = adjMin * -1; adjSec = adjSec * -1; } # convert gps offset time in H:M:S to seconds for GPSDBASE class local numeric gpsTimeOffset = adjHour * 3600 + adjMin * 60 + adjSec; # get maximum time difference; used as parameter to GPSDBASE.Compute() local numeric maxTimeDiff = gpsdialog.GetCtrlByID("maxDiff").GetValueNum(); # get method (Interpolate or Closest); used as parameter to GPSDBASE.Compute() local string method$ = gpsdialog.GetCtrlByID("method").GetValueStr(); } # end proc GetOptions ######################################################## # Procedure to check how many records get geotagged with current settings. # Called by Test button on dialog. proc Test () { # get GPS log processing options from dialog using user-defined function local numeric gpsTimeOffset, maxTimeDiff; local string method$; GetOptions(gpsTimeOffset, maxTimeDiff, method$); gpsdbase.SetOffset(gpsTimeOffset); # set GPS time offset ### loop through records in table to compute and add coordinates local numeric j; # loop counter local numeric numTagged = 0; # count of number of records succesfully geotagged local class GPSDATA gpsdata; # current GPS location to be passed to GBSDBASE local class DATETIME datetime; # date time info to be passed to GPSDBASE local class POINT3D position; # point location returned by GPSDATA for j = 1 to tblSource.NumRecords { # set date and time in DATETIME class to be passed to GPSDBASE # by calling user-defined functions that get them from the source database # and return Julian date and time in seconds since midnight datetime.SetDateJulian( getDate(j, tblSource, srcdatefld$) ); datetime.SetTimeSecondsSinceMidnight( getTime(j, tblSource, srctimefld$) ); # compute coordinates; method returns 0 if time match found and -1 if not if (gpsdbase.Compute(datetime, gpsdata, method$, maxTimeDiff) == 0) # time match found { position = gpsdata.position; # get position from GPSDATA as 3D point ++ numTagged; # increment success counter # printf("latitude= %.6f, longitude = %.6f, elevation = %.2f \n", position.y, position.x, position.z); } } # end for # update status prompt statusText.SetValueStr( sprintf("Current options would geotag %d of %d records.", numTagged, tblSource.NumRecords) ); } # end proc Test ####################################################### # Procedure called when Run button is pressed. # Copies source table to destination, adds position fields, # and loops through records to compute position and write # values to the new fields. proc Run () { # get GPS log processing options from dialog using user-defined function local numeric gpsTimeOffset, maxTimeDiff; local string method$; GetOptions(gpsTimeOffset, maxTimeDiff, method$); gpsdbase.SetOffset(gpsTimeOffset); # set GPS time offset ### copy source table and rename with name previously chosen by user TableCopy(dbSource, tblSource, dbSource); # copy table; name gets incremented by adding "1" local string ctablename$ = srctablename$ + "1"; # make string for name of copied table tblDest = DatabaseGetTableInfo(dbSource, ctablename$); # get table info for copied table tblDest.Name = desttablename$; # rename table ### add fields for Latitude, Longitude, and Elevation to the copied table local class DBFIELDINFO elevInfo; TableAddFieldFloat(tblDest, "Latitude", 10, 6); TableAddFieldFloat(tblDest, "Longitude", 10, 6); elevInfo = TableAddFieldFloat(tblDest, "Elevation", 10, 6); elevInfo.UnitType = "length"; elevInfo.Units = "meters"; ### loop through records in destination table to compute and add coordinates local numeric j; # loop counter local numeric numTagged = 0; # count of number of records succesfully geotagged local class GPSDATA gpsdata; # current GPS location to be passed to GPSDBASE local class DATETIME datetime; # date time info to be passed to GPSDBASE local class POINT3D position; # point location returned by GPSDATA for j = 1 to tblDest.NumRecords { # set date and time in DATETIME class to be passed to GPSDBASE # by calling user-defined functions that get them from the destination database # and return Julian date and time in seconds since midnight datetime.SetDateJulian( getDate(j, tblDest, srcdatefld$) ); datetime.SetTimeSecondsSinceMidnight( getTime(j, tblDest, srctimefld$) ); # compute coordinates; method returns 0 if time match found and -1 if not if (gpsdbase.Compute(datetime, gpsdata, method$, maxTimeDiff) == 0) # if time match found { position = gpsdata.position; # get position for record from GPSDATA as 3D point TableWriteField(tblDest, j, "Latitude", position.y); # write coordinates to table TableWriteField(tblDest, j, "Longitude", position.x); TableWriteField(tblDest, j, "Elevation", position.z); ++ numTagged; # increment success counter # printf("latitude= %.6f, longitude = %.6f, elevation = %.2f \n", position.y, position.x, position.z); } } # end for # update status prompt statusText.SetValueStr( sprintf("Successfully geotagged %d of %d records.", numTagged, tblDest.NumRecords) ); dbDestView = DBEditorCreate(dbSource); DBEditorOpenTabularView(dbDestView, desttablename$); # open tabular view of result table destTblOpen = 1; } # end proc Run ################################################################ ################### Main Program ############################### ################################################################ clear(); xml$=' '; ### parse XML text for the dialog into memory; ### return an error code (number < 0 ) if there are syntax errorsi err = dlgdoc.Parse(xml$); if ( err < 0 ) { PopupError( err ); # Popup an error dialog. "Details" button shows syntax errors. Exit(); } # get the dialog element from the parsed XML document and # show error message if the dialog element can't be found gpsdlgnode = dlgdoc.GetElementByID("gps"); if ( gpsdlgnode == 0 ) { PopupMessage("Could not find dialog node in XML document"); Exit(); } # Set the XML dialog element as the source for the GUI_DLG class instance # we are using for the dialog window. gpsdialog.SetXMLNode(gpsdlgnode); err = gpsdialog.DoModal(); if ( err == -1 ) { # exit script if Cancel button on dialog is pressed Exit(); }