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


GPSphoto.sml


#### GPSphoto.sml

### Requires version 2006:72 of the TNT products.

### Brett Colombe
### Microimages, Inc.
### Software Engineer
### 25 October 2006

#Coordinates now displayed next to image in image list and updated as new logs added.
#Changes to offset time now reflected in time displayed under log list
#Added View Image option
#Added 4 options for assigning different coordinates to images

#Changed 9/25/06: New EXIF class added to SML, changed script to use this new class
#		Now have ability to write to EXIF tags

#######################################################
# 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
# All log files must be MicroImages standard log format, 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
# Set the maximum allowed difference in distance between image and closest log point
# Choose if both time and distance constraints must be satisfied or if only 1 must be satisfied
# Set the offset time for camera if camera time is different than gps log time
########## Global Class Declarations ##############################

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 logStringList, coordStringList; # string lists containing gps log names and coordinates read in from logs

class STRING imageListTime[];
class STRING imageListTimeAttached[];
class STRING imageStringAttached[];

#####
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 TransParm transViewToRastMap;

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

numeric xcoords[];
numeric ycoords[];
numeric zcoords[];

numeric Assigned[];

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

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

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();
}
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$);
}#for
}
}#end proc DisplayExif

############################
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

############################
proc GetOptions(var numeric cameraoffset, var string method$, var numeric timeoffset, var numeric gpsoffset, var string constraints$) { # procedure to get the options set by user and return the values
	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)));
	gpsoffset=gpsdialog.GetCtrlValueNum("gpsoffset"); # get max allowed difference in gps coordinates distance
	constraints$=gpsdialog.GetCtrlValueStr("constraints"); # get option for checking constraints
}# end proc GetOptions()

############################
proc SearchCoords(string inputtime$, var numeric tLeft, var numeric tRight, var numeric tInput, var numeric leftCoord, var numeric rightCoord) {
# procedure to search through coordinates
# finds closest time less than input time (tLeft)
# finds closest time greater than input time (tRight)
# if both tLeft and tRight are greater than inputtime, then Image's time is less than all times in list
# if both tLeft and tRight are less than inputtime, then Image's time is greater than all times in list
# if both tLeft and tRight are equal to inputtime, then Image's time is equal to a time in the list
# if tLeft less than inputtime and tRight greater than inputtime, then can interpolate new coordinates for Image's time

class DATETIME datetimeLeft, datetimeRight, datetimeInput, datetimeCurrent;
local string date$, time$;
local numeric count = coordStringList.GetNumItems()-1;
local numeric i = 0;
local numeric tCurrent=0;

# tLeft, tRight, tCurrent, tInput decimal times for those records
# leftCoord, rightCoord record number of current closest left and right times
# leftCoord and rightCoord start at record 0
	leftCoord=0;
	rightCoord=0;

	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

	#tInput=tInput - cameraoffset; # apply camera time offset

	date$=coordStringList.GetString(leftCoord).substr(0,8); # parse date for left cord
	datetimeLeft.SetDateYYYYMMDD(StrToNum(date$));
	time$=coordStringList.GetString(leftCoord).substr(9,8); # parse time for left coord
	datetimeLeft.SetTime(StrToNum(time$.substr(0,2)),StrToNum(time$.substr(3,2)),StrToNum(time$.substr(6,2)));
	tLeft=DecimalTime(datetimeLeft.GetHour(),datetimeLeft.GetMin(),datetimeLeft.GetSec()); # get decimal time

	datetimeRight=datetimeLeft; # right date and time same as left for first record
	tRight=tLeft; # decimal times equal for first record
	# check if day has rolled over
	# adjusts by 24*(difference in days)
	if(datetimeInput.GetDateYYYYMMDD() != datetimeLeft.GetDateYYYYMMDD())
		tLeft=tLeft+24*(datetimeLeft.GetDateJulian()-datetimeInput.GetDateJulian());
	if(datetimeInput.GetDateYYYYMMDD() != datetimeRight.GetDateYYYYMMDD())
		tRight=tRight+24*(datetimeRight.GetDateJulian()-datetimeInput.GetDateJulian());

for i=0 to count # for all items in coordinate list
	{

	date$=coordStringList.GetString(i).substr(0,8); # parse date for current record
	datetimeCurrent.SetDateYYYYMMDD(StrToNum(date$));
	time$=coordStringList.GetString(i).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

	# check if day has rolled over
	# adjusts by 24*(difference in days)
	if(datetimeInput.GetDateYYYYMMDD() != datetimeCurrent.GetDateYYYYMMDD())
		tCurrent=tCurrent+24*(datetimeCurrent.GetDateJulian()-datetimeInput.GetDateJulian());
   #print(tInput, tCurrent, tRight);
	# check for new closest left
	if( tCurrent <= tInput )
		{
		leftCoord=i;
		tLeft=tCurrent;
		datetimeLeft=datetimeCurrent;
		}
	# check for new closest right
	if( tCurrent >= tInput && ( (tRight-tInput) > (tCurrent-tInput) || (tRight-tInput) < 0 ) )
		{
		rightCoord=i;
		tRight=tCurrent;
		datetimeRight=datetimeCurrent;
		}

	}# for

}# end proc SearchCoords

