home products news downloads documentation support gallery online maps resellers search
TNTmips Downloads Menu

HOME

CONTACT US

CURRENT RELEASE
  TNT 2013

DEVELOPMENT VERSION
  TNT 2014

TNTmips Pro
PRIOR RELEASES
  TNT 2012

FREE SOFTWARE
  TNTmips Free
  TNTatlas
  TNTsdk

MORE DOWNLOADS
  HASP Key Driver
  Screen Recorder
  TNT Language Kits
  Sample Geodata
  TNT Scripts

DOCUMENTATION
  TNTmips Tutorials
  Tutorial Datasets
  Technical Guides
  Scripts
  Quick Guides

MORE INFO
  Download FAQs
  FTP
  Download Managers
  Find Reseller

SITE MAP


GPSphoto2.sml


#### GPSphoto2.sml

### Requires version 2006:73 of the TNT products

### Brett Colombe
### MicroImages, Inc.
###	Software Engineer
###	19 January 2007

# 2.0 - Major changes to use new GPSDBASE class

#######################################################
# Script inputs: GPS log file(s) and JPEG images with EXIF headers.
#
# This Script gets from the EXIF header the date and time each picture was taken
# and compares them with the GPS log to assign geographic coordinates for the image.
# User chooses whether to interpolate coordinates based on image time or use closest GPS coordinates.
# Each image location is added as a point to the user-selected vector object.
#
# As images are added, the EXIF fields for Date and Time are read and displayed in the listbox.
# As logs are added, each log file is parsed and the Date, Time, and Lat/Long/Elev are displayed in the listbox.
# User sets the constraints options and selects his output vector
# Coordinates for the images are calculated by:
# Search through the list of coordinates from the gps log files for the two closest log points
# Using the time of the image and these two coordinates, new coordinates are then interpolated for the image
# Constraints are checked, then Date, Time, Lat, Lon, Elev and Path of image are added to table, with records sorted by time
# Table is stored in Point Database for the vector, with each record attached to a vector point
# Vector may be displayed in a Layout group for hyperlinking the images by selecting File by Attribute and choosing the image Path in the table
##################################
# Images Tab:
# User has option to add a directory of images or single images, images are then displayed in a listbox
# Save As - allows user to select the vector outputed by script
# Compute Image Coordinates - calculates the coordinates of the images and adds these to the table
# Display GPS Table - to display the table
# Change Directory of Images - if user moves images to a different directory, allows user to select this other directory, 
#			searches for image names in the new directory matching those in the table, then these images' paths are changed to the new directory
##################################
# GPS Log Tab:
# User has option to add a directory of log files or single log files
# Log file path/name added to first list box
# Total list of coordinates added to second list box
# Set the offset time for camera if camera time is different than gps log time
# All log files must be MicroImages standard log format or NMEA standard, see script below for details
##################################
# Options Tab:
# Choose to interpolate new coordinates or simply select closest log point's coordinates
# Set the maximum allowed difference in time between image and closest log point


########## Global Class Declarations ##############################
class GPSDBASE gpsdbase; ## NEW CLASS

class XMLDOC dlgdoc, logdoc, latlondoc, transferdoc, exifdoc;			# class instance for the XML document
class XMLNODE gpsdlgnode, lognode, latlonnode, transfernode, exifnode;	# class instance for the node in the XML

class GUI_DLG gpsdialog, logdialog, latlondialog, transferdialog, exifdialog;	# class instance for the GUI dialog

class DATABASE dbase; # class instance for database
class DBTABLEINFO table; # class instance for GPS table

class STRINGLIST imageListName; # string lists containing listbox image name
class STRINGLIST imageListNameAttached; # string lists containing image name for found coordinates
class STRINGLIST coordStringList;

class STRING imageStringList[];
class DATETIME imageListTime[];
class GPSDATA imageListData[];

numeric Assigned[];
#####
class GRE_GROUP gp;                  # spatial group for display.
class GRE_VIEW view;                 # view that displays the group.
class XmForm pcwin;           # parent form for dialog window.
class PointTool myPt;  # class for tool used to return a 3D point.

class RASTER rasterIn;

class GRE_LAYER_RASTER rasterInLayer;
class POINT2D ptrastmap;       # point location in raster map coordinates.
class Point2D ptGeog;				# point location in WGS84 / Geographic coordinates.

class SR_COORDREFSYS coordrefsysRast;
class SR_COORDREFSYS coordrefsysGeog;
class TransParm transViewToRastMap;
class TransParm transRastMapToGeog;

class MAPPROJ rastmapproj;            # Coordinate system / projection parameters.
#####

class SR_COORDREFSYS crs; # coordinate reference system class
crs.Assign("Geographic2D_WGS84_Deg"); # set crs for vector

class EXIF exifhandle; # declare EXIF class handle for getting/writing exif values

string xml$;			# string containing the XML text with the dialog specification
numeric err;			# value returned by the class method that reads and parses
							         # the XML text 
numeric ret;			# value returned by the class method that opens dialog
string filename$, obj$, desc$; # database and project file name
string IMAGEtablename$="Images"; # image table name
string tabledesc$="Table created by GPS.sml script to hold GPS coordinates and attached images"; # gps table description
vector GPSVector; # gps vector object 



####################### Procedures ###############################

###############################################
########## Procedures for GPS Logs ############
###############################################

