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