############################
proc ComputeCoordinates(numeric imageNum, numeric computedflag) {
#same as AttachImages, but doesn't use Table
# procedure to compute where images should be attached
# calls SearchCoords to determine closest leftCoord and RightCoord
class POINT3D leftXYZ, rightXYZ, newXYZ;

local string report$;
local numeric i, listcount, attached, scale=0; 
local numeric tLeft, tRight, tInput, leftCoord, rightCoord=0; #values returned by searchcoords

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

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

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

GetOptions(cameraoffset, method$, timeoffset, gpsoffset, constraints$); # returns: method$, constraints$ cameraoffset, timeoffset, gpsoffset
# timeoffset=max allowed difference in time
# gpsoffset=max allowed difference in distance

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
	{
	attached=0; # mark image as current unattached

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

	##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") 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") coord.y=coord.y*-1;

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

	xcoords[imageName$] = coord.x;
	ycoords[imageName$] = coord.y;
	zcoords[imageName$] = coord.z;

	Assigned[imageName$]=1;
	}

	##
	else if( (Assigned[inputname$]!=1 || IsNull(Assigned[inputname$])) || computedflag==1){
	Assigned[inputname$]=0;
	xcoords[inputname$]=0;
	ycoords[inputname$]=0;
	zcoords[inputname$]=0;

	SearchCoords( inputtime$, tLeft, tRight, tInput, leftCoord, rightCoord); # return leftCoord, rightCoord

	# get x,y,z of leftCoord
	leftXYZ.x=StrToNum(GetToken(coordStringList.GetString(leftCoord),",",2).slice(3));
	leftXYZ.y=StrToNum(GetToken(coordStringList.GetString(leftCoord),",",3).slice(3));
	leftXYZ.z=StrToNum(GetToken(coordStringList.GetString(leftCoord),",",4).slice(3));

	# get x, y, z of RightCoord
	rightXYZ.x=StrToNum(GetToken(coordStringList.GetString(rightCoord),",",2).slice(3));
	rightXYZ.y=StrToNum(GetToken(coordStringList.GetString(rightCoord),",",3).slice(3));
	rightXYZ.z=StrToNum(GetToken(coordStringList.GetString(rightCoord),",",4).slice(3));

	# if(both times greater than input) attach to right cord
	# check if falls within timeoffset
	if(tLeft > tInput && abs(tRight-tInput) <= timeoffset)
		{
		#NewRecord(inputtime$, rightXYZ.x, rightXYZ.y, rightXYZ.z, inputname$);
		xcoords[inputname$]=rightXYZ.x;
		ycoords[inputname$]=rightXYZ.y;
		zcoords[inputname$]=rightXYZ.z;
		attached=1;
		}
	# if(both times less than input) attach to left cord
	# check if falls within timeoffset
	if(tRight < tInput && abs(tInput-tLeft) <= timeoffset)
		{
		#NewRecord(inputtime$, leftXYZ.x, leftXYZ.y, leftXYZ.z, inputname$);
		xcoords[inputname$]=leftXYZ.x;
		ycoords[inputname$]=leftXYZ.y;
		zcoords[inputname$]=leftXYZ.z;
		attached=1;
		}
	# if(both times equal input) attach to either cord
	if(tRight==tInput && tLeft==tInput)
		{
		#NewRecord(inputtime$, rightXYZ.x, rightXYZ.y, rightXYZ.z, inputname$);
		xcoords[inputname$]=rightXYZ.x;
		ycoords[inputname$]=rightXYZ.y;
		zcoords[inputname$]=rightXYZ.z;
		attached=1;
		}
	# if(tleft < tInput && tRight > tInput) interpolate
	if(tLeft < tInput && tRight > tInput)
		{
		scale=( (tInput-tLeft) / (tRight-tLeft) ); 
		# linear interpolate new coordinates
		# e.g. if Current x is 10, Previous x is 5, inputtime gives a scale of .2
		# then new x value is: 5 + ( (10-5) * .2) = 5 + (5 * .2) = 5 + 1 = 6
		newXYZ.x=leftXYZ.x + ((rightXYZ.x-leftXYZ.x) * scale); 
		newXYZ.y=leftXYZ.y + ((rightXYZ.y-leftXYZ.y) * scale);
		newXYZ.z=leftXYZ.z + ((rightXYZ.z-leftXYZ.z) * scale);

		# print(newXYZ.x, newXYZ.y, newXYZ.z);

		# check if distance and time fall within constraints
		local numeric ORcheck = (constraints$=="OR" && ( (abs(tInput-tLeft) <= timeoffset || abs(tRight-tInput) <= timeoffset ) || ( (abs(newXYZ.x-leftXYZ.x)^2 + abs(newXYZ.y-leftXYZ.y)^2)^.5 <= gpsoffset || (abs(newXYZ.x-rightXYZ.x)^2 + abs(newXYZ.y-rightXYZ.y)^2)^.5 <= gpsoffset)));
		local numeric ANDcheck = (constraints$=="AND" && ( (abs(tInput-tLeft) <= timeoffset || abs(tRight-tInput) <= timeoffset ) && ( (abs(newXYZ.x-leftXYZ.x)^2 + abs(newXYZ.y-leftXYZ.y)^2)^.5 <= gpsoffset || (abs(newXYZ.x-rightXYZ.x)^2 + abs(newXYZ.y-rightXYZ.y)^2)^.5 <= gpsoffset))); 

		if( ORcheck==1 || ANDcheck==1 )#if valid
			{
			if(method$=="Interpolate")
				{
				#NewRecord(inputtime$, newXYZ.x, newXYZ.y, newXYZ.z, inputname$);
				xcoords[inputname$]=newXYZ.x;
				ycoords[inputname$]=newXYZ.y;
				zcoords[inputname$]=newXYZ.z;
				}
			if(method$=="Closest")
				{
				if( tInput-tLeft <= tRight-tInput ) #closer to left
					{
					#NewRecord(inputtime$, leftXYZ.x, leftXYZ.y, leftXYZ.z, inputname$);
					xcoords[inputname$]=leftXYZ.x;
					ycoords[inputname$]=leftXYZ.y;
					zcoords[inputname$]=leftXYZ.z;
					}
				if( tInput-tLeft > tRight-tInput ) #closer to right
					{
					#NewRecord(inputtime$, rightXYZ.x, rightXYZ.y, rightXYZ.z, inputname$);
					xcoords[inputname$]=rightXYZ.x;
					ycoords[inputname$]=rightXYZ.y;
					zcoords[inputname$]=rightXYZ.z;
					}
				}# closest
			attached=1;
			}# if within gpsoffset
		}# if falls between times

	if(attached==1)
		{
			imageListNameAttached.AddToEnd(inputname$);
			imageListTimeAttached[inputname$]=inputtime$;
		}

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

	}#if Assigned 
	else
		{
		imageListNameAttached.AddToEnd(inputname$);
		imageListTimeAttached[inputname$]=inputtime$;
		}
	}# for