proc DisplayEXIF() { # Procedure to create dialog to display EXIF information about a photo
	local numeric errexif;
	local string xmlexif$;
 
	### Create string variable with XML specification of dialog
	xmlexif$ = '
	
		
			
		
	';

	### parse XML text for the dialog into memory; 
	### return an error code (number < 0 ) if there are syntax errorsi
	errexif = exifdoc.Parse(xmlexif$);

	if ( errexif < 0 ) {
		PopupError( errexif ); 	# 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
	exifnode = exifdoc.GetElementByID("exifdlg");

	if ( exifnode == 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.
	exifdialog.SetXMLNode(exifnode);
	ret = exifdialog.DoModal();
	} # end DisplayEXIF

#############################################
proc PopulateEXIF() { # Get detailed information about EXIF tags and add to dialog

	local numeric index; # selected item index
	class GUI_CTRL_LISTBOX list, exifbox;
	class STRINGLIST keyStrings;

	exifbox = exifdialog.GetCtrlByID("exifbox");
	exifbox.DeleteAllItems();
	list = gpsdialog.GetCtrlByID("imagebox"); # get control for list box
	index = list.GetSelectedItemIndex(); # get selected item index
	local string image$ = imageListName.GetString(index); # grab input name

	if(image$=="")
		{
		PopupMessage("Please Select an Image.");
		}
	else 
		{
		local string output$="";
		output$+=sprintf("EXIF Header Information for image: %s\n -----------------------------\n");

		# GetEXIFTags takes filename of image as input
		# returns hash with key names and values
		exifhandle.Open(image$);

		# Get key list from hash, returns as string list
		keyStrings = exifhandle.GetKeyList();

		local numeric value,i;
		local string value$;

		# print all keys and values to text file
		for i=0 to keyStrings.GetNumItems()-1 {
			output$= sprintf("Key: %i\t%s\nValue: %s\n", i, keyStrings[i], exifhandle.GetDatumStr(keyStrings[i]));

			# Exif.Photo.Flash
			# bit0: 0 flash didn't fire, 1 flash fired
			# bit12: 00 no strobe return detection function, 01 reserved, 10 strobe return light not detected, 11 strobe return ligth deteced
			# bit34: 00 unknown, 01 Compulsory flash firing, 10 Compulsory flash suppression, 11 auto mode
			# bit5: 0 flash function present, 1 no flash function
			# bit6: 0 no red eye reduction mode or unknown, 1 red eye reduction supported
			if (keyStrings[i]=="Exif.Photo.Flash")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.Flash"));

				if (value>=64) # red eye mode
					{
					value=value-64;
					output$+= "\tRed Eye Mode - Red eye reduction supported";
					}
				else
					output$+= "\tRed Eye Mode - No red eye reduction mode or unknown";

				if(value>=32) # flash function
					{
					value=value-32;
					output$+= "\tFlash Function - No flash function";
					}
				else
					output$+= "\tFlash Function - Flash function present";

				if (value>=16) # flash mode
					{
					value=value-16;
					if (value>=8)
						{
						value=value-8;
						output$+= "\tFlash Mode - Auto mode\n";
						}
					else
						output$+= "\tFlash Mode - Compulsory flash suprression\n";
					}
				else
					{
					if (value>=8)
						{
						value=value-8;
						output$+="\tFlash Mode - Compulsory flash firing";
						}
					else
						output$+="\tFlash Mode - Unknown mode";
					}

				if (value>=4) # flash return
					{
					value=value-4;
					if (value>=2)
						{
						value=value-2;
						output$+=  "\t Flash Return - Strobe return light detected";
						}
					else
						output$+= "\tFlash Return - Strobe return light not detected";
					}
				else
					{
					if (value>=2)
						{
						value=value-2;
						output$+= "\tFlash Return - Reserved";
						}
					else
						output$+= "\tFlash Return - No strobe return detection function";
					}

				if (value>=1) # flash fired
					output$+= "\tFlash Fired - Flash did fire\n";
				else
					output$+= "\tFlash Fired - Flash did not fire\n";
				} # end Exif.Photo.Flash

			# Exif.Photo.ExifVersion
			if(keyStrings[i]=="Exif.Photo.ExifVersion")
				{
				value$=exifhandle.GetDatumStr("Exif.Photo.ExifVersion");
				output$+= sprintf("\tExif Version: %i%i.%i%i\n", StrToNum(GetToken(value$, " ",1))-48, StrToNum(GetToken(value$, " ",2))-48, StrToNum(GetToken(value$, " ",3))-48, StrToNum(GetToken(value$, " ",4))-48);
				}

			# Exif.Photo.FlashpixVersion
			if(keyStrings[i]=="Exif.Photo.FlashpixVersion")
				{
				value$=exifhandle.GetDatumStr("Exif.Photo.FlashpixVersion");
				output$+= sprintf("\tFlashpix Version: %i%i.%i%i\n", StrToNum(GetToken(value$, " ",1))-48, StrToNum(GetToken(value$, " ",2))-48, StrToNum(GetToken(value$, " ",3))-48, StrToNum(GetToken(value$, " ",4))-48);
				}

			# Exif.Iop.InteroperabilityVersion
			if(keyStrings[i]=="Exif.Iop.InteroperabilityVersion")
				{
				value$=exifhandle.GetDatumStr("Exif.Iop.InteroperabilityVersion");
				output$+= sprintf("\tInteroperability Version: %i%i.%i%i\n", StrToNum(GetToken(value$, " ",1))-48, StrToNum(GetToken(value$, " ",2))-48, StrToNum(GetToken(value$, " ",3))-48, StrToNum(GetToken(value$, " ",4))-48);
				}

			# Exif.Image.Orientation
			if(keyStrings[i]=="Exif.Image.Orientation" || keyStrings[i]=="Exif.Thumbnail.Orientation")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Image.Orientation"));
				switch(value)
					{
			      case 1: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","top","left-hand side"); break;
			      case 2: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","top","right-hand side"); break;
   			   case 3: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","bottom","right-hand side"); break;
  				   case 4: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","bottom","left-hand side"); break;
			      case 5: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","left-hand side","top"); break;
  				   case 6: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","right-hand side","top"); break;
					case 7: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","right-hand side","bottom"); break;
					case 8: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","left-hand side","bottom"); break;
					}
				}

			# Exif.Image.ResolutionUnit
			if(keyStrings[i]=="Exif.Image.ResolutionUnit" || keyStrings[i]=="Exif.Thumbnail.ResolutionUnit" || keyStrings[i]=="Exif.Photo.FocalPlaneResolutionUnit")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Image.ResolutionUnit"));
				switch(value)
					{
					case 2: output$+= sprintf("\tUnit for Resolution: Inches\n"); break;
					case 3: output$+= sprintf("\tUnit for Resolution: Centimeters\n"); break;
					}
				}

			# Exif.Image.YCbCrPositioning
			if(keyStrings[i]=="Exif.Image.YCbCrPositioning" || keyStrings[i]=="Exif.Thumbnail.YCbCrPositioning")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Image.YCbCrPositioning"));
				switch(value)
					{
					case 1: output$+= sprintf("\tPosition of Chrominance components in relation to Luminance components: centered.\n"); break;
					case 2: output$+= sprintf("\tPosition of Chrominance components in relation to Luminance components: co-sited.\n"); break;
					}
				}

			# Exif.Image.XResolution / YResolution
			if(keyStrings[i]=="Exif.Image.XResolution" || keyStrings[i]=="Exif.Image.YResolution" || keyStrings[i]=="Exif.Photo.FocalPlaneXResolution" || keyStrings[i]=="Exif.Photo.FocalPlaneYResolution" || keyStrings[i]=="Exif.Thumbnail.XResolution" || keyStrings[i]=="Exif.Thumbnail.YResolution")
				output$+= sprintf("\tNumber of pixles per resolution unit.\n");

			# Exif.Iop.InteroperabilityIndex
			if(keyStrings[i]=="Exif.Iop.InteroperabilityIndex")
				{
				value$=exifhandle.GetDatumStr("Exif.Iop.InteroperabilityIndex");
				switch(value$)
					{
					case "R98": output$+= sprintf("\tIndicates a file conforming to R98 file specification of Recommended Exif Interoperability Rules (ExifR98) or to DCF basic file stipulated by Design Rule for Camera File System.\n"); break;
					case "THM": output$+= sprintf("\tIndicates a file conforming to DCF thumbnail file stipulated by Design rule for Camera File System.\n"); break;
					}
				}

			# Exif.Photo.ColorSpace
			if (keyStrings[i]=="Exif.Photo.ColorSpace")
				{
				value$=exifhandle.GetDatumStr("Exif.Photo.ColorSpace");
				if (value$=="1")
					output$+= sprintf("\tsRGB (=1) is used to define the color space based on the PC monitor conditions and environment.\n");
				else
					output$+= sprintf("\tUncalibrated.\n"); 
				}

			# Exif.Photo.ExposureMode
			if(keyStrings[i]=="Exif.Photo.ExposureMode")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.ExposureMode"));
				switch(value)
					{
					case 0: output$+= sprintf("\tAuto exposure.\n"); break;
					case 1: output$+= sprintf("\tManual exposure.\n"); break;
					case 2: output$+= sprintf("\tAuto bracket.\n"); break;
					}
				}

			# Exif.Photo.FileSource
			if(keyStrings[i]=="Exif.Photo.FileSource")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.FileSource"));
				if(value==3)
					output$+= sprintf("\tImage recorded on a DSC.\n");
				}

			# Exif.Photo.SceneType
			if(keyStrings[i]=="Exif.Photo.SceneType")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.SceneType"));
				if(value==1)
					output$+= sprintf("\tA directly photographed image.\n");
				}

			# Exif.Photo.ComponentsConfiguration
			if(keyStrings[i]=="Exif.Photo.ComponentsConfiguration")
				{
				value$=exifhandle.GetDatumStr("Exif.Photo.ComponentsConfiguration");
				local string line$="";
				local numeric k;
				for k=1 to 4
					{
					value=StrToNum(GetToken(value$," ",k));
					switch(value)
		 				{
						case 0: line$=line$ + sprintf("%i=%s ",value ,"does not exist"); break;
			 			case 1: line$=line$ + sprintf("%i=%s ",value ,"Y"); break;
						case 2: line$=line$ + sprintf("%i=%s ",value ,"Cb"); break;
						case 3: line$=line$ + sprintf("%i=%s ",value ,"Cr"); break;
		 				case 4: line$=line$ + sprintf("%i=%s ",value ,"R"); break;
						case 5: line$=line$ + sprintf("%i=%s ",value ,"G"); break;
						case 6: line$=line$ + sprintf("%i=%s ",value ,"B"); break;
						}
					}
				output$+= sprintf("\tChannels of each component: %s.\n",line$);
				}

			# Exif.Photo.MeteringMode
			if (keyStrings[i]=="Exif.Photo.MeteringMode")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.MeteringMode"));
				switch(value)
					{
		       	case 0: output$+= sprintf("\tMetering Mode: %s.\n","Unknown"); break;
		       	case 1: output$+= sprintf("\tMetering Mode: %s.\n","Average"); break;
		       	case 2: output$+= sprintf("\tMetering Mode: %s.\n","Center Weighted Average"); break;
		       	case 3: output$+= sprintf("\tMetering Mode: %s.\n","Spot"); break;
		       	case 4: output$+= sprintf("\tMetering Mode: %s.\n","Multi Spot"); break;
		       	case 5: output$+= sprintf("\tMetering Mode: %s.\n","Pattern"); break;
		       	case 6: output$+= sprintf("\tMetering Mode: %s.\n","Partial"); break;
		       	case 255: output$+= sprintf("\tMetering Mode: %s.\n","Other"); break;
		 		 	}
				}

			# Exif.Photo.SceneCaptureType
			if(keyStrings[i]=="Exif.Photo.SceneCaptureType")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.SceneCaptureType"));
				switch(value)
					{
		       	case 0: output$+= sprintf("\tScene Type: %s.\n","Standard"); break;
  			     	case 1: output$+= sprintf("\tScene Type: %s.\n","Landscape"); break;
   		    	case 2: output$+= sprintf("\tScene Type: %s.\n","Portrait"); break;
		       	case 3: output$+= sprintf("\tScene Type: %s.\n","Night Scene"); break;
		 		 	}
				}

			# Exif.Photo.SensingMethod
			if(keyStrings[i]=="Exif.Photo.SensingMethod")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.SensingMethod"));
				switch(value)
					{
		       	case 1: output$+= sprintf("\tImage Sensor Type: %s.\n","Not defined"); break;
		       	case 2: output$+= sprintf("\tImage Sensor Type: %s.\n","One-chip color area sensor"); break;
		       	case 3: output$+= sprintf("\tImage Sensor Type: %s.\n","Two-chip color area sensor"); break;
   		    	case 4: output$+= sprintf("\tImage Sensor Type: %s.\n","Three-chip color area sensor"); break;
   		    	case 5: output$+= sprintf("\tImage Sensor Type: %s.\n","Color sequential area sensor"); break;
		       	case 7: output$+= sprintf("\tImage Sensor Type: %s.\n","Trilinear sensor"); break;
		       	case 8: output$+= sprintf("\tImage Sensor Type: %s.\n","Color sequential linear sensor"); break;
		 		 	}
				}

			# Exif.Photo.WhiteBalance
			if(keyStrings[i]=="Exif.Photo.WhiteBalance")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.WhiteBalance"));
				switch(value)
					{
					case 0: output$+= sprintf("\tWhite Balance: %s\n", "Auto"); break;
					case 1: output$+= sprintf("\tWhite Balance: %s\n", "Manual"); break;
					}
				}

			# Exif.Thumbnail.Compression
			if (keyStrings[i]=="Exif.Thumbnail.Compression")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Thumbnail.Compression"));
				switch(value)
					{
					case 1: output$+= sprintf("\t%s\n", "Uncompressed"); break;
					case 6: output$+= sprintf("\t%s\n", "JPEG compression"); break;
					}
				}

			# Exif.Thumbnail.JPEGInterchangeFormat
			if (keyStrings[i]=="Exif.Thumbnail.JPEGInterchangeFormat")
				output$+= sprintf("\tThe offset to the start byte (SOI) of JPEG compressed thumbnail data.\n");

			# Exif.Photo.ExposureProgram
			if (keyStrings[i]=="Exif.Photo.ExposureProgram")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.ExposureProgram"));
				switch(value)
					{
		       	case 0: output$+= sprintf("\tExposure Program Class: %s.\n","Not defined"); break;
		       	case 1: output$+= sprintf("\tExposure Program Class: %s.\n","Manual"); break;
		       	case 2: output$+= sprintf("\tExposure Program Class: %s.\n","Normal Program"); break;
		       	case 3: output$+= sprintf("\tExposure Program Class: %s.\n","Aperture priority"); break;
		       	case 4: output$+= sprintf("\tExposure Program Class: %s.\n","Shutter priority"); break;
		       	case 5: output$+= sprintf("\tExposure Program Class: %s.\n","Creative program (biased toward depth of field)"); break;
		       	case 6: output$+= sprintf("\tExposure Program Class: %s.\n","Action program (biased toward fast shutter speed"); break;
		       	case 7: output$+= sprintf("\tExposure Program Class: %s.\n","Portrait mode (for closeup photos with the background out of focus)"); break;
		       	case 8: output$+= sprintf("\tExposure Program Class: %s.\n","Landscape mode (for landscape photos with the background in focus"); break;
		 		 	}
				}

			# Exif.Photo.LightSource
			if (keyStrings[i]=="Exif.Photo.LightSource")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.LightSource"));
				switch(value)
					{
		       	case 0: output$+= sprintf("\tLight Source: %s.\n","Unknown"); break;
		       	case 1: output$+= sprintf("\tLight Source: %s.\n","Daylight"); break;
		       	case 2: output$+= sprintf("\tLight Source: %s.\n","Fluorescent"); break;
		       	case 3: output$+= sprintf("\tLight Source: %s.\n","Tungsten (incandescent light)"); break;
 		      	case 4: output$+= sprintf("\tLight Source: %s.\n","Flash"); break;
 		      	case 9: output$+= sprintf("\tLight Source: %s.\n","Fine weather"); break;
 		      	case 10: output$+= sprintf("\tLight Source: %s.\n","Cloudy weather"); break;
 		      	case 11: output$+= sprintf("\tLight Source: %s.\n","Shade"); break;
 		      	case 12: output$+= sprintf("\tLight Source: %s.\n","Daylight fluorescent (D 5700 - 7100K)"); break;
  			     	case 13: output$+= sprintf("\tLight Source: %s.\n","Day white fluorescent (N 4600 - 5400K)"); break;
		       	case 14: output$+= sprintf("\tLight Source: %s.\n","Cool white fluorescent (W 3900 - 4500K)"); break;
		       	case 15: output$+= sprintf("\tLight Source: %s.\n","White fluorescent (WW 3200 - 3700K)"); break;
		       	case 17: output$+= sprintf("\tLight Source: %s.\n","Standard light A"); break;
		       	case 18: output$+= sprintf("\tLight Source: %s.\n","Standard light B"); break;
		       	case 19: output$+= sprintf("\tLight Source: %s.\n","Standard light C"); break;
		       	case 20: output$+= sprintf("\tLight Source: %s.\n","D55"); break;
		  	     	case 21: output$+= sprintf("\tLight Source: %s.\n","D65"); break;
		       	case 22: output$+= sprintf("\tLight Source: %s.\n","D75"); break;
		       	case 23: output$+= sprintf("\tLight Source: %s.\n","D50"); break;
		       	case 24: output$+= sprintf("\tLight Source: %s.\n","ISO studio tungsten"); break;
		       	case 255: output$+= sprintf("\tLight Source: %s.\n","Other light source"); break;
		 		 	}
				}

			# Exif.Thumbnail.PhotometricInterpretation
			if (keyStrings[i]=="Exif.Image.PhotometricInterpretation" || keyStrings[i]=="Exif.Thumbnail.PhotometricInterpretation")
				{
				value=StrToNum(exifhandle.GetDatumStr("Exif.Thumbnail.PhotometricInterpretation"));
				switch(value)
					{
					case 2: output$+= sprintf("\tPixel Composition: %s\n", "RGB"); break;
					case 6: output$+= sprintf("\tPixel Composition: %s\n", "YCbCr"); break;
					}
				}

			#########################################################
			exifbox.AddItem(output$);
			} # end else
		} # end for
	} # end proc PopulateExif


