GeotagDbase.sml

  Download

More scripts: GPS

Syntax Highlighing:

comments, key words, predefined symbols, class members & methods, functions & classes
            
### 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$='<?xml version="1.0"?>
<root>
	<dialog id="gps" Title="Geotag Database Records" VertResize="Relative" Buttons="" OnOpen="OnDlgOpen()">
		<groupbox Name=" Select Input " ExtraBorder="4" VertResize="Fixed">
			<pane Orientation="horizontal" HorizResize="Fixed">
				<pushbutton Name="Source Database..." OnPressed="GetSourceDatabase()"/>
				<edittext id="dbText" HorizResize="Fixed" Width="48" ReadOnly="true"/>
			</pane>
			<pane Orientation="horizontal" HorizResize="Fixed">
				<pushbutton id="tblBtn" Name="Source Table..." Enabled="false" OnPressed="GetSourceTable()"/>
				<edittext id="tblText" HorizResize="Fixed" Width="30" ReadOnly="true"/>
				<pushbutton id="openSrcTblView" Name="Open View" Enabled="false" OnPressed="OpenSourceTblView()"/>
			</pane>
			<pane Orientation="horizontal" HorizResize="Fixed">
				<pushbutton id="dateBtn" Name="Date Field..." Enabled="false" OnPressed="GetDateField()"/>
				<label>(MM/DD/YYYY)</label>
				<edittext id="dateText" HorizResize="Fixed" Width="30" ReadOnly="true"/>
			</pane>
			<pane Orientation="horizontal" HorizResize="Fixed">
				<pushbutton id="timeBtn" Name="Time Field..." Enabled="false" OnPressed="GetTimeField()"/>
				<label>(Hour:Min:Sec)</label>
				<edittext id="timeText" HorizResize="Fixed" Width="30" ReadOnly="true"/>
			</pane>
			<pane Orientation="horizontal" HorizResize="Fixed">
				<pushbutton id="logBtn" Name="GPS Log..." Enabled="false" OnPressed="GetGPSlog()"/>
				<edittext id="logText" HorizResize="Fixed" Width="50" ReadOnly="true"/>
			</pane>
			<pane Orientation="horizontal" HorizResize="Fixed">
				<label id="tblLabel" Enabled="false">New Table Name</label>
				<edittext id="newTblText" HorizResize="Fixed" Width="30" Enabled="false" ReadOnly="false" OnChanged="SetOutputTableName()"/>
			</pane>
		</groupbox>
		<groupbox Name=" Options " ExtraBorder="4" VertResize="Fixed">
			<pane Orientation="horizontal" HorizResize="Fixed">
				<label>Adjust log time to match table time  +/-UTC  </label>
				<editnumber id="adjHour" Width="3" Default="0" Precision="0" MinVal="-24" MaxVal="24"/>
				<label>:</label>
				<editnumber id="adjMin" Width="2" Default="0" Precision="0" MinVal="0" MaxVal="59"/>
				<label>:</label>
				<editnumber id="adjSec" Width="2" Default="0" Precision="0" MinVal="0" MaxVal="59"/>
				<label>H:M:S</label>
			</pane>
			<pane Orientation="horizontal" HorizResize="Fixed">
				<label>Set maximum difference in time for computing coordinates: </label>
				<editnumber id="maxDiff" Width="3" Precision="0" Default="60" MinVal="0" MaxVal="120"/>
				<label> seconds</label>
			</pane>
			<pane Orientation="horizontal" HorizResize="Fixed">
				<radiogroup id="method">
					<item Value="Interpolate" Name="Interpolate Coordinates"/>
					<item Value="Closest" Name="Use Closest Coordinates"/>
				</radiogroup>
			</pane>
		</groupbox>
		<pane Orientation="horizontal" HorizAlign="Right">
			<edittext id="statusText" ReadOnly="true" Width="45"/>
			<pushbutton id="runBtn" Enabled="false" Name=" Run " HorizResize="Fixed" OnPressed="Run()"/>
			<pushbutton id="testBtn" Enabled="false" Name= " Test " HorizResize="Fixed" OnPressed="Test()"/>
			<pushbutton id="exitBtn" Name=" Exit " HorizResize="Fixed" OnPressed="ExitScript()"/>
		</pane>
	</dialog>
</root>';
### 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();
	}