if(report$ != "") # report$ contains images not attached
	{
	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

	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
	local string EXIFDateTime$=sprintf("%s%s%s %s",exifDateTime$.substr(0,4),exifDateTime$.substr(5,2),exifDateTime$.substr(8,2),exifDateTime$.substr(11,8));

	# if no value for key "Exif.Image.DateTime" then check key "Exif.Photo,DateTimeOriginal"
	if (GetToken(EXIFDateTime$, ",", 1) == " " || GetToken(EXIFDateTime$, "," ,1) == "")
		{
		exifDateTime$ = exifhandle.GetDatumStr("Exif.Photo.DateTimeOriginal");
		EXIFDateTime$=sprintf("%s%s%s %s",exifDateTime$.substr(0,4),exifDateTime$.substr(5,2),exifDateTime$.substr(8,2),exifDateTime$.substr(11,8));
		}
	# if no value for either key, return 0 as unsuccessful
	if (GetToken(EXIFDateTime$, ",", 1) == " " || GetToken(EXIFDateTime$, "," ,1) == "")
		return 0;
	gpsdialog.SetCtrlValueStr( "status", "Added: " + FileNameGetName(filename$) + "." + FileNameGetExt(filename$) + " - " + EXIFDateTime$  );
	print("Added: ",EXIFDateTime$, filename$);

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

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

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

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

		for i=0 to imageListName.GetNumItems()-1{
			imageName$=imageListName[i];
			if(Assigned[imageName$]==1)#assigned coordinates
				{
				imageString$ = sprintf("%s.%s   %s  x: %.7f  y: %.7f  z: %.7f ", FileNameGetName(imageName$), FileNameGetExt(imageName$), imageListTime[imageName$], xcoords[imageName$], ycoords[imageName$], zcoords[imageName$]);
				imageListNameAttached.AddToEnd(imageName$);
				imageListNameAttached.RemoveDuplicates();
				imageListTimeAttached[imageName$]=imageListTime[imageName$];
				imageStringAttached[imageName$]=imageString$;
				}
			else
				{
					if(xcoords[imageName$]==0 && ycoords[imageName$]==0 && zcoords[imageName$]==0)#computed coordinates
						imageString$ = sprintf("*** %s.%s   %s  x: %.7f  y: %.7f  z: %.7f ", FileNameGetName(imageName$), FileNameGetExt(imageName$), imageListTime[imageName$], xcoords[imageName$], ycoords[imageName$], zcoords[imageName$]);
					else{
						imageString$ = sprintf("%s.%s   %s  x: %.7f  y: %.7f  z: %.7f ", FileNameGetName(imageName$), FileNameGetExt(imageName$), imageListTime[imageName$], xcoords[imageName$], ycoords[imageName$], zcoords[imageName$]);
						imageStringAttached[imageName$]=imageString$;
						}
				}
			imagelist.AddItem(imageString$);
	     	}#for

	}#end proc UpdateImages()