############################
func DecimalTime(numeric hour, numeric min, numeric second) { # function to convert time to decimal value, returns decimal time
	if (hour<0) # check if hour is negative value for camera offset time
		{
		min=min*-1; # set minutes to negative
		second=second*-1; # set seconds to negative
		}
	local numeric time=hour + (min/60) + (second/3600); # convert to decimal value
	return time;
} # end func DecimalTime


############################
# Procedure to get the options set by user and return the values;
# Values are returned to the variables defined as procedure parameters
proc GetOptions(var numeric cameraoffset, var string method$, var numeric timeoffset) { 
	local string offsetTime$ = gpsdialog.GetCtrlValueStr("cameraoffset"); #get camera offset
	# parse and convert to decimal time
	cameraoffset=DecimalTime(StrToNum(GetToken(offsetTime$,":",1)), StrToNum(GetToken(offsetTime$,":",2)), StrToNum(GetToken(offsetTime$,":",3)));
	method$=gpsdialog.GetCtrlValueStr("method"); # get selected method: Interpolate or Closest
	offsetTime$=gpsdialog.GetCtrlValueStr("timeoffset"); # get max allowed difference in time for gps
	# parse and convert to decimal time
	timeoffset=DecimalTime(StrToNum(GetToken(offsetTime$,":",1)), StrToNum(GetToken(offsetTime$,":",2)), StrToNum(GetToken(offsetTime$,":",3)));
	timeoffset = timeoffset *3600;
	cameraoffset = cameraoffset*3600;
} # end proc GetOptions()


############################
proc ComputeCoordinates(numeric imageNum, numeric computedflag, numeric errorreportflag) {
	# same as AttachImages, but doesn't use Table
	# procedure to compute coordinates for images
	class POINT3D leftXYZ, rightXYZ, newXYZ;

	local string report$;
	local numeric i, listcount = 0; 

	local string method$; # options
	local numeric cameraoffset, timeoffset = 0; # options

	local string inputname$; # image time and name
	class DATETIME inputtime;
	class GPSDATA inputdata;

	if(imageNum==-1){
		imageListNameAttached.Clear();
	}

	GetOptions(cameraoffset, method$, timeoffset);
	# timeoffset=max allowed difference in time

	local numeric startcount, endcount;
	if (imageNum == -1) {
		endcount = imageListName.GetNumItems()-1;
		startcount=0;
		}
	else
		{
		endcount = imageNum;
		startcount=imageNum;
		}

	for listcount=startcount to endcount # for all images in image list box
		{

		inputname$ = imageListName.GetString(listcount); # grab input name
		inputtime = imageListTime[inputname$]; # grab input date and time
		inputdata = imageListData[inputname$];

		## check for existing coords
		local string imageName$=inputname$;
		class POINT3D coord;
		class STRINGLIST keys;
		local numeric keyindex = 0;
		local numeric valid=0;

		exifhandle.Open(imageName$);
		keys = exifhandle.GetKeyList();

		for keyindex=0 to keys.GetNumItems()-1{
			if (keys[keyindex]=="Exif.GPSInfo.GPSLongitude")
				valid++;
			if (keys[keyindex]=="Exif.GPSInfo.GPSLongitudeRef")
				valid++;
			if (keys[keyindex]=="Exif.GPSInfo.GPSLatitude")
				valid++;
			if (keys[keyindex]=="Exif.GPSInfo.GPSLatitudeRef")
				valid++;
			if (keys[keyindex]=="Exif.GPSInfo.GPSAltitude")
				valid++;
			if (keys[keyindex]=="Exif.GPSInfo.GPSAltitudeRef")
				valid++;
			}

		if (valid==6 && computedflag==0 && (Assigned[inputname$]!=1 || IsNull(Assigned[inputname$])))
			{
			string xcoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLongitude");
			string xcoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLongitudeRef");

			string ycoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLatitude");
			string ycoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLatitudeRef");

			string zcoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSAltitude");
			string zcoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSAltitudeRef");

			coord.x = (StrToNum(GetToken(GetToken(xcoord$, " ", 1),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 1),"/",2))) 
							+ ((StrToNum(GetToken(GetToken(xcoord$, " ", 2),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 2),"/",2)))
							+ (StrToNum(GetToken(GetToken(xcoord$, " ", 3),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 3),"/",2)))/60)/60;

			if (xcoordref$=="W") then coord.x=coord.x*-1;

			coord.y = (StrToNum(GetToken(GetToken(ycoord$, " ", 1),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 1),"/",2))) 
							+ ((StrToNum(GetToken(GetToken(ycoord$, " ", 2),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 2),"/",2)))
							+ (StrToNum(GetToken(GetToken(ycoord$, " ", 3),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 3),"/",2)))/60)/60;

			if (ycoordref$=="S") then coord.y=coord.y*-1;

			coord.z = StrToNum(GetToken(zcoord$,"/",1))/ StrToNum(GetToken(zcoord$,"/",2));

			if (zcoordref$=="1") then coord.z=coord.z*-1;

			imageListData[imageName$].Position.x = coord.x;
			imageListData[imageName$].Position.y = coord.y;
			imageListData[imageName$].Position.z = coord.z;

			Assigned[imageName$]=1;
			} # end if

		else if ( (Assigned[inputname$]!=1 || IsNull(Assigned[inputname$])) || computedflag==1)
			{
			Assigned[inputname$]=0;
			imageListData[inputname$].Position.x = 0;
			imageListData[inputname$].Position.y = 0;
			imageListData[inputname$].Position.z = 0;

			# compute coordinates using GPSDBASE class method
			class GPSDATA data;
			gpsdbase.Compute(inputtime, data, method$, timeoffset);
			imageListData[inputname$] = data;

			if (!IsNull(data.Position.x)  && !IsNull(data.Position.y)  && !IsNull(data.Position.z))
				{
				imageListNameAttached.AddToEnd(inputname$);
				imageListData[inputname$] = data;
				}

		else if (errorreportflag == 1) # image coordinates not computed
			report$=report$ + sprintf("Failed to compute coordinates for image '%s' \n", inputname$);

		} # end if Assigned 

		else
			{
			imageListNameAttached.AddToEnd(inputname$);
			imageListData[inputname$] = inputdata;
			}
		} # end for listcount

		if (report$ != "") # report$ contains images with no coordinates
			{
			report$=report$ + sprintf("These Images fall outside log range.");
			PopupMessage(report$);
			}

	} # end proc ComputeCoordinates