##################################
proc ParseLogNMEA(var numeric records, class STRINGLIST slist, string logname$) {

class DATETIME datetime;
class TIMEINTERVAL offset;

local string line$, date$, timeToken$, time$, type$;
local numeric x,y,z=0;
local numeric xdeg, ydeg, zdge, xmin, ymin, zmin;
records=0;

local numeric cameraoffset, timeoffset, gpsoffset;
local string method$, constraints$;
GetOptions(cameraoffset, method$, timeoffset, gpsoffset, constraints$); # returns: method$, constraints$ cameraoffset, timeoffset, gpsoffset

	class FILE logfile;
	logfile = fopen(logname$); # open logfile

	date$ = FileNameGetName(logname$).substr(2,8);

	while (!feof(logfile)) # while not reached end of file
		{
		line$ = fgetline$(logfile); # get next line in log file
		type$ = GetToken(line$,",",1); # parse first entry of line

		if (type$=="$GPGGA") #fix data
			{
			records++;

			datetime.SetDateYYYYMMDD(StrToNum(date$));
			datetime.ConvertToLocal();
			datetime.SetDateYYYYMMDD(StrToNum(date$));
			timeToken$ = GetToken(line$,",",2); # parse second entry of line (time)
			datetime.SetTime(StrToNum(timeToken$.substr(0,2)), StrToNum(timeToken$.substr(2,2)), StrToNum(timeToken$.substr(4,2)));
			offset.Value = cameraoffset*3600;
			datetime=datetime+offset;
			datetime.ConvertToLocal();

			y = StrToNum(GetToken(line$,",",3)); # parse third entry of line (XPos)
			ydeg = floor(y/100);
			ymin = (y - (ydeg * 100))/60;
			y = ydeg + ymin;
			if(GetToken(line$, ",", 4) == "S") y=y*-1;

			x = StrToNum(GetToken(line$,",",5));
			xdeg = floor(x/100);
			xmin = (x - (xdeg * 100))/60;
			x = xdeg + xmin;
			if(GetToken(line$, ",", 6) == "W") x=x*-1;

			z = StrToNum(GetToken(line$,",",10));

			line$ = sprintf("%04i%02i%02i %02i:%02i:%02i , X: %.8f , Y: %.8f , Z: %.8f", datetime.GetYear(), datetime.GetMonth(), datetime.GetDayOfMonth(), datetime.GetHour(), datetime.GetMin(), datetime.GetSec(), x, y, z);
			slist.AddToEnd(line$); # add date, time, x, y, z to string list
			}# if
		}# while
	fclose(logfile); # close logfile

}

##################################
proc ParseLogMI(var numeric records, class STRINGLIST slist, string logname$) {
# Procedure to open log file "logname$" and parse the results
# adds string containing Time(YYYYMMDD HH:MM:SS), X, Y, Z to stringlist
# format of standard MI log file:
# 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
local string line$, date$, timeToken$, time$;
local numeric x,y,z=0;
local numeric i, format=0;
records=0;

	class FILE logfile;
	logfile = fopen(logname$); # open logfile
	while(!feof(logfile) && i<=5)
		{
		i++;
		line$ = fgetline$(logfile);
		if(GetToken(line$,",",1)=="#date(YYYYMMDD)" && GetToken(line$,",",2)=="time(HHMMSS)" && GetToken(line$,",",3)=="XPos(deg)" && GetToken(line$,",",4)=="YPos(deg)" && GetToken(line$,",",5)=="Elev(m)")
			{
			format=1;
			i=6;
			}
		}

	if(format==1)
	{
	while (!feof(logfile)) # while not reached end of file
		{
		line$ = fgetline$(logfile); # get next line in log file
		date$ = GetToken(line$,",",1); # parse first entry of line (date)
		if (StrToNum(date$) > 1)
			{
			records++;
			timeToken$ = GetToken(line$,",",2); # parse second entry of line (time)
			time$ = sprintf("%s:%s:%s", timeToken$.substr(0,2), timeToken$.substr(2,2), timeToken$.substr(4,2)); # change HHMMSS to HH:MM:SS
			x = StrToNum(GetToken(line$,",",3)); # parse third entry of line (XPos)
			y = StrToNum(GetToken(line$,",",4)); # parse fourth entry of line (YPos)
			z = StrToNum(GetToken(line$,",",5)); # parse fifth entry of line (Elev)
			line$ = sprintf("%s %s , X: %.6f , Y: %.6f , Z: %.6f", date$, time$, x, y, z);
			slist.AddToEnd(line$); # add date, time, x, y, z to string list
			}# if
		}# while
	}
	fclose(logfile); # close logfile
}# end proc ParseLog