#################################################

func GetEXIF(string filename$) {
	## Function to read EXIF header from image "filename$"
	## Adds string containing date and time (YYYYMMDD HH:MM:SS) to imageListTime string list
	## returns 0 if it cannot find a header, 1 if successful
	class DATETIME datetime;

	exifhandle.Open(filename$);
	# key "Exif.Image.DateTime" contains date/time value (YYYY:MM:DD HH:MM:SS)
	# parse string and add to EXIFDateTime$ string as YYYYMMDD HH:MM:SS
	string exifDateTime$ = exifhandle.GetDatumStr("Exif.Image.DateTime"); #pass the name of a Key to GetDatumStr and returns the value as a string
	string exifDateTime2$ = exifhandle.GetDatumStr("Exif.Photo.DateTimeOriginal");

	if (exifDateTime$ != "") {
		datetime.SetDateYYYYMMDD(StrToNum(sprintf("%s%s%s",exifDateTime$.substr(0,4),exifDateTime$.substr(5,2),exifDateTime$.substr(8,2))));
		datetime.SetTime(StrToNum(exifDateTime$.substr(11,2)), StrToNum(exifDateTime$.substr(14,2)), StrToNum(exifDateTime$.substr(17,2)));
		}
	else if (exifDateTime2$ != "") {
		datetime.SetDateYYYYMMDD(StrToNum(sprintf("%s%s%s",exifDateTime2$.substr(0,4),exifDateTime2$.substr(5,2),exifDateTime2$.substr(8,2))));
		datetime.SetTime(StrToNum(exifDateTime2$.substr(11,2)), StrToNum(exifDateTime2$.substr(14,2)), StrToNum(exifDateTime2$.substr(17,2)));
		}
	else
		return 0;

	gpsdialog.SetCtrlValueStr( "status", "Added: " + FileNameGetName(filename$) + "." + FileNameGetExt(filename$));

	imageListTime[filename$] = datetime; # add time to string list
	return 1; # return 1 as successful
	}# end func GetEXIF

##################################
proc UpdateImages(numeric imageNum, numeric computedflag, numeric errorreportflag) {
		class GUI_CTRL_LISTBOX imagelist;
		local string imageString$, imageName$;
		local numeric i=0;

		imagelist = gpsdialog.GetCtrlByID("imagebox");
		imagelist.DeleteAllItems(); # clear list box
		imageStringList.Clear();

		imageListName.RemoveDuplicates();
		ComputeCoordinates(imageNum, computedflag, errorreportflag); # recompute coordiantes for images

		for i=0 to imageListName.GetNumItems()-1{
			imageName$=imageListName[i];
			if(Assigned[imageName$]==1) # assigned coordinates
				{
				imageListNameAttached.AddToEnd(imageName$);
				imageListNameAttached.RemoveDuplicates();
				}

			print(imageListData[imageName$].Position.x);

			if(IsNull(imageListData[imageName$].Position.x) && IsNull(imageListData[imageName$].Position.y) && IsNull(imageListData[imageName$].Position.z)) # computed coordinates
				imageString$ = sprintf("*** %s.%s   %i %02i:%02i:%02i  x: %.7f  y: %.7f  z: %.7f ", FileNameGetName(imageName$), FileNameGetExt(imageName$), imageListTime[imageName$].GetDateYYYYMMDD(), imageListTime[imageName$].GetHour(), imageListTime[imageName$].GetMin(), imageListTime[imageName$].GetSec(), imageListData[imageName$].Position.x, imageListData[imageName$].Position.y, imageListData[imageName$].Position.z);
			else
				imageString$ = sprintf("%s.%s   %i %02i:%02i:%02i  x: %.7f  y: %.7f  z: %.7f ", FileNameGetName(imageName$), FileNameGetExt(imageName$), imageListTime[imageName$].GetDateYYYYMMDD(), imageListTime[imageName$].GetHour(), imageListTime[imageName$].GetMin(), imageListTime[imageName$].GetSec(), imageListData[imageName$].Position.x, imageListData[imageName$].Position.y, imageListData[imageName$].Position.z);

			imagelist.AddItem(imageString$);
			imageStringList[imageName$] = imageString$;
	     	} # end for

	}# end proc UpdateImages()

##################################
proc RemoveAll() { # procedure to remove all GPS coordinates in listbox
	class GUI_CTRL_LISTBOX coordlist, loglist;
	coordlist = gpsdialog.GetCtrlByID("coordlistbox"); # get control for listbox
	loglist = gpsdialog.GetCtrlByID("loglistbox");
		if( PopupYesNo("Remove All GPS coordinates?")==1) # confirm remove all coordinates
			{
			coordlist.DeleteAllItems(); # clear coord list box
			loglist.DeleteAllItems(); # clear log list box
			coordStringList.Clear();
			gpsdbase.RemoveAllLogs();
			gpsdialog.SetCtrlValueStr( "status", "Removed All Logs");
			}
	} # end proc RemoveAll()


#############################
proc AddGPS(string logname$) {
# procedure to add GPS coordinates to listbox
# called each time a log is removed or added to list
	class GUI_CTRL_LISTBOX loglist,coordlist;
	local numeric count=0;
	class FILE logfile;
	class DATETIME datetime;
	class GPSDATA data;

	numeric cameraoffset, timeoffset;
	string method$;
	GetOptions(cameraoffset, method$, timeoffset); # returns: method$, constraints$ cameraoffset, timeoffset
	gpsdbase.SetOffset(cameraoffset);

	coordlist = gpsdialog.GetCtrlByID("coordlistbox");
	coordlist.DeleteAllItems(); # clear coord listbox
	coordStringList.Clear();

	if(logname$!="")
		gpsdbase.ReadLog(logname$);

	for count=0 to gpsdbase.GetNumPoints()-1 {
		gpsdbase.GetPoint(count, datetime, data);
		datetime = gpsdbase.ApplyOffset(datetime);
		local string coord$ = sprintf("%i %02i:%02i:%02i , X: %.6f , Y: %.6f , Z: %.6f", datetime.GetDateYYYYMMDD(), datetime.GetHour(), datetime.GetMin(), datetime.GetSec(), data.Position.x, data.Position.y, data.Position.z); 
		coordlist.AddItem(coord$);
		coordStringList.AddToEnd(coord$);
		}

	UpdateImages(-1, 0, 1);
  	} # end proc AddGPS()

#############################
proc RemoveGPS() { # procedure to remove GPS coordinates in listbox
	class GUI_CTRL_LISTBOX list;
	local numeric index; # selected item index
	list = gpsdialog.GetCtrlByID("coordlistbox"); # get control for list box
	index = list.GetSelectedItemIndex(); # get selected item index
	list.DeleteItemIndex(index); # remove from listbox
	gpsdbase.RemovePoint(index);
	coordStringList.Remove(index);
	} # end proc RemoveGPS()

#############################
proc AddLogDirectory() {
# Procedure to add a directory of logfiles to current list of log files
# Checks if valid log file before adding
# Correct format:
# date(YYYYMMDD),time(HHMMSS),XPos(deg),YPos(deg),Elev(m),XVel(m/s),YVel(m/s),ZVel(m/s),Head(deg),Speed(m/s),DataSrc,NumSat
	class GUI_CTRL_LISTBOX list; # class for listbox control
	class FILEPATH filepath;
	class STRINGLIST filenames;

	local string defaultpath$,logpath$, line$;
	local numeric records, count, i=0; # format=flag to check for valid format, 1=valid, 0=invalid
	local string report$; # string holding any errors with adding log files

	defaultpath$ = _context.ScriptDir; # get directory containing gps logs
	filepath.SetName( GetDirectory( defaultpath$, "Please select the directory containing the GPS log files" ) );
	filenames = filepath.GetFileList( "*.gps"); # get file list in directory

	list = gpsdialog.GetCtrlByID("loglistbox"); # get control for listbox holding logfile list
	for count=0 to filenames.GetNumItems()-1 # for all files in directory
		{
		logpath$ = sprintf( "%s/%s.%s", filepath.GetPath(), FileNameGetName(filenames[count]), FileNameGetExt(filenames[count]));

		if(FileNameGetName(filenames[count])!="") # if user did not cancel selection dialog
			{
			records = gpsdbase.GetNumPoints();
			AddGPS(logpath$);
			if(gpsdbase.GetNumPoints() > records)
				list.AddItem(logpath$);
			else # if not valid
				report$=report$ + sprintf("Could not add '%s.%s'\n",FileNameGetName(logpath$), FileNameGetExt(logpath$));
			} # end if
		} # end for

	filenames = filepath.GetFileList( "*.log"); # get file list in directory

	list = gpsdialog.GetCtrlByID("loglistbox"); # get control for listbox holding logfile list
	for count=0 to filenames.GetNumItems()-1 # for all files in directory
		{
		logpath$ = sprintf( "%s/%s.%s", filepath.GetPath(), FileNameGetName(filenames[count]), FileNameGetExt(filenames[count]));

		if(FileNameGetName(filenames[count])!="") # if user did not cancel selection dialog
			{
			records = gpsdbase.GetNumPoints();
			AddGPS(logpath$);
			if(gpsdbase.GetNumPoints() > records)
				list.AddItem(logpath$);
			else # end if not valid
				report$=report$ + sprintf("Could not add '%s.%s'\n",FileNameGetName(logpath$), FileNameGetExt(logpath$));
			} # end if
		} # end for

	if(report$ != "") # print error report
		{
		report$=report$ + sprintf("These gps log files are not standard format and could not be added.");
		PopupMessage(report$);
		}

	gpsdialog.SetCtrlValueStr( "status", "Added: " + filepath.GetPath());
	} # end proc AddLogDirectory()