##################################
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(); # clear coord string list
			logStringList.Clear(); # clear log string list
			gpsdialog.SetCtrlValueStr( "status", "Removed All Logs");
			}
	} # end proc RemoveAll()

#############################
proc AddGPS(string logname$, var numeric records) {
# 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;

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

	for count=0 to logStringList.GetNumItems()-1 # for all logs in list
		{
		if(FileNameGetExt(logStringList[count])=="gps")
			ParseLogMI(records, coordStringList, logStringList[count]); # add coordinates to coordStringList
		if(FileNameGetExt(logStringList[count])=="log")
			ParseLogNMEA(records, coordStringList, logStringList[count]);
		}

	if(logname$!="")
	{
	logfile=fopen(logname$);
		if(FileNameGetExt(logname$)=="gps")
			ParseLogMI(records, coordStringList, logname$); # add coordinates to coordStringList
		if(FileNameGetExt(logname$)=="log")
			ParseLogNMEA(records, coordStringList,logname$);
	}

	coordStringList.Sort(); # sort coordinates by time

	for count=0 to coordStringList.GetNumItems()-1
		coordlist.AddItem(coordStringList[count]); # add all coordinates in coordStringList to coord list box

	UpdateImages(-1, 0);
  	} # 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
	local string coord$ = coordStringList[index];
	if(coord$ != "") gpsdialog.SetCtrlValueStr( "status", "Removed: " + coord$);
	coordStringList.Remove(index); # remove from stringlist
	} # 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
			{
			AddGPS(logpath$, records);
			if(records>0)
			{
			list.AddItem(logpath$);
			logStringList.AddToEnd(logpath$);
			}
			if(records==0)# if not valid
				report$=report$ + sprintf("Could not add '%s.%s'\n",FileNameGetName(logpath$), FileNameGetExt(logpath$));
			}# if
		}# 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
			{
			AddGPS(logpath$, records);
			if(records>0)
			{
			list.AddItem(logpath$);
			logStringList.AddToEnd(logpath$);
			}
			if(records==0)# if not valid
				report$=report$ + sprintf("Could not add '%s.%s'\n",FileNameGetName(logpath$), FileNameGetExt(logpath$));
			}# if
		}# 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
		{
		AddGPS(logname$, records);
		if(records>0)
			{
			list.AddItem(logpath$);
			logStringList.AddToEnd(logpath$);
			gpsdialog.SetCtrlValueStr( "status", "Added: " + FileNameGetName(logname$) + "." + FileNameGetExt(logname$));
			}
		if(records==0)# 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$));
			}
		}# 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$ = logStringList[index];
	gpsdialog.SetCtrlValueStr( "status", "Removed: " + FileNameGetName(logname$) + "." + FileNameGetExt(logname$));
	list.DeleteItemIndex(index); # remove from listbox
	logStringList.Remove(index); # remove from string list
	AddGPS("",records); # 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
				xcoords[imagePath$]=0; ycoords[imagePath$]=0; zcoords[imagePath$]=0;
				}
			if(hasEXIF == 0) # has no EXIF
				{
				report$=report$ + sprintf("Could not add image '%s.%s'.\n",FileNameGetName(filenames[count]),FileNameGetExt(filenames[count]));
				}
			}# if
	}# 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);
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);
		}
	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 proc AddImage

###########################
#############################
#Procedures to bring up view window to select coords:

proc OnToolSet () {

       clear();
       ptrastmap = TransPoint2D(myPt.Point,transViewToRastMap);

       local string coords$ = sprintf("x = %5.5f, y = %5.5f",ptrastmap.x,ptrastmap.y);
       ViewSetMessage(view,coords$);
}

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
		xcoords[imageName$] = ptrastmap.x;
		ycoords[imageName$] = ptrastmap.y;
		zcoords[imageName$] = 0;

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

proc OnClose() {
       DialogClose(pcwin);
       DestroyWidget(pcwin);
       CloseRaster(rasterIn);
}