##############################
proc AddLog() {
# Add a single selected log file to list of log files
	class GUI_CTRL_LISTBOX list;

	local numeric records=0;
	local string line$, message$;

	local string prompt$ = "Select GPS Log";
	local string logname$=GetInputFileName("", prompt$, ".gps .log"); # User selected log file
	local string logpath$ = sprintf( "%s/%s.%s", FileNameGetPath(logname$), FileNameGetName(logname$), FileNameGetExt(logname$));
	list = gpsdialog.GetCtrlByID("loglistbox");

	if(FileNameGetName(logname$)!="") # if user did not cancel dialog
		{
		records = gpsdbase.GetNumPoints();
		AddGPS(logpath$);
		if(gpsdbase.GetNumPoints() > records)
			{
			list.AddItem(logpath$);
			gpsdialog.SetCtrlValueStr( "status", "Added: " + FileNameGetName(logname$) + "." + FileNameGetExt(logname$));
			}
		else # if not valid
			{ # print error report
			message$=sprintf("Could not add '%s.%s', log file not standard format.",FileNameGetName(logname$), FileNameGetExt(logname$));
			PopupMessage(message$); # pop up message
			gpsdialog.SetCtrlValueStr( "status", "Could not add: " + FileNameGetName(logname$) + "." + FileNameGetExt(logname$));
			}
		} # end if
	} # end proc

#############################
proc RemoveLog() { # procedure to remove a log from the list
	class GUI_CTRL_LISTBOX list;
	local numeric records;
	list = gpsdialog.GetCtrlByID("loglistbox"); # get control for list box
	local numeric index=list.GetSelectedItemIndex(); # get selected item index
	local string logname$; 
	gpsdbase.GetLog(index-1, logname$);
	gpsdialog.SetCtrlValueStr( "status", "Removed: " + FileNameGetName(logname$) + "." + FileNameGetExt(logname$));
	list.DeleteItemIndex(index); # remove from listbox
	gpsdbase.RemoveLog(logname$);
	AddGPS(""); # redisplay new list of coordinates
	} # end proc RemoveLog()

#############################
proc CreateTable() { # procedure to create new GPS table

	# DatabaseCreate
	if (ObjectExists(filename$, obj$, "Vector") == 0) # check if vector exists
		{
		CreateVector(GPSVector, filename$, obj$, desc$, "3DVector"); # create vector
		CreateImpliedGeoref(GPSVector, crs);
		}

	if (ObjectExists(filename$, obj$, "Vector") == 1) # check if vector exists
		OpenVector(GPSVector, filename$, obj$); # open vector

	dbase= OpenVectorPointDatabase(GPSVector); # open point dbase

	if ( TableExists(dbase,IMAGEtablename$) == 0) # table does not exist
		{
		table=TableCreate(dbase, IMAGEtablename$, tabledesc$); # create table, add time, x, y, z, image fields
		TableAddFieldString(table, "Date Time", 17, 17);
		TableAddFieldFloat(table, "Long (deg)",9,6);
		TableAddFieldFloat(table, "Lat (deg)",9,6);
		TableAddFieldFloat(table, "Elev (m)",9,6);
		TableAddFieldString(table, "Image",200,100);
		}

  } # end proc CreateTable()

############################
proc Save() { # procedure to select GPS log file

	string prompt$ = "Select Vector Object";
	GetOutputObject("Vector", "NewOrExisting", prompt$, filename$, obj$, desc$); # User selected vector object
	# filename$ = rvc file
	# obj$ = vector object name
	# desc$ = vector object description

	# write file name to text box
	local string dlgtext$ = sprintf( "%s %s/%s.%s", FileNameGetName(obj$), FileNameGetPath(filename$), FileNameGetName(filename$), FileNameGetExt(filename$));
	gpsdialog.SetCtrlValueStr( "filetext", dlgtext$ );

	# enable Display of database, Attaching to Database
#	gpsdialog.GetCtrlbyID("displaytable").SetEnabled(1);
	gpsdialog.GetCtrlbyID("attach").SetEnabled(1);
	gpsdialog.GetCtrlbyID("changedirectory").SetEnabled(1);

	CreateTable(); # create GPS table
	}	# end proc Save()

#######################################
######### View Display Procedures ###############
#######################################
proc DisplayTable() { # procedure to display table in database editor
	class DBEDITOR dbedit;
	class DBEDITORTABLE tabview;

	dbase= OpenVectorPointDatabase(GPSVector); # open point dbase
	dbedit = DBEditorCreate(dbase); # create dbeditor handle
	tabview = DBEditorOpenTabularView(dbedit, IMAGEtablename$); # open table in tabular view
	} # end proc DisplayTable()
#######################################


###############################################
########## Procedures for Images #######################
###############################################

############################
proc AddImageDirectory() { # procedure to select input image folder and add to image list

	class FILEPATH filepath;
	class STRINGLIST filenames;

	local string defaultpath$ = _context.ScriptDir; # get directory containing images
	local numeric hasEXIF=0; # flag to determine if jpg has valid EXIF header
	local numeric count=0;
	local string report$; # string containing error report
	local string imagePath$, imageTime$;

	filepath.SetName( GetDirectory( defaultpath$, "Please select the directory containing the JPEG files" ) );
	filenames = filepath.GetFileList( "*.jpg" ); # get list of files in directory

	for count=0 to filenames.GetNumItems()-1 # for all images in directory
	{ 
		imagePath$ = sprintf( "%s/%s.%s", filepath.GetPath(), FileNameGetName(filenames[count]), FileNameGetExt(filenames[count]));
		if (Assigned[imagePath$]==1)
			break;
		if (filenames[count]!="") # if user has not cancelled dialog
			{
			hasEXIF=GetEXIF(imagePath$); # get EXIF header, returns 1 if valid, 0 if invalid
			if (hasEXIF == 1) # has EXIF
				{
				imageListName.AddToEnd(imagePath$); # add name to string list
				imageListData[imagePath$].Position.x=0; imageListData[imagePath$].Position.y=0; imageListData[imagePath$].Position.z=0;
				}
			if (hasEXIF == 0) # has no EXIF
				{
				report$=report$ + sprintf("Could not add image '%s.%s'.\n",FileNameGetName(filenames[count]),FileNameGetExt(filenames[count]));
				}
			} # end if
	} # end for

	if(report$ != "") # print error report
		{
		report$=report$ + sprintf("These Images contained no EXIF header and could not be added.");
		PopupMessage(report$);
		}

	UpdateImages(-1,0,0);
	gpsdialog.SetCtrlValueStr( "status", "Added: " + filepath.GetPath());
	} # end proc AddImageDirectory

############################
proc AddImage() { # procedure to select single input image and add to image list

	local string prompt$ = "Select Image";
	local string imageName$=GetInputFileName("", prompt$, ".jpg"); # User selected Image
	local string imagePath$ = sprintf( "%s/%s.%s", FileNameGetPath(imageName$), FileNameGetName(imageName$), FileNameGetExt(imageName$));
	local numeric hasEXIF=0;
	local string imageTime$, message$;

	if(Assigned[imagePath$]==1)
		break;

	if(imageName$!="")# if user did not cancel dialog
		{
		hasEXIF=GetEXIF(imagePath$); # get exif header

		if(hasEXIF == 1) # has exif
			{
			imageListName.AddToEnd(imagePath$); # add name to string list
			local numeric NumItems = imageListName.GetNumItems();
			imageListName.RemoveDuplicates();
			if(imageListName.GetNumItems() == NumItems)
				UpdateImages(imageListName.GetNumItems()-1,0,0);
			}

		if(hasEXIF == 0) # has no exif
			{
			message$=sprintf("Could not add image '%s.%s', image has no EXIF header.",FileNameGetName(imageName$),FileNameGetExt(imageName$));
			PopupMessage(message$);# report error
			}
		} # end if imageName
	} # end proc AddImage


#######################################################################
## Procedures related to choosing image coordinates from a view of a
## georeferenced raster using a point tool.

# Procedure called when point tool is placed in the view.
proc OnToolSet () {

	clear();

	# transform point coordinates from view coordinates returned by point tool to raster map coordinates
	ptrastmap = TransPoint2D(myPt.Point, transViewToRastMap);

	# transform point coordinates from raster map to WGS84 / Geographic
	ptGeog = TransPoint2D(ptrastmap, transRastMapToGeog);

	local string coords$ = sprintf("x = %5.5f, y = %5.5f",ptGeog.x, ptGeog.y);
	ViewSetMessage(view,coords$);		# update status line in view
	}

# Procedure called when right mouse button is pressed
proc OnToolApply () {
	class GUI_CTRL_LISTBOX list;

	list = gpsdialog.GetCtrlByID("imagebox"); # get control for list box
	local numeric index = list.GetSelectedItemIndex(); # get selected item index
	local string imageName$ = imageListName.GetString(index); # grab input name
	imageListData[imageName$].Position.x = ptGeog.x;
	imageListData[imageName$].Position.y = ptGeog.y;
	imageListData[imageName$].Position.z = 0;

	Assigned[imageName$]=1;
	UpdateImages(index,0,1);
	}

# Procedure called when Close button on view window is pressed.
proc OnClose() {
	DialogClose(pcwin);
	DestroyWidget(pcwin);
	CloseRaster(rasterIn);
	}

# Procedure called when view window widget is destroyed.
proc OnDestroy() {
       Exit();
	}

# Procedure called by Select Coordinates from Georeferenced Raster entry on the gpsdialog Image menu.
# Prompts to choose raster, creates a view window and displays the raster, and creates a point tool
# for obtaining map coordinates from the raster.
proc SelectPoint() {

	GetInputRaster(rasterIn);
	gp = GroupCreate();

	# Create dialog window.
	pcwin = CreateFormDialog("Find Coordinates");
	WidgetAddCallback(pcwin.Shell.PopdownCallback, OnClose);
	WidgetAddCallback(pcwin.DestroyCallback, OnDestroy);

	# Create pushbutton item for Close.
	class PushButtonItem btnItemClose;
	btnItemClose = CreatePushButtonItem(" Close ",OnClose);

	# Create button row for Close button.
	class XmForm btnrow;
	btnrow = CreateButtonRow(pcwin,btnItemClose);
	btnrow.BottomWidget = pcwin;
	btnrow.RightWidget = pcwin;
	btnrow.LeftWidget = pcwin;

	# Create view in pcwin form to display input raster and vector.
	# A view has its own XmForm widget accessed as a class member "Form".
	# It is automatically attached to the parent form at the top.
	view = GroupCreateView(gp,pcwin,"",380,380,
              "NoLegendView,NoScalePosLine,DestroyOnClose");
	view.Form.LeftWidget = pcwin;
	view.Form.RightWidget = pcwin;
	view.Form.BottomWidget = btnrow;

	# Add point tool to view.
	myPt = ViewCreatePointTool(view,"Point Tool","point_select","standard");
	ToolAddCallback(myPt.PositionSetCallback,OnToolSet);
	ToolAddCallback(myPt.ActivateCallback,OnToolApply);
	myPt.DialogPosition = "RightCenter";
	ViewAddToolIcons(view);

	DialogOpen(pcwin);	# open the view

	# add selected raster to the view and redraw
	rasterInLayer = GroupQuickAddRasterVar(gp,rasterIn);
	ViewRedrawFull(view);

	# get the coordinate reference system from the raster layer and
	# get TRANSPARM from view to raster map coordinates
	coordrefsysRast = rasterInLayer.MapRegion.CoordRefSys;		
	transViewToRastMap = ViewGetTransMapToView(view, coordrefsysRast, 1); 	

	# set up a TRANSPARM from raster map coordinates to WGS84 / Geographic
	# to match GPS log coordinates
	coordrefsysGeog.Assign("Geographic2D_WGS84_Deg");
	transRastMapToGeog.InputCoordRefSys = coordrefsysRast;
	transRastMapToGeog.OutputCoordRefSys = coordrefsysGeog;

	ViewActivateTool(view,myPt);
	ViewSetMessage(view,"Left-click to move point.  Right-click to assign coordinates.");
	} # end proc SelectPoint


#############################
proc FillLog(){ # procedure to fill log selection dialog
	class GUI_CTRL_LISTBOX coordlist;

	coordlist = logdialog.GetCtrlByID("coordlistbox");
	coordlist.DeleteAllItems();
	local numeric i=0;
	for i=0 to coordStringList.GetNumItems()-1
	coordlist.AddItem(coordStringList[i]);
	} # end proc FillLog()

#############################
proc SelectLog(){ # procedure to select log record from dialog
	local numeric imageindex, coordindex; # selected item index

	class GUI_CTRL_LISTBOX imagelist, coordlist;
	class POINT3D coord;
	class DATETIME datetime;
	class GPSDATA data;

	imagelist = gpsdialog.GetCtrlByID("imagebox"); # get control for list box
	coordlist = logdialog.GetCtrlByID("coordlistbox");

	imageindex = imagelist.GetSelectedItemIndex(); # get selected item index
	local string imageName$ = imageListName.GetString(imageindex); # grab input name

	coordindex = coordlist.GetSelectedItemIndex(); # get selected item index
	gpsdbase.GetPoint(coordindex, datetime, data);
	imageListData[imageName$].Position.x = data.Position.x;
	imageListData[imageName$].Position.x = data.Position.y;
	imageListData[imageName$].Position.x = data.Position.z;

	Assigned[imageName$]=1;

	UpdateImages(imageindex,0,1);
	} # end proc SelectLog()

######################################
proc AssignLogImage() { # procedure to select log record and assign lat/lon to image

	local numeric errlog;
	local string xmllog$;
 

	### Create string variable with XML specification of dialog
	xmllog$ = '
	
		
		
			
				
			
		
		
	';

	### parse XML text for the dialog into memory; 
	### return an error code (number < 0 ) if there are syntax errorsi
	errlog = logdoc.Parse(xmllog$);

	if ( errlog < 0 ) {
		PopupError( errlog ); 	# 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
	lognode = logdoc.GetElementByID("logdlg");

	if ( lognode == 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.
	logdialog.SetXMLNode(lognode);
	ret = logdialog.DoModal();

	} # end proc AssignLogImage()


#############################
proc FillImage() { # procedure to fill log selection dialog
	class GUI_CTRL_LISTBOX imagelist;
	local string imageName$;
	local numeric i=0;

	imagelist = transferdialog.GetCtrlByID("imagebox");
	imagelist.DeleteAllItems();

	for i=0 to imageListNameAttached.GetNumItems()-1{
		imageName$=imageListNameAttached[i];
		imagelist.AddItem(imageStringList[imageName$]);
		}
	} # end proc FillLog()

#############################
proc SelectImage() { # procedure to select log record from dialog
	local numeric imageindex, coordindex; #selected item index
	class POINT3D coord;
	class GUI_CTRL_LISTBOX imagelist, transferlist;

	imagelist = gpsdialog.GetCtrlByID("imagebox"); # get control for list box
	transferlist = transferdialog.GetCtrlByID("imagebox");

	imageindex = imagelist.GetSelectedItemIndex(); # get selected item index
	local string imageName$ = imageListName.GetString(imageindex); # grab input name

	imageindex = transferlist.GetSelectedItemIndex();
	local string transferName$ = imageListNameAttached.GetString(imageindex);

	imageListData[imageName$] = imageListData[transferName$];

	Assigned[imageName$]=1;

	UpdateImages(imageindex,0,1);
	} # end proc SelectLog()

#############################
proc AssignTransferImage() { # procedure to select log record and assign lat/lon to image

	local numeric errtransfer;
	local string xmltransfer$;
 
	### Create string variable with XML specification of dialog
	xmltransfer$ = '
	
		
		
			
				
			
		
		
	';

	### parse XML text for the dialog into memory; 
	### return an error code (number < 0 ) if there are syntax errorsi
	errtransfer = transferdoc.Parse(xmltransfer$);

	if ( errtransfer < 0 ) {
		PopupError( errtransfer ); 	# 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
	transfernode = transferdoc.GetElementByID("transferdlg");

	if ( transfernode == 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.
	transferdialog.SetXMLNode(transfernode);
	ret = transferdialog.DoModal();

	} # end proc AssignTransferImage()

#############################
proc SetLatLon() { # procedure to select log record from dialog
	local numeric imageindex; # selected item index
	local numeric xcoord, ycoord=0;

	class GUI_CTRL_LISTBOX imagelist;

	imagelist = gpsdialog.GetCtrlByID("imagebox"); # get control for list box

	imageindex = imagelist.GetSelectedItemIndex(); # get selected item index

	local string imageName$ = imageListName.GetString(imageindex); # grab input name

	xcoord = latlondialog.GetCtrlValueNum("xcoorddeg") + (latlondialog.GetCtrlValueNum("xcoordmin") + (latlondialog.GetCtrlValueNum("xcoordsec")/60))/60;
	ycoord = latlondialog.GetCtrlValueNum("ycoorddeg") + (latlondialog.GetCtrlValueNum("ycoordmin") + (latlondialog.GetCtrlValueNum("ycoordsec")/60))/60;

	imageListData[imageName$].Position.x = xcoord;
	imageListData[imageName$].Position.y = ycoord;
	imageListData[imageName$].Position.z = latlondialog.GetCtrlValueNum("zcoord");

	Assigned[imageName$]=1;

	UpdateImages(imageindex,0,1);
	} # end proc SetLatLon

#############################
proc AssignLatLonImage() { # procedure to select log record and assign lat/lon to image

	local numeric errlatlon;
	local string xmllatlon$;
 
	### Create string variable with XML specification of dialog
	xmllatlon$ = '
	
		
		
			
				
				
				
				
			
			
				
				
				
				
			
			
				
				
			
		
		
	';

	### parse XML text for the dialog into memory; 
	### return an error code (number < 0 ) if there are syntax errorsi
	errlatlon = latlondoc.Parse(xmllatlon$);

	if ( errlatlon < 0 ) {
		PopupError( errlatlon ); 	# 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
	latlonnode = latlondoc.GetElementByID("latlondlg");

	if ( latlonnode == 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.
	latlondialog.SetXMLNode(latlonnode);
	ret = latlondialog.DoModal();

	} # end proc AssignLogImage()

#############################
proc AssignExistingImage() { # procedure to use coordinates already existing in image
	local numeric index, keyindex, valid = 0; # selected item index
	class GUI_CTRL_LISTBOX list;
	class POINT3D coord;
	class STRINGLIST keys;

	list = gpsdialog.GetCtrlByID("imagebox"); # get control for list box
	index = list.GetSelectedItemIndex(); # get selected item index
	local string imageName$ = imageListName.GetString(index); # grab input name

	exifhandle.Open(imageName$);
	keys = exifhandle.GetKeyList();

	for keyindex=0 to keys.GetNumItems()-1{
		if(keys[keyindex]=="Exif.GPSInfo.GPSLongitude")
			valid++;
		if(keys[keyindex]=="Exif.GPSInfo.GPSLongitudeRef")
			valid++;
		if(keys[keyindex]=="Exif.GPSInfo.GPSLatitude")
			valid++;
		if(keys[keyindex]=="Exif.GPSInfo.GPSLatitudeRef")
			valid++;
		if(keys[keyindex]=="Exif.GPSInfo.GPSAltitude")
			valid++;
		if(keys[keyindex]=="Exif.GPSInfo.GPSAltitudeRef")
			valid++;
		}

	if(valid==6){
		string xcoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLongitude");
		string xcoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLongitudeRef");

		string ycoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLatitude");
		string ycoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLatitudeRef");

		string zcoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSAltitude");
		string zcoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSAltitudeRef");

		coord.x = (StrToNum(GetToken(GetToken(xcoord$, " ", 1),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 1),"/",2))) 
							+ ((StrToNum(GetToken(GetToken(xcoord$, " ", 2),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 2),"/",2)))
							+ (StrToNum(GetToken(GetToken(xcoord$, " ", 3),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 3),"/",2)))/60)/60;
		if (xcoordref$=="W") then coord.x=coord.x*-1;

		coord.y = (StrToNum(GetToken(GetToken(ycoord$, " ", 1),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 1),"/",2))) 
							+ ((StrToNum(GetToken(GetToken(ycoord$, " ", 2),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 2),"/",2)))
							+ (StrToNum(GetToken(GetToken(ycoord$, " ", 3),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 3),"/",2)))/60)/60;

		if (ycoordref$=="S") then coord.y=coord.y*-1;

		coord.z = StrToNum(GetToken(zcoord$,"/",1))/ StrToNum(GetToken(zcoord$,"/",2));

		if (zcoordref$=="1") then coord.z=coord.z*-1;

		imageListData[imageName$].Position.x = coord.x;
		imageListData[imageName$].Position.y = coord.y;
		imageListData[imageName$].Position.z = coord.z;

		Assigned[imageName$]=1;
		UpdateImages(index, 0, 1);
		}
		else
		{
		PopupMessage("Image does not contain all necessary GPS tags.");
		}
	} # end proc AssignExistingImage