proc OnDestroy() {
       Exit();
}

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);

rasterInLayer = GroupQuickAddRasterVar(gp,rasterIn);

ViewRedrawFull(view);

class MAPPROJ rastmapproj = rasterInLayer.Projection;
rastmapproj.SetSystemLatLon();

transViewToRastMap = ViewGetTransMapToView(view,rastmapproj,1);

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

#############################
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;

	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
	coord.x=StrToNum(GetToken(coordStringList.GetString(coordindex),",",2).slice(3));
	coord.y=StrToNum(GetToken(coordStringList.GetString(coordindex),",",3).slice(3));
	coord.z=StrToNum(GetToken(coordStringList.GetString(coordindex),",",4).slice(3));

	xcoords[imageName$] = coord.x;
	ycoords[imageName$] = coord.y;
	zcoords[imageName$] = coord.z;

	Assigned[imageName$]=1;

	UpdateImages(imageindex,0);
}#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(imageStringAttached[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);

	coord.x=xcoords[transferName$];
	coord.y=ycoords[transferName$];
	coord.z=zcoords[transferName$];

	xcoords[imageName$] = coord.x;
	ycoords[imageName$] = coord.y;
	zcoords[imageName$] = coord.z;

	Assigned[imageName$]=1;

	UpdateImages(imageindex,0);
}#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;
	xcoords[imageName$] = xcoord;
	ycoords[imageName$] = ycoord;
	zcoords[imageName$] = latlondialog.GetCtrlValueNum("zcoord");

	Assigned[imageName$]=1;

	UpdateImages(imageindex,0);
}#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") 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") coord.y=coord.y*-1;

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

	xcoords[imageName$] = coord.x;
	ycoords[imageName$] = coord.y;
	zcoords[imageName$] = coord.z;

	Assigned[imageName$]=1;
	UpdateImages(index, 0);
	}
	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");
}

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);
	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();
}

#############################
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$]);
		xcoords[imageName$]=0; ycoords[imageName$]=0; zcoords[imageName$]=0;
		Assigned[imageName$]=0;
		imageListTime[imageName$]="";
		imageListTimeAttached[imageName$]="";
		imageStringAttached[imageName$]="";
		imageListName.Remove(index); # remove from stringlist
		local numeric i;
		for i=0 to imageListNameAttached.GetNumItems()-1{
			if(imageListNameAttached[i]==imageName$) imageListNameAttached.Remove(i);
			}

	} # 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
			imageListTime.Clear(); # clear string list
			imageListName.Clear(); # clear string list
			xcoords.Clear(); ycoords.Clear(); zcoords.Clear();
			Assigned.Clear();
			imageListTimeAttached.Clear();
			imageStringAttached.Clear();
			imageListNameAttached.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
	##numberOfElements=NumVectorPoints(GPSVector);
	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$; 
	}# 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;
			}
		}# for
	}# 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() {

#ComputeCoordinates();

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$ = imageListTimeAttached[inputname$]; # grab input date and time

	NewRecord(inputtime$,xcoords[inputname$],ycoords[inputname$],zcoords[inputname$], inputname$);
	}

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

} # end proc AttachImages


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

#ComputeCoordinates();

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
	imageTime$=imageListTimeAttached[imagePathOriginal$];

	coord.x=xcoords[imagePathOriginal$];
	coord.y=ycoords[imagePathOriginal$];
	coord.z=zcoords[imagePathOriginal$];

	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("%s:%s:%s", imageTime$.substr(0,4), imageTime$.substr(4,2), imageTime$.substr(6,2));
	#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() {
	local numeric records;
	AddGPS("", records);
}

#############################
proc OpenScript() { # procedure called when starting
# set default values
gpsdialog.SetCtrlValueStr("cameraoffset", "+000:00:00");
gpsdialog.SetCtrlValueStr("timeoffset", "00:05:00");
gpsdialog.SetCtrlValueNum("gpsoffset", .5);
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