#############################
proc PopulateImageMenu() {
	class GUI_CTRL_MENUBUTTON menu;
	menu = gpsdialog.GetCtrlByID("imagemenu");
	menu.AddItem("  Use Computed Coordinates from Logs...", "ComputedCoords");
	menu.AddItem("  Select Coordinates from Georeferenced Raster... ", "SelectPoint");
	menu.AddItem("  Select Coordinates in a Log Record... ", "AssignLogImage");
	menu.AddItem("  Manually Enter Lat/Lon Coordinates... ", "AssignLatLonImage");
	menu.AddItem("  Transfer Coordinates from Other Image... ", "AssignTransferImage");
	menu.AddItem("  Use Pre-Existing EXIF Coordinates... ", "AssignExistingImage");
	} # end proc PopulateImageMenu

proc SelectImageMenu() {
	class GUI_CTRL_MENUBUTTON menu;
	class GUI_CTRL_LISTBOX list;
	menu = gpsdialog.GetCtrlByID("imagemenu");
	list = gpsdialog.GetCtrlByID("imagebox");
	local numeric index = list.GetSelectedItemIndex();
	if(menu.GetValue()=="ComputedCoords") UpdateImages(index,1,1);
	if(menu.GetValue()=="SelectPoint") SelectPoint();
	if(menu.GetValue()=="AssignLogImage") AssignLogImage();
	if(menu.GetValue()=="AssignLatLonImage") AssignLatLonImage();
	if(menu.GetValue()=="AssignTransferImage") AssignTransferImage();
	if(menu.GetValue()=="AssignExistingImage") AssignExistingImage();
	} # end proc SelectImageMenu

#############################
proc ViewImage() { # procedure to open selected image in default OS viewer
	local numeric index; # selected item index
	class GUI_CTRL_LISTBOX list;

	list = gpsdialog.GetCtrlByID("imagebox"); # get control for list box
	index = list.GetSelectedItemIndex(); # get selected item index
	local string image$ = imageListName.GetString(index); # grab input name
	RunAssociatedApplication(image$);
	} # end proc ViewImage()

#############################
proc RemoveImage() { # procedure to remove image in listbox
	class GUI_CTRL_LISTBOX list;
	local numeric index; # selected item index
		list = gpsdialog.GetCtrlByID("imagebox"); # get control for list box
		index = list.GetSelectedItemIndex(); # get selected item index
		list.DeleteItemIndex(index); # remove from listbox
		local string imageName$ = imageListName[index];
		gpsdialog.SetCtrlValueStr( "status", "Removed: " + FileNameGetName(imageName$) + "." + FileNameGetExt(imageName$) + " - " + imageListTime[imageName$]);
		imageListData[imageName$].Position.x = 0; imageListData[imageName$].Position.y = 0; imageListData[imageName$].Position.z = 0;
		Assigned[imageName$]=0;
		imageListName.Remove(index); # remove from stringlist
		local numeric i;
		for i=0 to imageListNameAttached.GetNumItems()-1{
			if(imageListNameAttached[i]==imageName$) imageListNameAttached.Remove(i);
			}
		imageStringList[imageName$] = "";

	} # end proc RemoveImage()

#############################
proc RemoveAllImages() { # procedure to remove all images in listbox
	class GUI_CTRL_LISTBOX list;
	list = gpsdialog.GetCtrlByID("imagebox"); # get control for listbox
	if( PopupYesNo("Remove All Images?")==1) # confirm remove all images
		{
		list.DeleteAllItems(); # clear list box

		imageListName.Clear(); # clear string list
		imageListNameAttached.Clear();
		imageStringList.Clear();
		imageListTime.Clear(); # clear string list
		imageListData.Clear();
		Assigned.Clear();

		gpsdialog.SetCtrlValueStr( "status", "Removed All Images");
		}
	} # end proc RemoveAll()

############################
proc ChangeDirectory() {
# Procedure to change directory of all images in table
# User selects directory to change the image path to
# Image name read in from each record of the table
# Image name then compared to the list of images in the directory
# If the image name is equal to a file in the directory,
# then the path of the image in the table is changed to the new directory
	class FILEPATH filepath;
	class STRINGLIST filenames;

	dbase= OpenVectorPointDatabase(GPSVector); # get dbase
	table=DatabaseGetTableInfo(dbase, IMAGEtablename$); # get table class

	local numeric count,recordNum;
	local string imageName$, imagePath$, imageCurrent$, imageExt$;
	local numeric numberOfRecords=table.NumRecords;

	local string defaultpath$ = _context.ScriptDir; # get directory containing gps logs
	filepath.SetName( GetDirectory( defaultpath$, "Please select the directory containing the images" ) );
	filenames = filepath.GetFileList( "*.jpg" );# get file list in directory

	for recordNum=1 to numberOfRecords # for all records
		{
		# get image path, name and extension
		imagePath$=TableReadFieldStr(table, "Image", recordNum);
		imageCurrent$=FileNameGetName(imagePath$);
		imageExt$=FileNameGetExt(imagePath$);

		for count=0 to filenames.GetNumItems()-1 # for all files in directory
			{
			imageName$=FileNameGetName(filenames[count]); #get name of file
			if(imageCurrent$==imageName$)# compare both names
				{
				count=filenames.GetNumItems()-1;
				# if image name in directory equal to record, then change path in table to this directory
				imagePath$ = sprintf( "%s/%s.%s", filepath.GetPath(), imageName$, imageExt$);
				}
			}
		# change image path
		TableWriteField(table, recordNum, "Image", imagePath$);
		}

	}# end proc ChangeDirectory()

############################
proc AttachPoints() { 
# procedure to attach table records to points in vector
# call each time new record added
	local array numeric recordarray[100]; # record array
	local array numeric elementarray[1]; # element array
	local array numeric writearray[1]; # records to write array
	local numeric numberOfRecords, numberOfElements, elementnum, recordnum,x,y,z,i;

	dbase= OpenVectorPointDatabase(GPSVector); # get dbase
	table=DatabaseGetTableInfo(dbase, IMAGEtablename$); # get table class

	elementnum=1; # set to first element
	numberOfRecords=table.NumRecords;

	# clear all current attachments:
	for i=1 to numberOfRecords # add all records to record array
		recordarray[i]=i;
	for i=1 to numberOfRecords # for every element in vector 
		{
		elementarray[1]=i;
		 # remove all records from each element
		TableRemoveAttachment(table, elementarray[1], recordarray, numberOfRecords);
		}

	for recordnum=1 to numberOfRecords # for all records
		{
		x=TableReadFieldNum(table, "Long (deg)", recordnum); # read in coordinates
		y=TableReadFieldNum(table, "Lat (deg)", recordnum);
		z=TableReadFieldNum(table, "Elev (m)", recordnum);

		VectorChangePoint(GPSVector, elementnum, x, y, z); # change vector point to coordinates of record
		writearray[1]=recordnum; # add current record to array of records to write
		TableWriteAttachment(table, elementnum, writearray, 1, "point"); # make attachment
		if(TableReadFieldStr(table, "Date Time",recordnum) != TableReadFieldStr(table, "Date Time", recordnum+1))
			elementnum++; # if not duplicate records, move to next point
		}
	} # end proc AttachPoints()

############################
proc ShiftRecords(numeric recordnum){ 
# procedure to create new record and shift all records forward 1 starting at recordnum
# designed to handle TableNewRecord only adding record to end of table,
# but want to preserve a certain record order number

	class POINT3D coordCurrent,coordNext; # point3d class to hold current and next record coordinates

	dbase= OpenVectorPointDatabase(GPSVector); # open point dbase
	table=DatabaseGetTableInfo(dbase, IMAGEtablename$); # get table class

	local numeric count=table.NumRecords;
	local string datetimeCurrent$, datetimeNext$;
	local string imageCurrent$, imageNext$;
	local numeric i=0;

	datetimeCurrent$=TableReadFieldStr(table, "Date Time", recordnum); # read current record coordinates
	coordCurrent.x=TableReadFieldNum(table, "Long (deg)", recordnum);
	coordCurrent.y=TableReadFieldNum(table, "Lat (deg)", recordnum);
	coordCurrent.z=TableReadFieldNum(table, "Elev (m)", recordnum);
	imageCurrent$=TableReadFieldStr(table, "Image", recordnum);

	 TableNewRecord(table); # create new blank record at end of table to shift last record into

	 for i=recordnum to count
		{
		datetimeNext$=TableReadFieldStr(table, "Date Time", i+1); # read next record info
		coordNext.x=TableReadFieldNum(table, "Long (deg)", i+1); 
		coordNext.y=TableReadFieldNum(table, "Lat (deg)", i+1);
		coordNext.z=TableReadFieldNum(table, "Elev (m)", i+1);
		imageNext$=TableReadFieldStr(table, "Image", i+1);

		# write previous record information to next record, shifting the record forward 1
		TableWriteRecord(table, i+1, datetimeCurrent$, coordCurrent.x, coordCurrent.y, coordCurrent.z, imageCurrent$);

		datetimeCurrent$=datetimeNext$; # Old next record becomes new current record
		coordCurrent.x=coordNext.x;
		coordCurrent.y=coordNext.y;
		coordCurrent.z=coordNext.z;
		imageCurrent$=imageNext$; 
		} # end for
 
	} # end proc ShiftRecords

############################
func NewRecord(string inputtime$, numeric x, numeric y, numeric z, string inputname$) {
# function to add a new record to a table
# preserves correct record order based on time
# also checks for duplicate records

	class DATETIME datetimeInput, datetimeCurrent;

	local numeric count=1;
	local string currentTime$, currentName$, date$, time$;
	local numeric tInput, tCurrent;

	dbase= OpenVectorPointDatabase(GPSVector); # open point dbase
	table= DatabaseGetTableInfo(dbase, IMAGEtablename$); # open table

	date$=inputtime$.substr(0,8); # parse date for input
	datetimeInput.SetDateYYYYMMDD(StrToNum(date$));
	time$=inputtime$.substr(9,8); # parse time for input
	datetimeInput.SetTime(StrToNum(time$.substr(0,2)),StrToNum(time$.substr(3,2)),StrToNum(time$.substr(6,2)));
	tInput=DecimalTime(datetimeInput.GetHour(),datetimeInput.GetMin(),datetimeInput.GetSec()); # get decimal time for input

	if (table.NumRecords >= 1) # if table not empty
		{
		for count=1 to table.NumRecords
			{
			currentTime$=TableReadFieldStr(table, "Date Time", count); # get current record time
			currentName$=TableReadFieldStr(table, "Image", count); # get current record name

			date$=currentTime$.substr(0,8); # parse date for current record
			datetimeCurrent.SetDateYYYYMMDD(StrToNum(date$));
			time$=currentTime$.substr(9,8); # parse time for current record
			datetimeCurrent.SetTime(StrToNum(time$.substr(0,2)),StrToNum(time$.substr(3,2)),StrToNum(time$.substr(6,2)));
			tCurrent=DecimalTime(datetimeCurrent.GetHour(),datetimeCurrent.GetMin(),datetimeCurrent.GetSec()); # get decimal time for current record

			# check if day has rolled over
			# adjusts by 24*(difference in days)
			if(datetimeInput.GetDateYYYYMMDD() != datetimeCurrent.GetDateYYYYMMDD())
				tCurrent=tCurrent+24*(datetimeCurrent.GetDateYYYYMMDD()-datetimeInput.GetDateYYYYMMDD());

			if(currentName$ == inputname$) # if image already attached, do not create new record
				{
				TableWriteRecord(table, count, inputtime$, x, y, z, inputname$); # write to current record
				return 0;
				}
			if(tInput < tCurrent) # if input time is less than current record's time
				{
				ShiftRecords(count); # shift records forward 1 to make room for new record
				TableWriteRecord(table, count, inputtime$, x, y, z, inputname$); # write to newly freed record
				VectorAddPoint(GPSVector,x,y,z); # add blank vector point
				return 0;
				}
			if(tInput>tCurrent && count==table.NumRecords) # if has reached end of table
				{
				TableNewRecord(table, inputtime$, x, y, z, inputname$); # add new record at end of table
				VectorAddPoint(GPSVector,x,y,z); # add blank vector point
				return 0;
				}
			} # end for
		} # end if

	if (table.NumRecords==0) # if table empty
		{
		TableNewRecord(table, inputtime$, x, y, z, inputname$); # add new record
		VectorAddPoint(GPSVector,x,y,z); # add blank vector point
		}

	return 1;
	} # end proc NewRecord

############################
proc AttachImages() {

	local numeric listcount;
	local string inputtime$, inputname$; #image time and name

	for listcount=0 to imageListNameAttached.GetNumItems()-1 # for all images in image list box
		{
		inputname$ = imageListNameAttached.GetString(listcount); # grab input name
		inputtime$ = sprintf("%i %i:%i:%i",imageListTime[inputname$].GetDateYYYYMMDD(), imageListTime[inputname$].GetHour(),  imageListTime[inputname$].GetMin(),  imageListTime[inputname$].GetSec()); # grab input date and time

		NewRecord(inputtime$,imageListData[inputname$].Position.x, imageListData[inputname$].Position.y, imageListData[inputname$].Position.z, inputname$);
		}

	AttachPoints(); # attach records to points in vector
	VectorValidate(GPSVector);
	CloseVector(GPSVector);

	# enable Display Table icon button
	gpsdialog.GetCtrlbyID("displaytable").SetEnabled(1);

	} # end proc AttachImages


############################
proc WriteEXIF(){ 
# write to exif metadata

	class POINT3D coord; # point3d class to hold current and next record coordinates

	local string imagePathOriginal$, imageTime$;
	local numeric i=0;

	local numeric BackupFlag = PopupYesNo("Would you like to create backup copies of the images?");

	 for i=0 to imageListNameAttached.GetNumItems()-1
		{ # get values to write
		imagePathOriginal$=imageListNameAttached.GetString(i); # grab input name

		coord.x =imageListData[imagePathOriginal$].Position.x;
		coord.y =imageListData[imagePathOriginal$].Position.y;
		coord.z =imageListData[imagePathOriginal$].Position.z;

		if(BackupFlag){
			local string imagePath$ = sprintf( "%s/%s%s.%s", FileNameGetPath(imagePathOriginal$), FileNameGetName(imagePathOriginal$), "-SML",FileNameGetExt(imagePathOriginal$));
			CopyFile(imagePathOriginal$, imagePath$);
			exifhandle.Open(imagePath$); # open jpeg to write
			}
		else
		exifhandle.Open(imagePathOriginal$);

		# writeexif:
		# Set Long Reference: indicates if West or East longitude
		if (coord.x < 0)
			exifhandle.AddDatumStr("Exif.GPSInfo.GPSLongitudeRef","W");
		if (coord.x >= 0)
			exifhandle.AddDatumStr("Exif.GPSInfo.GPSLongitudeRef", "E");

		# Set Lat Reference: indicates if South or North Latitude
		if (coord.y < 0)
			exifhandle.AddDatumStr("Exif.GPSInfo.GPSLatitudeRef","S");
		if (coord.y >= 0)
			exifhandle.AddDatumStr("Exif.GPSInfo.GPSLatitudeRef", "N");

		# Set altitude used as reference altitude
		exifhandle.AddDatumStr("Exif.GPSInfo.GPSAltitudeRef", "0");

		# EXIF tags take decimal values as a fraction
		# Must convert altitude to a fraction string to write
		exifhandle.AddDatumStr("Exif.GPSInfo.GPSVersionID", "2 2 0 0");
		exifhandle.AddDatumStr("Exif.GPSInfo.GPSMapDatum", "WGS-84");

		local string Date$ = sprintf("%i:%i:%i", imageListTime[imagePathOriginal$].GetYear(), imageListTime[imagePathOriginal$].GetMonth(), imageListTime[imagePathOriginal$].GetDayOfMonth());
		local string Time$ = sprintf("%s/1 %s/1 %s/1", imageTime$.substr(9,2), imageTime$.substr(12,2), imageTime$.substr(15,2));
		exifhandle.AddDatumStr("Exif.GPSInfo.GPSDateStamp", Date$);
		exifhandle.AddDatumStr("Exif.GPSInfo.GPSTimeStamp", Time$);

		exifhandle.AddDatumStr("Exif.GPSInfo.GPSAltitude", sprintf("%i/1000",round(coord.z*1000)));

		coord.x = abs(coord.x);
		coord.y = abs(coord.y);
		numeric LatDeg = floor(coord.y);
		numeric LatMin = floor((coord.y - LatDeg)*60);
		numeric LatSec = floor( ((((coord.y - LatDeg)*60) - LatMin) *60)*10 );
		exifhandle.AddDatumStr("Exif.GPSInfo.GPSLatitude", sprintf("%i/1 %i/1 %i/10",LatDeg,LatMin,LatSec));

		numeric LonDeg = floor(coord.x);
		numeric LonMin = floor((coord.x - LonDeg)*60);
		numeric LonSec = floor( ((((coord.x - LonDeg)*60) - LonMin) *60)*10 );
		exifhandle.AddDatumStr("Exif.GPSInfo.GPSLongitude", sprintf("%i/1 %i/1 %i/10",LonDeg,LonMin,LonSec));

		exifhandle.Write();
		} # for

		local string report$;
		if(BackupFlag) 
 			report$=sprintf("%i image(s) saved. Images written to new files appended with -SML to preserve originals.", imageListNameAttached.GetNumItems());
		else
			report$=sprintf("%i image(s) saved.",imageListNameAttached.GetNumItems());

		PopupMessage(report$);
		gpsdialog.SetCtrlValueStr( "status", NumToStr(imageListNameAttached.GetNumItems()) + " images' EXIF tags saved.");
	} # end proc WriteEXIF

#############################
proc ApplyChanges() {
	AddGPS("");
	}

#############################
proc OpenScript() { # procedure called when starting
# set default values
	gpsdialog.SetCtrlValueStr("cameraoffset", "+000:00:00");
	gpsdialog.SetCtrlValueStr("timeoffset", "00:05:00");
	PopulateImageMenu();
	}
#############################
proc ExitScript() { # procedure called when exiting
	print("Closing...");
	CloseVector(GPSVector);

	# delete lock file
	local string lokfile$ = FileNameGetPath(filename$) + "/" + FileNameGetName(filename$) + "_rvc.lok";
	if(fexists(lokfile$)==1) # check for lok file and delete
		{
		DeleteFile(lokfile$);
		print("Deleted ",lokfile$);
		}

	gpsdialog.Close(0);
	}

#################################################
############## Main Program #####################
#################################################

clear();
$warnings 3;

# create string variable with XML specification for control dialog
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);
ret = gpsdialog.DoModal();

if ( ret == -1 ) {	# exit script if Cancel button on dialog is pressed
	Exit();
	}


Back Home ©MicroImages, Inc. 2013 Published in the United States of America
11th Floor - Sharp Tower, 206 South 13th Street, Lincoln NE 68508-2010   USA
Business & Sales: (402)477-9554  Support: (402)477-9562  Fax: (402)477-9559
Business info@microimages.com  Support support@microimages.com  Web webmaster@microimages.com

25 March 2009

page update: 26 May 11