#### GPSphoto2.sml ### Requires version 2006:73 of the TNT products ### Brett Colombe ### MicroImages, Inc. ### Software Engineer ### 19 January 2007 # 2.0 - Major changes to use new GPSDBASE class ####################################################### # Script inputs: GPS log file(s) and JPEG images with EXIF headers. # # This Script gets from the EXIF header the date and time each picture was taken # and compares them with the GPS log to assign geographic coordinates for the image. # User chooses whether to interpolate coordinates based on image time or use closest GPS coordinates. # Each image location is added as a point to the user-selected vector object. # # As images are added, the EXIF fields for Date and Time are read and displayed in the listbox. # As logs are added, each log file is parsed and the Date, Time, and Lat/Long/Elev are displayed in the listbox. # User sets the constraints options and selects his output vector # Coordinates for the images are calculated by: # Search through the list of coordinates from the gps log files for the two closest log points # Using the time of the image and these two coordinates, new coordinates are then interpolated for the image # Constraints are checked, then Date, Time, Lat, Lon, Elev and Path of image are added to table, with records sorted by time # Table is stored in Point Database for the vector, with each record attached to a vector point # Vector may be displayed in a Layout group for hyperlinking the images by selecting File by Attribute and choosing the image Path in the table ################################## # Images Tab: # User has option to add a directory of images or single images, images are then displayed in a listbox # Save As - allows user to select the vector outputed by script # Compute Image Coordinates - calculates the coordinates of the images and adds these to the table # Display GPS Table - to display the table # Change Directory of Images - if user moves images to a different directory, allows user to select this other directory, # searches for image names in the new directory matching those in the table, then these images' paths are changed to the new directory ################################## # GPS Log Tab: # User has option to add a directory of log files or single log files # Log file path/name added to first list box # Total list of coordinates added to second list box # Set the offset time for camera if camera time is different than gps log time # All log files must be MicroImages standard log format or NMEA standard, see script below for details ################################## # Options Tab: # Choose to interpolate new coordinates or simply select closest log point's coordinates # Set the maximum allowed difference in time between image and closest log point ########## Global Class Declarations ############################## class GPSDBASE gpsdbase; ## NEW CLASS class XMLDOC dlgdoc, logdoc, latlondoc, transferdoc, exifdoc; # class instance for the XML document class XMLNODE gpsdlgnode, lognode, latlonnode, transfernode, exifnode; # class instance for the node in the XML class GUI_DLG gpsdialog, logdialog, latlondialog, transferdialog, exifdialog; # class instance for the GUI dialog class DATABASE dbase; # class instance for database class DBTABLEINFO table; # class instance for GPS table class STRINGLIST imageListName; # string lists containing listbox image name class STRINGLIST imageListNameAttached; # string lists containing image name for found coordinates class STRINGLIST coordStringList; class STRING imageStringList[]; class DATETIME imageListTime[]; class GPSDATA imageListData[]; numeric Assigned[]; ##### class GRE_GROUP gp; # spatial group for display. class GRE_VIEW view; # view that displays the group. class XmForm pcwin; # parent form for dialog window. class PointTool myPt; # class for tool used to return a 3D point. class RASTER rasterIn; class GRE_LAYER_RASTER rasterInLayer; class POINT2D ptrastmap; # point location in raster map coordinates. class Point2D ptGeog; # point location in WGS84 / Geographic coordinates. class SR_COORDREFSYS coordrefsysRast; class SR_COORDREFSYS coordrefsysGeog; class TransParm transViewToRastMap; class TransParm transRastMapToGeog; class MAPPROJ rastmapproj; # Coordinate system / projection parameters. ##### class SR_COORDREFSYS crs; # coordinate reference system class crs.Assign("Geographic2D_WGS84_Deg"); # set crs for vector class EXIF exifhandle; # declare EXIF class handle for getting/writing exif values string xml$; # string containing the XML text with the dialog specification numeric err; # value returned by the class method that reads and parses # the XML text numeric ret; # value returned by the class method that opens dialog string filename$, obj$, desc$; # database and project file name string IMAGEtablename$="Images"; # image table name string tabledesc$="Table created by GPS.sml script to hold GPS coordinates and attached images"; # gps table description vector GPSVector; # gps vector object ####################### Procedures ############################### ############################################### ########## Procedures for GPS Logs ############ ############################################### proc DisplayEXIF() { # Procedure to create dialog to display EXIF information about a photo local numeric errexif; local string xmlexif$; ### Create string variable with XML specification of dialog xmlexif$ = ' '; ### parse XML text for the dialog into memory; ### return an error code (number < 0 ) if there are syntax errorsi errexif = exifdoc.Parse(xmlexif$); if ( errexif < 0 ) { PopupError( errexif ); # Popup an error dialog. "Details" button shows syntax errors. Exit(); } # get the dialog element from the parsed XML document and # show error message if the dialog element can't be found exifnode = exifdoc.GetElementByID("exifdlg"); if ( exifnode == 0 ) { PopupMessage("Could not find dialog node in XML document"); Exit(); } # Set the XML dialog element as the source for the GUI_DLG class instance # we are using for the dialog window. exifdialog.SetXMLNode(exifnode); ret = exifdialog.DoModal(); } # end DisplayEXIF ############################################# proc PopulateEXIF() { # Get detailed information about EXIF tags and add to dialog local numeric index; # selected item index class GUI_CTRL_LISTBOX list, exifbox; class STRINGLIST keyStrings; exifbox = exifdialog.GetCtrlByID("exifbox"); exifbox.DeleteAllItems(); list = gpsdialog.GetCtrlByID("imagebox"); # get control for list box index = list.GetSelectedItemIndex(); # get selected item index local string image$ = imageListName.GetString(index); # grab input name if(image$=="") { PopupMessage("Please Select an Image."); } else { local string output$=""; output$+=sprintf("EXIF Header Information for image: %s\n -----------------------------\n"); # GetEXIFTags takes filename of image as input # returns hash with key names and values exifhandle.Open(image$); # Get key list from hash, returns as string list keyStrings = exifhandle.GetKeyList(); local numeric value,i; local string value$; # print all keys and values to text file for i=0 to keyStrings.GetNumItems()-1 { output$= sprintf("Key: %i\t%s\nValue: %s\n", i, keyStrings[i], exifhandle.GetDatumStr(keyStrings[i])); # Exif.Photo.Flash # bit0: 0 flash didn't fire, 1 flash fired # bit12: 00 no strobe return detection function, 01 reserved, 10 strobe return light not detected, 11 strobe return ligth deteced # bit34: 00 unknown, 01 Compulsory flash firing, 10 Compulsory flash suppression, 11 auto mode # bit5: 0 flash function present, 1 no flash function # bit6: 0 no red eye reduction mode or unknown, 1 red eye reduction supported if (keyStrings[i]=="Exif.Photo.Flash") { value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.Flash")); if (value>=64) # red eye mode { value=value-64; output$+= "\tRed Eye Mode - Red eye reduction supported"; } else output$+= "\tRed Eye Mode - No red eye reduction mode or unknown"; if(value>=32) # flash function { value=value-32; output$+= "\tFlash Function - No flash function"; } else output$+= "\tFlash Function - Flash function present"; if (value>=16) # flash mode { value=value-16; if (value>=8) { value=value-8; output$+= "\tFlash Mode - Auto mode\n"; } else output$+= "\tFlash Mode - Compulsory flash suprression\n"; } else { if (value>=8) { value=value-8; output$+="\tFlash Mode - Compulsory flash firing"; } else output$+="\tFlash Mode - Unknown mode"; } if (value>=4) # flash return { value=value-4; if (value>=2) { value=value-2; output$+= "\t Flash Return - Strobe return light detected"; } else output$+= "\tFlash Return - Strobe return light not detected"; } else { if (value>=2) { value=value-2; output$+= "\tFlash Return - Reserved"; } else output$+= "\tFlash Return - No strobe return detection function"; } if (value>=1) # flash fired output$+= "\tFlash Fired - Flash did fire\n"; else output$+= "\tFlash Fired - Flash did not fire\n"; } # end Exif.Photo.Flash # Exif.Photo.ExifVersion if(keyStrings[i]=="Exif.Photo.ExifVersion") { value$=exifhandle.GetDatumStr("Exif.Photo.ExifVersion"); output$+= sprintf("\tExif Version: %i%i.%i%i\n", StrToNum(GetToken(value$, " ",1))-48, StrToNum(GetToken(value$, " ",2))-48, StrToNum(GetToken(value$, " ",3))-48, StrToNum(GetToken(value$, " ",4))-48); } # Exif.Photo.FlashpixVersion if(keyStrings[i]=="Exif.Photo.FlashpixVersion") { value$=exifhandle.GetDatumStr("Exif.Photo.FlashpixVersion"); output$+= sprintf("\tFlashpix Version: %i%i.%i%i\n", StrToNum(GetToken(value$, " ",1))-48, StrToNum(GetToken(value$, " ",2))-48, StrToNum(GetToken(value$, " ",3))-48, StrToNum(GetToken(value$, " ",4))-48); } # Exif.Iop.InteroperabilityVersion if(keyStrings[i]=="Exif.Iop.InteroperabilityVersion") { value$=exifhandle.GetDatumStr("Exif.Iop.InteroperabilityVersion"); output$+= sprintf("\tInteroperability Version: %i%i.%i%i\n", StrToNum(GetToken(value$, " ",1))-48, StrToNum(GetToken(value$, " ",2))-48, StrToNum(GetToken(value$, " ",3))-48, StrToNum(GetToken(value$, " ",4))-48); } # Exif.Image.Orientation if(keyStrings[i]=="Exif.Image.Orientation" || keyStrings[i]=="Exif.Thumbnail.Orientation") { value=StrToNum(exifhandle.GetDatumStr("Exif.Image.Orientation")); switch(value) { case 1: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","top","left-hand side"); break; case 2: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","top","right-hand side"); break; case 3: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","bottom","right-hand side"); break; case 4: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","bottom","left-hand side"); break; case 5: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","left-hand side","top"); break; case 6: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","right-hand side","top"); break; case 7: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","right-hand side","bottom"); break; case 8: output$+= sprintf("\tOrientation: The 0th row is at the visual %s of the image, and the 0th column is the visual %s.\n","left-hand side","bottom"); break; } } # Exif.Image.ResolutionUnit if(keyStrings[i]=="Exif.Image.ResolutionUnit" || keyStrings[i]=="Exif.Thumbnail.ResolutionUnit" || keyStrings[i]=="Exif.Photo.FocalPlaneResolutionUnit") { value=StrToNum(exifhandle.GetDatumStr("Exif.Image.ResolutionUnit")); switch(value) { case 2: output$+= sprintf("\tUnit for Resolution: Inches\n"); break; case 3: output$+= sprintf("\tUnit for Resolution: Centimeters\n"); break; } } # Exif.Image.YCbCrPositioning if(keyStrings[i]=="Exif.Image.YCbCrPositioning" || keyStrings[i]=="Exif.Thumbnail.YCbCrPositioning") { value=StrToNum(exifhandle.GetDatumStr("Exif.Image.YCbCrPositioning")); switch(value) { case 1: output$+= sprintf("\tPosition of Chrominance components in relation to Luminance components: centered.\n"); break; case 2: output$+= sprintf("\tPosition of Chrominance components in relation to Luminance components: co-sited.\n"); break; } } # Exif.Image.XResolution / YResolution if(keyStrings[i]=="Exif.Image.XResolution" || keyStrings[i]=="Exif.Image.YResolution" || keyStrings[i]=="Exif.Photo.FocalPlaneXResolution" || keyStrings[i]=="Exif.Photo.FocalPlaneYResolution" || keyStrings[i]=="Exif.Thumbnail.XResolution" || keyStrings[i]=="Exif.Thumbnail.YResolution") output$+= sprintf("\tNumber of pixles per resolution unit.\n"); # Exif.Iop.InteroperabilityIndex if(keyStrings[i]=="Exif.Iop.InteroperabilityIndex") { value$=exifhandle.GetDatumStr("Exif.Iop.InteroperabilityIndex"); switch(value$) { case "R98": output$+= sprintf("\tIndicates a file conforming to R98 file specification of Recommended Exif Interoperability Rules (ExifR98) or to DCF basic file stipulated by Design Rule for Camera File System.\n"); break; case "THM": output$+= sprintf("\tIndicates a file conforming to DCF thumbnail file stipulated by Design rule for Camera File System.\n"); break; } } # Exif.Photo.ColorSpace if (keyStrings[i]=="Exif.Photo.ColorSpace") { value$=exifhandle.GetDatumStr("Exif.Photo.ColorSpace"); if (value$=="1") output$+= sprintf("\tsRGB (=1) is used to define the color space based on the PC monitor conditions and environment.\n"); else output$+= sprintf("\tUncalibrated.\n"); } # Exif.Photo.ExposureMode if(keyStrings[i]=="Exif.Photo.ExposureMode") { value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.ExposureMode")); switch(value) { case 0: output$+= sprintf("\tAuto exposure.\n"); break; case 1: output$+= sprintf("\tManual exposure.\n"); break; case 2: output$+= sprintf("\tAuto bracket.\n"); break; } } # Exif.Photo.FileSource if(keyStrings[i]=="Exif.Photo.FileSource") { value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.FileSource")); if(value==3) output$+= sprintf("\tImage recorded on a DSC.\n"); } # Exif.Photo.SceneType if(keyStrings[i]=="Exif.Photo.SceneType") { value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.SceneType")); if(value==1) output$+= sprintf("\tA directly photographed image.\n"); } # Exif.Photo.ComponentsConfiguration if(keyStrings[i]=="Exif.Photo.ComponentsConfiguration") { value$=exifhandle.GetDatumStr("Exif.Photo.ComponentsConfiguration"); local string line$=""; local numeric k; for k=1 to 4 { value=StrToNum(GetToken(value$," ",k)); switch(value) { case 0: line$=line$ + sprintf("%i=%s ",value ,"does not exist"); break; case 1: line$=line$ + sprintf("%i=%s ",value ,"Y"); break; case 2: line$=line$ + sprintf("%i=%s ",value ,"Cb"); break; case 3: line$=line$ + sprintf("%i=%s ",value ,"Cr"); break; case 4: line$=line$ + sprintf("%i=%s ",value ,"R"); break; case 5: line$=line$ + sprintf("%i=%s ",value ,"G"); break; case 6: line$=line$ + sprintf("%i=%s ",value ,"B"); break; } } output$+= sprintf("\tChannels of each component: %s.\n",line$); } # Exif.Photo.MeteringMode if (keyStrings[i]=="Exif.Photo.MeteringMode") { value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.MeteringMode")); switch(value) { case 0: output$+= sprintf("\tMetering Mode: %s.\n","Unknown"); break; case 1: output$+= sprintf("\tMetering Mode: %s.\n","Average"); break; case 2: output$+= sprintf("\tMetering Mode: %s.\n","Center Weighted Average"); break; case 3: output$+= sprintf("\tMetering Mode: %s.\n","Spot"); break; case 4: output$+= sprintf("\tMetering Mode: %s.\n","Multi Spot"); break; case 5: output$+= sprintf("\tMetering Mode: %s.\n","Pattern"); break; case 6: output$+= sprintf("\tMetering Mode: %s.\n","Partial"); break; case 255: output$+= sprintf("\tMetering Mode: %s.\n","Other"); break; } } # Exif.Photo.SceneCaptureType if(keyStrings[i]=="Exif.Photo.SceneCaptureType") { value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.SceneCaptureType")); switch(value) { case 0: output$+= sprintf("\tScene Type: %s.\n","Standard"); break; case 1: output$+= sprintf("\tScene Type: %s.\n","Landscape"); break; case 2: output$+= sprintf("\tScene Type: %s.\n","Portrait"); break; case 3: output$+= sprintf("\tScene Type: %s.\n","Night Scene"); break; } } # Exif.Photo.SensingMethod if(keyStrings[i]=="Exif.Photo.SensingMethod") { value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.SensingMethod")); switch(value) { case 1: output$+= sprintf("\tImage Sensor Type: %s.\n","Not defined"); break; case 2: output$+= sprintf("\tImage Sensor Type: %s.\n","One-chip color area sensor"); break; case 3: output$+= sprintf("\tImage Sensor Type: %s.\n","Two-chip color area sensor"); break; case 4: output$+= sprintf("\tImage Sensor Type: %s.\n","Three-chip color area sensor"); break; case 5: output$+= sprintf("\tImage Sensor Type: %s.\n","Color sequential area sensor"); break; case 7: output$+= sprintf("\tImage Sensor Type: %s.\n","Trilinear sensor"); break; case 8: output$+= sprintf("\tImage Sensor Type: %s.\n","Color sequential linear sensor"); break; } } # Exif.Photo.WhiteBalance if(keyStrings[i]=="Exif.Photo.WhiteBalance") { value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.WhiteBalance")); switch(value) { case 0: output$+= sprintf("\tWhite Balance: %s\n", "Auto"); break; case 1: output$+= sprintf("\tWhite Balance: %s\n", "Manual"); break; } } # Exif.Thumbnail.Compression if (keyStrings[i]=="Exif.Thumbnail.Compression") { value=StrToNum(exifhandle.GetDatumStr("Exif.Thumbnail.Compression")); switch(value) { case 1: output$+= sprintf("\t%s\n", "Uncompressed"); break; case 6: output$+= sprintf("\t%s\n", "JPEG compression"); break; } } # Exif.Thumbnail.JPEGInterchangeFormat if (keyStrings[i]=="Exif.Thumbnail.JPEGInterchangeFormat") output$+= sprintf("\tThe offset to the start byte (SOI) of JPEG compressed thumbnail data.\n"); # Exif.Photo.ExposureProgram if (keyStrings[i]=="Exif.Photo.ExposureProgram") { value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.ExposureProgram")); switch(value) { case 0: output$+= sprintf("\tExposure Program Class: %s.\n","Not defined"); break; case 1: output$+= sprintf("\tExposure Program Class: %s.\n","Manual"); break; case 2: output$+= sprintf("\tExposure Program Class: %s.\n","Normal Program"); break; case 3: output$+= sprintf("\tExposure Program Class: %s.\n","Aperture priority"); break; case 4: output$+= sprintf("\tExposure Program Class: %s.\n","Shutter priority"); break; case 5: output$+= sprintf("\tExposure Program Class: %s.\n","Creative program (biased toward depth of field)"); break; case 6: output$+= sprintf("\tExposure Program Class: %s.\n","Action program (biased toward fast shutter speed"); break; case 7: output$+= sprintf("\tExposure Program Class: %s.\n","Portrait mode (for closeup photos with the background out of focus)"); break; case 8: output$+= sprintf("\tExposure Program Class: %s.\n","Landscape mode (for landscape photos with the background in focus"); break; } } # Exif.Photo.LightSource if (keyStrings[i]=="Exif.Photo.LightSource") { value=StrToNum(exifhandle.GetDatumStr("Exif.Photo.LightSource")); switch(value) { case 0: output$+= sprintf("\tLight Source: %s.\n","Unknown"); break; case 1: output$+= sprintf("\tLight Source: %s.\n","Daylight"); break; case 2: output$+= sprintf("\tLight Source: %s.\n","Fluorescent"); break; case 3: output$+= sprintf("\tLight Source: %s.\n","Tungsten (incandescent light)"); break; case 4: output$+= sprintf("\tLight Source: %s.\n","Flash"); break; case 9: output$+= sprintf("\tLight Source: %s.\n","Fine weather"); break; case 10: output$+= sprintf("\tLight Source: %s.\n","Cloudy weather"); break; case 11: output$+= sprintf("\tLight Source: %s.\n","Shade"); break; case 12: output$+= sprintf("\tLight Source: %s.\n","Daylight fluorescent (D 5700 - 7100K)"); break; case 13: output$+= sprintf("\tLight Source: %s.\n","Day white fluorescent (N 4600 - 5400K)"); break; case 14: output$+= sprintf("\tLight Source: %s.\n","Cool white fluorescent (W 3900 - 4500K)"); break; case 15: output$+= sprintf("\tLight Source: %s.\n","White fluorescent (WW 3200 - 3700K)"); break; case 17: output$+= sprintf("\tLight Source: %s.\n","Standard light A"); break; case 18: output$+= sprintf("\tLight Source: %s.\n","Standard light B"); break; case 19: output$+= sprintf("\tLight Source: %s.\n","Standard light C"); break; case 20: output$+= sprintf("\tLight Source: %s.\n","D55"); break; case 21: output$+= sprintf("\tLight Source: %s.\n","D65"); break; case 22: output$+= sprintf("\tLight Source: %s.\n","D75"); break; case 23: output$+= sprintf("\tLight Source: %s.\n","D50"); break; case 24: output$+= sprintf("\tLight Source: %s.\n","ISO studio tungsten"); break; case 255: output$+= sprintf("\tLight Source: %s.\n","Other light source"); break; } } # Exif.Thumbnail.PhotometricInterpretation if (keyStrings[i]=="Exif.Image.PhotometricInterpretation" || keyStrings[i]=="Exif.Thumbnail.PhotometricInterpretation") { value=StrToNum(exifhandle.GetDatumStr("Exif.Thumbnail.PhotometricInterpretation")); switch(value) { case 2: output$+= sprintf("\tPixel Composition: %s\n", "RGB"); break; case 6: output$+= sprintf("\tPixel Composition: %s\n", "YCbCr"); break; } } ######################################################### exifbox.AddItem(output$); } # end else } # end for } # end proc PopulateExif ############################ func DecimalTime(numeric hour, numeric min, numeric second) { # function to convert time to decimal value, returns decimal time if (hour<0) # check if hour is negative value for camera offset time { min=min*-1; # set minutes to negative second=second*-1; # set seconds to negative } local numeric time=hour + (min/60) + (second/3600); # convert to decimal value return time; } # end func DecimalTime ############################ # Procedure to get the options set by user and return the values; # Values are returned to the variables defined as procedure parameters proc GetOptions(var numeric cameraoffset, var string method$, var numeric timeoffset) { local string offsetTime$ = gpsdialog.GetCtrlValueStr("cameraoffset"); #get camera offset # parse and convert to decimal time cameraoffset=DecimalTime(StrToNum(GetToken(offsetTime$,":",1)), StrToNum(GetToken(offsetTime$,":",2)), StrToNum(GetToken(offsetTime$,":",3))); method$=gpsdialog.GetCtrlValueStr("method"); # get selected method: Interpolate or Closest offsetTime$=gpsdialog.GetCtrlValueStr("timeoffset"); # get max allowed difference in time for gps # parse and convert to decimal time timeoffset=DecimalTime(StrToNum(GetToken(offsetTime$,":",1)), StrToNum(GetToken(offsetTime$,":",2)), StrToNum(GetToken(offsetTime$,":",3))); timeoffset = timeoffset *3600; cameraoffset = cameraoffset*3600; } # end proc GetOptions() ############################ proc ComputeCoordinates(numeric imageNum, numeric computedflag, numeric errorreportflag) { # same as AttachImages, but doesn't use Table # procedure to compute coordinates for images class POINT3D leftXYZ, rightXYZ, newXYZ; local string report$; local numeric i, listcount = 0; local string method$; # options local numeric cameraoffset, timeoffset = 0; # options local string inputname$; # image time and name class DATETIME inputtime; class GPSDATA inputdata; if(imageNum==-1){ imageListNameAttached.Clear(); } GetOptions(cameraoffset, method$, timeoffset); # timeoffset=max allowed difference in time local numeric startcount, endcount; if (imageNum == -1) { endcount = imageListName.GetNumItems()-1; startcount=0; } else { endcount = imageNum; startcount=imageNum; } for listcount=startcount to endcount # for all images in image list box { inputname$ = imageListName.GetString(listcount); # grab input name inputtime = imageListTime[inputname$]; # grab input date and time inputdata = imageListData[inputname$]; ## check for existing coords local string imageName$=inputname$; class POINT3D coord; class STRINGLIST keys; local numeric keyindex = 0; local numeric valid=0; exifhandle.Open(imageName$); keys = exifhandle.GetKeyList(); for keyindex=0 to keys.GetNumItems()-1{ if (keys[keyindex]=="Exif.GPSInfo.GPSLongitude") valid++; if (keys[keyindex]=="Exif.GPSInfo.GPSLongitudeRef") valid++; if (keys[keyindex]=="Exif.GPSInfo.GPSLatitude") valid++; if (keys[keyindex]=="Exif.GPSInfo.GPSLatitudeRef") valid++; if (keys[keyindex]=="Exif.GPSInfo.GPSAltitude") valid++; if (keys[keyindex]=="Exif.GPSInfo.GPSAltitudeRef") valid++; } if (valid==6 && computedflag==0 && (Assigned[inputname$]!=1 || IsNull(Assigned[inputname$]))) { string xcoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLongitude"); string xcoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLongitudeRef"); string ycoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLatitude"); string ycoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLatitudeRef"); string zcoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSAltitude"); string zcoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSAltitudeRef"); coord.x = (StrToNum(GetToken(GetToken(xcoord$, " ", 1),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 1),"/",2))) + ((StrToNum(GetToken(GetToken(xcoord$, " ", 2),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 2),"/",2))) + (StrToNum(GetToken(GetToken(xcoord$, " ", 3),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 3),"/",2)))/60)/60; if (xcoordref$=="W") then coord.x=coord.x*-1; coord.y = (StrToNum(GetToken(GetToken(ycoord$, " ", 1),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 1),"/",2))) + ((StrToNum(GetToken(GetToken(ycoord$, " ", 2),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 2),"/",2))) + (StrToNum(GetToken(GetToken(ycoord$, " ", 3),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 3),"/",2)))/60)/60; if (ycoordref$=="S") then coord.y=coord.y*-1; coord.z = StrToNum(GetToken(zcoord$,"/",1))/ StrToNum(GetToken(zcoord$,"/",2)); if (zcoordref$=="1") then coord.z=coord.z*-1; imageListData[imageName$].Position.x = coord.x; imageListData[imageName$].Position.y = coord.y; imageListData[imageName$].Position.z = coord.z; Assigned[imageName$]=1; } # end if else if ( (Assigned[inputname$]!=1 || IsNull(Assigned[inputname$])) || computedflag==1) { Assigned[inputname$]=0; imageListData[inputname$].Position.x = 0; imageListData[inputname$].Position.y = 0; imageListData[inputname$].Position.z = 0; # compute coordinates using GPSDBASE class method class GPSDATA data; gpsdbase.Compute(inputtime, data, method$, timeoffset); imageListData[inputname$] = data; if (!IsNull(data.Position.x) && !IsNull(data.Position.y) && !IsNull(data.Position.z)) { imageListNameAttached.AddToEnd(inputname$); imageListData[inputname$] = data; } else if (errorreportflag == 1) # image coordinates not computed report$=report$ + sprintf("Failed to compute coordinates for image '%s' \n", inputname$); } # end if Assigned else { imageListNameAttached.AddToEnd(inputname$); imageListData[inputname$] = inputdata; } } # end for listcount if (report$ != "") # report$ contains images with no coordinates { report$=report$ + sprintf("These Images fall outside log range."); PopupMessage(report$); } } # end proc ComputeCoordinates ################################################# func GetEXIF(string filename$) { ## Function to read EXIF header from image "filename$" ## Adds string containing date and time (YYYYMMDD HH:MM:SS) to imageListTime string list ## returns 0 if it cannot find a header, 1 if successful class DATETIME datetime; exifhandle.Open(filename$); # key "Exif.Image.DateTime" contains date/time value (YYYY:MM:DD HH:MM:SS) # parse string and add to EXIFDateTime$ string as YYYYMMDD HH:MM:SS string exifDateTime$ = exifhandle.GetDatumStr("Exif.Image.DateTime"); #pass the name of a Key to GetDatumStr and returns the value as a string string exifDateTime2$ = exifhandle.GetDatumStr("Exif.Photo.DateTimeOriginal"); if (exifDateTime$ != "") { datetime.SetDateYYYYMMDD(StrToNum(sprintf("%s%s%s",exifDateTime$.substr(0,4),exifDateTime$.substr(5,2),exifDateTime$.substr(8,2)))); datetime.SetTime(StrToNum(exifDateTime$.substr(11,2)), StrToNum(exifDateTime$.substr(14,2)), StrToNum(exifDateTime$.substr(17,2))); } else if (exifDateTime2$ != "") { datetime.SetDateYYYYMMDD(StrToNum(sprintf("%s%s%s",exifDateTime2$.substr(0,4),exifDateTime2$.substr(5,2),exifDateTime2$.substr(8,2)))); datetime.SetTime(StrToNum(exifDateTime2$.substr(11,2)), StrToNum(exifDateTime2$.substr(14,2)), StrToNum(exifDateTime2$.substr(17,2))); } else return 0; gpsdialog.SetCtrlValueStr( "status", "Added: " + FileNameGetName(filename$) + "." + FileNameGetExt(filename$)); imageListTime[filename$] = datetime; # add time to string list return 1; # return 1 as successful }# end func GetEXIF ################################## proc UpdateImages(numeric imageNum, numeric computedflag, numeric errorreportflag) { class GUI_CTRL_LISTBOX imagelist; local string imageString$, imageName$; local numeric i=0; imagelist = gpsdialog.GetCtrlByID("imagebox"); imagelist.DeleteAllItems(); # clear list box imageStringList.Clear(); imageListName.RemoveDuplicates(); ComputeCoordinates(imageNum, computedflag, errorreportflag); # recompute coordiantes for images for i=0 to imageListName.GetNumItems()-1{ imageName$=imageListName[i]; if(Assigned[imageName$]==1) # assigned coordinates { imageListNameAttached.AddToEnd(imageName$); imageListNameAttached.RemoveDuplicates(); } print(imageListData[imageName$].Position.x); if(IsNull(imageListData[imageName$].Position.x) && IsNull(imageListData[imageName$].Position.y) && IsNull(imageListData[imageName$].Position.z)) # computed coordinates imageString$ = sprintf("*** %s.%s %i %02i:%02i:%02i x: %.7f y: %.7f z: %.7f ", FileNameGetName(imageName$), FileNameGetExt(imageName$), imageListTime[imageName$].GetDateYYYYMMDD(), imageListTime[imageName$].GetHour(), imageListTime[imageName$].GetMin(), imageListTime[imageName$].GetSec(), imageListData[imageName$].Position.x, imageListData[imageName$].Position.y, imageListData[imageName$].Position.z); else imageString$ = sprintf("%s.%s %i %02i:%02i:%02i x: %.7f y: %.7f z: %.7f ", FileNameGetName(imageName$), FileNameGetExt(imageName$), imageListTime[imageName$].GetDateYYYYMMDD(), imageListTime[imageName$].GetHour(), imageListTime[imageName$].GetMin(), imageListTime[imageName$].GetSec(), imageListData[imageName$].Position.x, imageListData[imageName$].Position.y, imageListData[imageName$].Position.z); imagelist.AddItem(imageString$); imageStringList[imageName$] = imageString$; } # end for }# end proc UpdateImages() ################################## proc RemoveAll() { # procedure to remove all GPS coordinates in listbox class GUI_CTRL_LISTBOX coordlist, loglist; coordlist = gpsdialog.GetCtrlByID("coordlistbox"); # get control for listbox loglist = gpsdialog.GetCtrlByID("loglistbox"); if( PopupYesNo("Remove All GPS coordinates?")==1) # confirm remove all coordinates { coordlist.DeleteAllItems(); # clear coord list box loglist.DeleteAllItems(); # clear log list box coordStringList.Clear(); gpsdbase.RemoveAllLogs(); gpsdialog.SetCtrlValueStr( "status", "Removed All Logs"); } } # end proc RemoveAll() ############################# proc AddGPS(string logname$) { # procedure to add GPS coordinates to listbox # called each time a log is removed or added to list class GUI_CTRL_LISTBOX loglist,coordlist; local numeric count=0; class FILE logfile; class DATETIME datetime; class GPSDATA data; numeric cameraoffset, timeoffset; string method$; GetOptions(cameraoffset, method$, timeoffset); # returns: method$, constraints$ cameraoffset, timeoffset gpsdbase.SetOffset(cameraoffset); coordlist = gpsdialog.GetCtrlByID("coordlistbox"); coordlist.DeleteAllItems(); # clear coord listbox coordStringList.Clear(); if(logname$!="") gpsdbase.ReadLog(logname$); for count=0 to gpsdbase.GetNumPoints()-1 { gpsdbase.GetPoint(count, datetime, data); datetime = gpsdbase.ApplyOffset(datetime); local string coord$ = sprintf("%i %02i:%02i:%02i , X: %.6f , Y: %.6f , Z: %.6f", datetime.GetDateYYYYMMDD(), datetime.GetHour(), datetime.GetMin(), datetime.GetSec(), data.Position.x, data.Position.y, data.Position.z); coordlist.AddItem(coord$); coordStringList.AddToEnd(coord$); } UpdateImages(-1, 0, 1); } # end proc AddGPS() ############################# proc RemoveGPS() { # procedure to remove GPS coordinates in listbox class GUI_CTRL_LISTBOX list; local numeric index; # selected item index list = gpsdialog.GetCtrlByID("coordlistbox"); # get control for list box index = list.GetSelectedItemIndex(); # get selected item index list.DeleteItemIndex(index); # remove from listbox gpsdbase.RemovePoint(index); coordStringList.Remove(index); } # end proc RemoveGPS() ############################# proc AddLogDirectory() { # Procedure to add a directory of logfiles to current list of log files # Checks if valid log file before adding # Correct format: # date(YYYYMMDD),time(HHMMSS),XPos(deg),YPos(deg),Elev(m),XVel(m/s),YVel(m/s),ZVel(m/s),Head(deg),Speed(m/s),DataSrc,NumSat class GUI_CTRL_LISTBOX list; # class for listbox control class FILEPATH filepath; class STRINGLIST filenames; local string defaultpath$,logpath$, line$; local numeric records, count, i=0; # format=flag to check for valid format, 1=valid, 0=invalid local string report$; # string holding any errors with adding log files defaultpath$ = _context.ScriptDir; # get directory containing gps logs filepath.SetName( GetDirectory( defaultpath$, "Please select the directory containing the GPS log files" ) ); filenames = filepath.GetFileList( "*.gps"); # get file list in directory list = gpsdialog.GetCtrlByID("loglistbox"); # get control for listbox holding logfile list for count=0 to filenames.GetNumItems()-1 # for all files in directory { logpath$ = sprintf( "%s/%s.%s", filepath.GetPath(), FileNameGetName(filenames[count]), FileNameGetExt(filenames[count])); if(FileNameGetName(filenames[count])!="") # if user did not cancel selection dialog { records = gpsdbase.GetNumPoints(); AddGPS(logpath$); if(gpsdbase.GetNumPoints() > records) list.AddItem(logpath$); else # if not valid report$=report$ + sprintf("Could not add '%s.%s'\n",FileNameGetName(logpath$), FileNameGetExt(logpath$)); } # end if } # end for filenames = filepath.GetFileList( "*.log"); # get file list in directory list = gpsdialog.GetCtrlByID("loglistbox"); # get control for listbox holding logfile list for count=0 to filenames.GetNumItems()-1 # for all files in directory { logpath$ = sprintf( "%s/%s.%s", filepath.GetPath(), FileNameGetName(filenames[count]), FileNameGetExt(filenames[count])); if(FileNameGetName(filenames[count])!="") # if user did not cancel selection dialog { records = gpsdbase.GetNumPoints(); AddGPS(logpath$); if(gpsdbase.GetNumPoints() > records) list.AddItem(logpath$); else # end if not valid report$=report$ + sprintf("Could not add '%s.%s'\n",FileNameGetName(logpath$), FileNameGetExt(logpath$)); } # end if } # end for if(report$ != "") # print error report { report$=report$ + sprintf("These gps log files are not standard format and could not be added."); PopupMessage(report$); } gpsdialog.SetCtrlValueStr( "status", "Added: " + filepath.GetPath()); } # end proc AddLogDirectory() ############################## proc AddLog() { # Add a single selected log file to list of log files class GUI_CTRL_LISTBOX list; local numeric records=0; local string line$, message$; local string prompt$ = "Select GPS Log"; local string logname$=GetInputFileName("", prompt$, ".gps .log"); # User selected log file local string logpath$ = sprintf( "%s/%s.%s", FileNameGetPath(logname$), FileNameGetName(logname$), FileNameGetExt(logname$)); list = gpsdialog.GetCtrlByID("loglistbox"); if(FileNameGetName(logname$)!="") # if user did not cancel dialog { records = gpsdbase.GetNumPoints(); AddGPS(logpath$); if(gpsdbase.GetNumPoints() > records) { list.AddItem(logpath$); gpsdialog.SetCtrlValueStr( "status", "Added: " + FileNameGetName(logname$) + "." + FileNameGetExt(logname$)); } else # if not valid { # print error report message$=sprintf("Could not add '%s.%s', log file not standard format.",FileNameGetName(logname$), FileNameGetExt(logname$)); PopupMessage(message$); # pop up message gpsdialog.SetCtrlValueStr( "status", "Could not add: " + FileNameGetName(logname$) + "." + FileNameGetExt(logname$)); } } # end if } # end proc ############################# proc RemoveLog() { # procedure to remove a log from the list class GUI_CTRL_LISTBOX list; local numeric records; list = gpsdialog.GetCtrlByID("loglistbox"); # get control for list box local numeric index=list.GetSelectedItemIndex(); # get selected item index local string logname$; gpsdbase.GetLog(index-1, logname$); gpsdialog.SetCtrlValueStr( "status", "Removed: " + FileNameGetName(logname$) + "." + FileNameGetExt(logname$)); list.DeleteItemIndex(index); # remove from listbox gpsdbase.RemoveLog(logname$); AddGPS(""); # redisplay new list of coordinates } # end proc RemoveLog() ############################# proc CreateTable() { # procedure to create new GPS table # DatabaseCreate if (ObjectExists(filename$, obj$, "Vector") == 0) # check if vector exists { CreateVector(GPSVector, filename$, obj$, desc$, "3DVector"); # create vector CreateImpliedGeoref(GPSVector, crs); } if (ObjectExists(filename$, obj$, "Vector") == 1) # check if vector exists OpenVector(GPSVector, filename$, obj$); # open vector dbase= OpenVectorPointDatabase(GPSVector); # open point dbase if ( TableExists(dbase,IMAGEtablename$) == 0) # table does not exist { table=TableCreate(dbase, IMAGEtablename$, tabledesc$); # create table, add time, x, y, z, image fields TableAddFieldString(table, "Date Time", 17, 17); TableAddFieldFloat(table, "Long (deg)",9,6); TableAddFieldFloat(table, "Lat (deg)",9,6); TableAddFieldFloat(table, "Elev (m)",9,6); TableAddFieldString(table, "Image",200,100); } } # end proc CreateTable() ############################ proc Save() { # procedure to select GPS log file string prompt$ = "Select Vector Object"; GetOutputObject("Vector", "NewOrExisting", prompt$, filename$, obj$, desc$); # User selected vector object # filename$ = rvc file # obj$ = vector object name # desc$ = vector object description # write file name to text box local string dlgtext$ = sprintf( "%s %s/%s.%s", FileNameGetName(obj$), FileNameGetPath(filename$), FileNameGetName(filename$), FileNameGetExt(filename$)); gpsdialog.SetCtrlValueStr( "filetext", dlgtext$ ); # enable Display of database, Attaching to Database # gpsdialog.GetCtrlbyID("displaytable").SetEnabled(1); gpsdialog.GetCtrlbyID("attach").SetEnabled(1); gpsdialog.GetCtrlbyID("changedirectory").SetEnabled(1); CreateTable(); # create GPS table } # end proc Save() ####################################### ######### View Display Procedures ############### ####################################### proc DisplayTable() { # procedure to display table in database editor class DBEDITOR dbedit; class DBEDITORTABLE tabview; dbase= OpenVectorPointDatabase(GPSVector); # open point dbase dbedit = DBEditorCreate(dbase); # create dbeditor handle tabview = DBEditorOpenTabularView(dbedit, IMAGEtablename$); # open table in tabular view } # end proc DisplayTable() ####################################### ############################################### ########## Procedures for Images ####################### ############################################### ############################ proc AddImageDirectory() { # procedure to select input image folder and add to image list class FILEPATH filepath; class STRINGLIST filenames; local string defaultpath$ = _context.ScriptDir; # get directory containing images local numeric hasEXIF=0; # flag to determine if jpg has valid EXIF header local numeric count=0; local string report$; # string containing error report local string imagePath$, imageTime$; filepath.SetName( GetDirectory( defaultpath$, "Please select the directory containing the JPEG files" ) ); filenames = filepath.GetFileList( "*.jpg" ); # get list of files in directory for count=0 to filenames.GetNumItems()-1 # for all images in directory { imagePath$ = sprintf( "%s/%s.%s", filepath.GetPath(), FileNameGetName(filenames[count]), FileNameGetExt(filenames[count])); if (Assigned[imagePath$]==1) break; if (filenames[count]!="") # if user has not cancelled dialog { hasEXIF=GetEXIF(imagePath$); # get EXIF header, returns 1 if valid, 0 if invalid if (hasEXIF == 1) # has EXIF { imageListName.AddToEnd(imagePath$); # add name to string list imageListData[imagePath$].Position.x=0; imageListData[imagePath$].Position.y=0; imageListData[imagePath$].Position.z=0; } if (hasEXIF == 0) # has no EXIF { report$=report$ + sprintf("Could not add image '%s.%s'.\n",FileNameGetName(filenames[count]),FileNameGetExt(filenames[count])); } } # end if } # end for if(report$ != "") # print error report { report$=report$ + sprintf("These Images contained no EXIF header and could not be added."); PopupMessage(report$); } UpdateImages(-1,0,0); gpsdialog.SetCtrlValueStr( "status", "Added: " + filepath.GetPath()); } # end proc AddImageDirectory ############################ proc AddImage() { # procedure to select single input image and add to image list local string prompt$ = "Select Image"; local string imageName$=GetInputFileName("", prompt$, ".jpg"); # User selected Image local string imagePath$ = sprintf( "%s/%s.%s", FileNameGetPath(imageName$), FileNameGetName(imageName$), FileNameGetExt(imageName$)); local numeric hasEXIF=0; local string imageTime$, message$; if(Assigned[imagePath$]==1) break; if(imageName$!="")# if user did not cancel dialog { hasEXIF=GetEXIF(imagePath$); # get exif header if(hasEXIF == 1) # has exif { imageListName.AddToEnd(imagePath$); # add name to string list local numeric NumItems = imageListName.GetNumItems(); imageListName.RemoveDuplicates(); if(imageListName.GetNumItems() == NumItems) UpdateImages(imageListName.GetNumItems()-1,0,0); } if(hasEXIF == 0) # has no exif { message$=sprintf("Could not add image '%s.%s', image has no EXIF header.",FileNameGetName(imageName$),FileNameGetExt(imageName$)); PopupMessage(message$);# report error } } # end if imageName } # end proc AddImage ####################################################################### ## Procedures related to choosing image coordinates from a view of a ## georeferenced raster using a point tool. # Procedure called when point tool is placed in the view. proc OnToolSet () { clear(); # transform point coordinates from view coordinates returned by point tool to raster map coordinates ptrastmap = TransPoint2D(myPt.Point, transViewToRastMap); # transform point coordinates from raster map to WGS84 / Geographic ptGeog = TransPoint2D(ptrastmap, transRastMapToGeog); local string coords$ = sprintf("x = %5.5f, y = %5.5f",ptGeog.x, ptGeog.y); ViewSetMessage(view,coords$); # update status line in view } # Procedure called when right mouse button is pressed proc OnToolApply () { class GUI_CTRL_LISTBOX list; list = gpsdialog.GetCtrlByID("imagebox"); # get control for list box local numeric index = list.GetSelectedItemIndex(); # get selected item index local string imageName$ = imageListName.GetString(index); # grab input name imageListData[imageName$].Position.x = ptGeog.x; imageListData[imageName$].Position.y = ptGeog.y; imageListData[imageName$].Position.z = 0; Assigned[imageName$]=1; UpdateImages(index,0,1); } # Procedure called when Close button on view window is pressed. proc OnClose() { DialogClose(pcwin); DestroyWidget(pcwin); CloseRaster(rasterIn); } # Procedure called when view window widget is destroyed. proc OnDestroy() { Exit(); } # Procedure called by Select Coordinates from Georeferenced Raster entry on the gpsdialog Image menu. # Prompts to choose raster, creates a view window and displays the raster, and creates a point tool # for obtaining map coordinates from the raster. proc SelectPoint() { GetInputRaster(rasterIn); gp = GroupCreate(); # Create dialog window. pcwin = CreateFormDialog("Find Coordinates"); WidgetAddCallback(pcwin.Shell.PopdownCallback, OnClose); WidgetAddCallback(pcwin.DestroyCallback, OnDestroy); # Create pushbutton item for Close. class PushButtonItem btnItemClose; btnItemClose = CreatePushButtonItem(" Close ",OnClose); # Create button row for Close button. class XmForm btnrow; btnrow = CreateButtonRow(pcwin,btnItemClose); btnrow.BottomWidget = pcwin; btnrow.RightWidget = pcwin; btnrow.LeftWidget = pcwin; # Create view in pcwin form to display input raster and vector. # A view has its own XmForm widget accessed as a class member "Form". # It is automatically attached to the parent form at the top. view = GroupCreateView(gp,pcwin,"",380,380, "NoLegendView,NoScalePosLine,DestroyOnClose"); view.Form.LeftWidget = pcwin; view.Form.RightWidget = pcwin; view.Form.BottomWidget = btnrow; # Add point tool to view. myPt = ViewCreatePointTool(view,"Point Tool","point_select","standard"); ToolAddCallback(myPt.PositionSetCallback,OnToolSet); ToolAddCallback(myPt.ActivateCallback,OnToolApply); myPt.DialogPosition = "RightCenter"; ViewAddToolIcons(view); DialogOpen(pcwin); # open the view # add selected raster to the view and redraw rasterInLayer = GroupQuickAddRasterVar(gp,rasterIn); ViewRedrawFull(view); # get the coordinate reference system from the raster layer and # get TRANSPARM from view to raster map coordinates coordrefsysRast = rasterInLayer.MapRegion.CoordRefSys; transViewToRastMap = ViewGetTransMapToView(view, coordrefsysRast, 1); # set up a TRANSPARM from raster map coordinates to WGS84 / Geographic # to match GPS log coordinates coordrefsysGeog.Assign("Geographic2D_WGS84_Deg"); transRastMapToGeog.InputCoordRefSys = coordrefsysRast; transRastMapToGeog.OutputCoordRefSys = coordrefsysGeog; ViewActivateTool(view,myPt); ViewSetMessage(view,"Left-click to move point. Right-click to assign coordinates."); } # end proc SelectPoint ############################# proc FillLog(){ # procedure to fill log selection dialog class GUI_CTRL_LISTBOX coordlist; coordlist = logdialog.GetCtrlByID("coordlistbox"); coordlist.DeleteAllItems(); local numeric i=0; for i=0 to coordStringList.GetNumItems()-1 coordlist.AddItem(coordStringList[i]); } # end proc FillLog() ############################# proc SelectLog(){ # procedure to select log record from dialog local numeric imageindex, coordindex; # selected item index class GUI_CTRL_LISTBOX imagelist, coordlist; class POINT3D coord; class DATETIME datetime; class GPSDATA data; imagelist = gpsdialog.GetCtrlByID("imagebox"); # get control for list box coordlist = logdialog.GetCtrlByID("coordlistbox"); imageindex = imagelist.GetSelectedItemIndex(); # get selected item index local string imageName$ = imageListName.GetString(imageindex); # grab input name coordindex = coordlist.GetSelectedItemIndex(); # get selected item index gpsdbase.GetPoint(coordindex, datetime, data); imageListData[imageName$].Position.x = data.Position.x; imageListData[imageName$].Position.x = data.Position.y; imageListData[imageName$].Position.x = data.Position.z; Assigned[imageName$]=1; UpdateImages(imageindex,0,1); } # end proc SelectLog() ###################################### proc AssignLogImage() { # procedure to select log record and assign lat/lon to image local numeric errlog; local string xmllog$; ### Create string variable with XML specification of dialog xmllog$ = ' '; ### parse XML text for the dialog into memory; ### return an error code (number < 0 ) if there are syntax errorsi errlog = logdoc.Parse(xmllog$); if ( errlog < 0 ) { PopupError( errlog ); # Popup an error dialog. "Details" button shows syntax errors. Exit(); } # get the dialog element from the parsed XML document and # show error message if the dialog element can't be found lognode = logdoc.GetElementByID("logdlg"); if ( lognode == 0 ) { PopupMessage("Could not find dialog node in XML document"); Exit(); } # Set the XML dialog element as the source for the GUI_DLG class instance # we are using for the dialog window. logdialog.SetXMLNode(lognode); ret = logdialog.DoModal(); } # end proc AssignLogImage() ############################# proc FillImage() { # procedure to fill log selection dialog class GUI_CTRL_LISTBOX imagelist; local string imageName$; local numeric i=0; imagelist = transferdialog.GetCtrlByID("imagebox"); imagelist.DeleteAllItems(); for i=0 to imageListNameAttached.GetNumItems()-1{ imageName$=imageListNameAttached[i]; imagelist.AddItem(imageStringList[imageName$]); } } # end proc FillLog() ############################# proc SelectImage() { # procedure to select log record from dialog local numeric imageindex, coordindex; #selected item index class POINT3D coord; class GUI_CTRL_LISTBOX imagelist, transferlist; imagelist = gpsdialog.GetCtrlByID("imagebox"); # get control for list box transferlist = transferdialog.GetCtrlByID("imagebox"); imageindex = imagelist.GetSelectedItemIndex(); # get selected item index local string imageName$ = imageListName.GetString(imageindex); # grab input name imageindex = transferlist.GetSelectedItemIndex(); local string transferName$ = imageListNameAttached.GetString(imageindex); imageListData[imageName$] = imageListData[transferName$]; Assigned[imageName$]=1; UpdateImages(imageindex,0,1); } # end proc SelectLog() ############################# proc AssignTransferImage() { # procedure to select log record and assign lat/lon to image local numeric errtransfer; local string xmltransfer$; ### Create string variable with XML specification of dialog xmltransfer$ = ' '; ### parse XML text for the dialog into memory; ### return an error code (number < 0 ) if there are syntax errorsi errtransfer = transferdoc.Parse(xmltransfer$); if ( errtransfer < 0 ) { PopupError( errtransfer ); # Popup an error dialog. "Details" button shows syntax errors. Exit(); } # get the dialog element from the parsed XML document and # show error message if the dialog element can't be found transfernode = transferdoc.GetElementByID("transferdlg"); if ( transfernode == 0 ) { PopupMessage("Could not find dialog node in XML document"); Exit(); } # Set the XML dialog element as the source for the GUI_DLG class instance # we are using for the dialog window. transferdialog.SetXMLNode(transfernode); ret = transferdialog.DoModal(); } # end proc AssignTransferImage() ############################# proc SetLatLon() { # procedure to select log record from dialog local numeric imageindex; # selected item index local numeric xcoord, ycoord=0; class GUI_CTRL_LISTBOX imagelist; imagelist = gpsdialog.GetCtrlByID("imagebox"); # get control for list box imageindex = imagelist.GetSelectedItemIndex(); # get selected item index local string imageName$ = imageListName.GetString(imageindex); # grab input name xcoord = latlondialog.GetCtrlValueNum("xcoorddeg") + (latlondialog.GetCtrlValueNum("xcoordmin") + (latlondialog.GetCtrlValueNum("xcoordsec")/60))/60; ycoord = latlondialog.GetCtrlValueNum("ycoorddeg") + (latlondialog.GetCtrlValueNum("ycoordmin") + (latlondialog.GetCtrlValueNum("ycoordsec")/60))/60; imageListData[imageName$].Position.x = xcoord; imageListData[imageName$].Position.y = ycoord; imageListData[imageName$].Position.z = latlondialog.GetCtrlValueNum("zcoord"); Assigned[imageName$]=1; UpdateImages(imageindex,0,1); } # end proc SetLatLon ############################# proc AssignLatLonImage() { # procedure to select log record and assign lat/lon to image local numeric errlatlon; local string xmllatlon$; ### Create string variable with XML specification of dialog xmllatlon$ = ' '; ### parse XML text for the dialog into memory; ### return an error code (number < 0 ) if there are syntax errorsi errlatlon = latlondoc.Parse(xmllatlon$); if ( errlatlon < 0 ) { PopupError( errlatlon ); # Popup an error dialog. "Details" button shows syntax errors. Exit(); } # get the dialog element from the parsed XML document and # show error message if the dialog element can't be found latlonnode = latlondoc.GetElementByID("latlondlg"); if ( latlonnode == 0 ) { PopupMessage("Could not find dialog node in XML document"); Exit(); } # Set the XML dialog element as the source for the GUI_DLG class instance # we are using for the dialog window. latlondialog.SetXMLNode(latlonnode); ret = latlondialog.DoModal(); } # end proc AssignLogImage() ############################# proc AssignExistingImage() { # procedure to use coordinates already existing in image local numeric index, keyindex, valid = 0; # selected item index class GUI_CTRL_LISTBOX list; class POINT3D coord; class STRINGLIST keys; list = gpsdialog.GetCtrlByID("imagebox"); # get control for list box index = list.GetSelectedItemIndex(); # get selected item index local string imageName$ = imageListName.GetString(index); # grab input name exifhandle.Open(imageName$); keys = exifhandle.GetKeyList(); for keyindex=0 to keys.GetNumItems()-1{ if(keys[keyindex]=="Exif.GPSInfo.GPSLongitude") valid++; if(keys[keyindex]=="Exif.GPSInfo.GPSLongitudeRef") valid++; if(keys[keyindex]=="Exif.GPSInfo.GPSLatitude") valid++; if(keys[keyindex]=="Exif.GPSInfo.GPSLatitudeRef") valid++; if(keys[keyindex]=="Exif.GPSInfo.GPSAltitude") valid++; if(keys[keyindex]=="Exif.GPSInfo.GPSAltitudeRef") valid++; } if(valid==6){ string xcoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLongitude"); string xcoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLongitudeRef"); string ycoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLatitude"); string ycoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSLatitudeRef"); string zcoord$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSAltitude"); string zcoordref$ = exifhandle.GetDatumStr("Exif.GPSInfo.GPSAltitudeRef"); coord.x = (StrToNum(GetToken(GetToken(xcoord$, " ", 1),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 1),"/",2))) + ((StrToNum(GetToken(GetToken(xcoord$, " ", 2),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 2),"/",2))) + (StrToNum(GetToken(GetToken(xcoord$, " ", 3),"/",1)) / StrToNum(GetToken(GetToken(xcoord$, " ", 3),"/",2)))/60)/60; if (xcoordref$=="W") then coord.x=coord.x*-1; coord.y = (StrToNum(GetToken(GetToken(ycoord$, " ", 1),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 1),"/",2))) + ((StrToNum(GetToken(GetToken(ycoord$, " ", 2),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 2),"/",2))) + (StrToNum(GetToken(GetToken(ycoord$, " ", 3),"/",1)) / StrToNum(GetToken(GetToken(ycoord$, " ", 3),"/",2)))/60)/60; if (ycoordref$=="S") then coord.y=coord.y*-1; coord.z = StrToNum(GetToken(zcoord$,"/",1))/ StrToNum(GetToken(zcoord$,"/",2)); if (zcoordref$=="1") then coord.z=coord.z*-1; imageListData[imageName$].Position.x = coord.x; imageListData[imageName$].Position.y = coord.y; imageListData[imageName$].Position.z = coord.z; Assigned[imageName$]=1; UpdateImages(index, 0, 1); } else { PopupMessage("Image does not contain all necessary GPS tags."); } } # end proc AssignExistingImage ############################# proc PopulateImageMenu() { class GUI_CTRL_MENUBUTTON menu; menu = gpsdialog.GetCtrlByID("imagemenu"); menu.AddItem(" Use Computed Coordinates from Logs...", "ComputedCoords"); menu.AddItem(" Select Coordinates from Georeferenced Raster... ", "SelectPoint"); menu.AddItem(" Select Coordinates in a Log Record... ", "AssignLogImage"); menu.AddItem(" Manually Enter Lat/Lon Coordinates... ", "AssignLatLonImage"); menu.AddItem(" Transfer Coordinates from Other Image... ", "AssignTransferImage"); menu.AddItem(" Use Pre-Existing EXIF Coordinates... ", "AssignExistingImage"); } # end proc PopulateImageMenu proc SelectImageMenu() { class GUI_CTRL_MENUBUTTON menu; class GUI_CTRL_LISTBOX list; menu = gpsdialog.GetCtrlByID("imagemenu"); list = gpsdialog.GetCtrlByID("imagebox"); local numeric index = list.GetSelectedItemIndex(); if(menu.GetValue()=="ComputedCoords") UpdateImages(index,1,1); if(menu.GetValue()=="SelectPoint") SelectPoint(); if(menu.GetValue()=="AssignLogImage") AssignLogImage(); if(menu.GetValue()=="AssignLatLonImage") AssignLatLonImage(); if(menu.GetValue()=="AssignTransferImage") AssignTransferImage(); if(menu.GetValue()=="AssignExistingImage") AssignExistingImage(); } # end proc SelectImageMenu ############################# proc ViewImage() { # procedure to open selected image in default OS viewer local numeric index; # selected item index class GUI_CTRL_LISTBOX list; list = gpsdialog.GetCtrlByID("imagebox"); # get control for list box index = list.GetSelectedItemIndex(); # get selected item index local string image$ = imageListName.GetString(index); # grab input name RunAssociatedApplication(image$); } # end proc ViewImage() ############################# proc RemoveImage() { # procedure to remove image in listbox class GUI_CTRL_LISTBOX list; local numeric index; # selected item index list = gpsdialog.GetCtrlByID("imagebox"); # get control for list box index = list.GetSelectedItemIndex(); # get selected item index list.DeleteItemIndex(index); # remove from listbox local string imageName$ = imageListName[index]; gpsdialog.SetCtrlValueStr( "status", "Removed: " + FileNameGetName(imageName$) + "." + FileNameGetExt(imageName$) + " - " + imageListTime[imageName$]); imageListData[imageName$].Position.x = 0; imageListData[imageName$].Position.y = 0; imageListData[imageName$].Position.z = 0; Assigned[imageName$]=0; imageListName.Remove(index); # remove from stringlist local numeric i; for i=0 to imageListNameAttached.GetNumItems()-1{ if(imageListNameAttached[i]==imageName$) imageListNameAttached.Remove(i); } imageStringList[imageName$] = ""; } # end proc RemoveImage() ############################# proc RemoveAllImages() { # procedure to remove all images in listbox class GUI_CTRL_LISTBOX list; list = gpsdialog.GetCtrlByID("imagebox"); # get control for listbox if( PopupYesNo("Remove All Images?")==1) # confirm remove all images { list.DeleteAllItems(); # clear list box imageListName.Clear(); # clear string list imageListNameAttached.Clear(); imageStringList.Clear(); imageListTime.Clear(); # clear string list imageListData.Clear(); Assigned.Clear(); gpsdialog.SetCtrlValueStr( "status", "Removed All Images"); } } # end proc RemoveAll() ############################ proc ChangeDirectory() { # Procedure to change directory of all images in table # User selects directory to change the image path to # Image name read in from each record of the table # Image name then compared to the list of images in the directory # If the image name is equal to a file in the directory, # then the path of the image in the table is changed to the new directory class FILEPATH filepath; class STRINGLIST filenames; dbase= OpenVectorPointDatabase(GPSVector); # get dbase table=DatabaseGetTableInfo(dbase, IMAGEtablename$); # get table class local numeric count,recordNum; local string imageName$, imagePath$, imageCurrent$, imageExt$; local numeric numberOfRecords=table.NumRecords; local string defaultpath$ = _context.ScriptDir; # get directory containing gps logs filepath.SetName( GetDirectory( defaultpath$, "Please select the directory containing the images" ) ); filenames = filepath.GetFileList( "*.jpg" );# get file list in directory for recordNum=1 to numberOfRecords # for all records { # get image path, name and extension imagePath$=TableReadFieldStr(table, "Image", recordNum); imageCurrent$=FileNameGetName(imagePath$); imageExt$=FileNameGetExt(imagePath$); for count=0 to filenames.GetNumItems()-1 # for all files in directory { imageName$=FileNameGetName(filenames[count]); #get name of file if(imageCurrent$==imageName$)# compare both names { count=filenames.GetNumItems()-1; # if image name in directory equal to record, then change path in table to this directory imagePath$ = sprintf( "%s/%s.%s", filepath.GetPath(), imageName$, imageExt$); } } # change image path TableWriteField(table, recordNum, "Image", imagePath$); } }# end proc ChangeDirectory() ############################ proc AttachPoints() { # procedure to attach table records to points in vector # call each time new record added local array numeric recordarray[100]; # record array local array numeric elementarray[1]; # element array local array numeric writearray[1]; # records to write array local numeric numberOfRecords, numberOfElements, elementnum, recordnum,x,y,z,i; dbase= OpenVectorPointDatabase(GPSVector); # get dbase table=DatabaseGetTableInfo(dbase, IMAGEtablename$); # get table class elementnum=1; # set to first element numberOfRecords=table.NumRecords; # clear all current attachments: for i=1 to numberOfRecords # add all records to record array recordarray[i]=i; for i=1 to numberOfRecords # for every element in vector { elementarray[1]=i; # remove all records from each element TableRemoveAttachment(table, elementarray[1], recordarray, numberOfRecords); } for recordnum=1 to numberOfRecords # for all records { x=TableReadFieldNum(table, "Long (deg)", recordnum); # read in coordinates y=TableReadFieldNum(table, "Lat (deg)", recordnum); z=TableReadFieldNum(table, "Elev (m)", recordnum); VectorChangePoint(GPSVector, elementnum, x, y, z); # change vector point to coordinates of record writearray[1]=recordnum; # add current record to array of records to write TableWriteAttachment(table, elementnum, writearray, 1, "point"); # make attachment if(TableReadFieldStr(table, "Date Time",recordnum) != TableReadFieldStr(table, "Date Time", recordnum+1)) elementnum++; # if not duplicate records, move to next point } } # end proc AttachPoints() ############################ proc ShiftRecords(numeric recordnum){ # procedure to create new record and shift all records forward 1 starting at recordnum # designed to handle TableNewRecord only adding record to end of table, # but want to preserve a certain record order number class POINT3D coordCurrent,coordNext; # point3d class to hold current and next record coordinates dbase= OpenVectorPointDatabase(GPSVector); # open point dbase table=DatabaseGetTableInfo(dbase, IMAGEtablename$); # get table class local numeric count=table.NumRecords; local string datetimeCurrent$, datetimeNext$; local string imageCurrent$, imageNext$; local numeric i=0; datetimeCurrent$=TableReadFieldStr(table, "Date Time", recordnum); # read current record coordinates coordCurrent.x=TableReadFieldNum(table, "Long (deg)", recordnum); coordCurrent.y=TableReadFieldNum(table, "Lat (deg)", recordnum); coordCurrent.z=TableReadFieldNum(table, "Elev (m)", recordnum); imageCurrent$=TableReadFieldStr(table, "Image", recordnum); TableNewRecord(table); # create new blank record at end of table to shift last record into for i=recordnum to count { datetimeNext$=TableReadFieldStr(table, "Date Time", i+1); # read next record info coordNext.x=TableReadFieldNum(table, "Long (deg)", i+1); coordNext.y=TableReadFieldNum(table, "Lat (deg)", i+1); coordNext.z=TableReadFieldNum(table, "Elev (m)", i+1); imageNext$=TableReadFieldStr(table, "Image", i+1); # write previous record information to next record, shifting the record forward 1 TableWriteRecord(table, i+1, datetimeCurrent$, coordCurrent.x, coordCurrent.y, coordCurrent.z, imageCurrent$); datetimeCurrent$=datetimeNext$; # Old next record becomes new current record coordCurrent.x=coordNext.x; coordCurrent.y=coordNext.y; coordCurrent.z=coordNext.z; imageCurrent$=imageNext$; } # end for } # end proc ShiftRecords ############################ func NewRecord(string inputtime$, numeric x, numeric y, numeric z, string inputname$) { # function to add a new record to a table # preserves correct record order based on time # also checks for duplicate records class DATETIME datetimeInput, datetimeCurrent; local numeric count=1; local string currentTime$, currentName$, date$, time$; local numeric tInput, tCurrent; dbase= OpenVectorPointDatabase(GPSVector); # open point dbase table= DatabaseGetTableInfo(dbase, IMAGEtablename$); # open table date$=inputtime$.substr(0,8); # parse date for input datetimeInput.SetDateYYYYMMDD(StrToNum(date$)); time$=inputtime$.substr(9,8); # parse time for input datetimeInput.SetTime(StrToNum(time$.substr(0,2)),StrToNum(time$.substr(3,2)),StrToNum(time$.substr(6,2))); tInput=DecimalTime(datetimeInput.GetHour(),datetimeInput.GetMin(),datetimeInput.GetSec()); # get decimal time for input if (table.NumRecords >= 1) # if table not empty { for count=1 to table.NumRecords { currentTime$=TableReadFieldStr(table, "Date Time", count); # get current record time currentName$=TableReadFieldStr(table, "Image", count); # get current record name date$=currentTime$.substr(0,8); # parse date for current record datetimeCurrent.SetDateYYYYMMDD(StrToNum(date$)); time$=currentTime$.substr(9,8); # parse time for current record datetimeCurrent.SetTime(StrToNum(time$.substr(0,2)),StrToNum(time$.substr(3,2)),StrToNum(time$.substr(6,2))); tCurrent=DecimalTime(datetimeCurrent.GetHour(),datetimeCurrent.GetMin(),datetimeCurrent.GetSec()); # get decimal time for current record # check if day has rolled over # adjusts by 24*(difference in days) if(datetimeInput.GetDateYYYYMMDD() != datetimeCurrent.GetDateYYYYMMDD()) tCurrent=tCurrent+24*(datetimeCurrent.GetDateYYYYMMDD()-datetimeInput.GetDateYYYYMMDD()); if(currentName$ == inputname$) # if image already attached, do not create new record { TableWriteRecord(table, count, inputtime$, x, y, z, inputname$); # write to current record return 0; } if(tInput < tCurrent) # if input time is less than current record's time { ShiftRecords(count); # shift records forward 1 to make room for new record TableWriteRecord(table, count, inputtime$, x, y, z, inputname$); # write to newly freed record VectorAddPoint(GPSVector,x,y,z); # add blank vector point return 0; } if(tInput>tCurrent && count==table.NumRecords) # if has reached end of table { TableNewRecord(table, inputtime$, x, y, z, inputname$); # add new record at end of table VectorAddPoint(GPSVector,x,y,z); # add blank vector point return 0; } } # end for } # end if if (table.NumRecords==0) # if table empty { TableNewRecord(table, inputtime$, x, y, z, inputname$); # add new record VectorAddPoint(GPSVector,x,y,z); # add blank vector point } return 1; } # end proc NewRecord ############################ proc AttachImages() { local numeric listcount; local string inputtime$, inputname$; #image time and name for listcount=0 to imageListNameAttached.GetNumItems()-1 # for all images in image list box { inputname$ = imageListNameAttached.GetString(listcount); # grab input name inputtime$ = sprintf("%i %i:%i:%i",imageListTime[inputname$].GetDateYYYYMMDD(), imageListTime[inputname$].GetHour(), imageListTime[inputname$].GetMin(), imageListTime[inputname$].GetSec()); # grab input date and time NewRecord(inputtime$,imageListData[inputname$].Position.x, imageListData[inputname$].Position.y, imageListData[inputname$].Position.z, inputname$); } AttachPoints(); # attach records to points in vector VectorValidate(GPSVector); CloseVector(GPSVector); # enable Display Table icon button gpsdialog.GetCtrlbyID("displaytable").SetEnabled(1); } # end proc AttachImages ############################ proc WriteEXIF(){ # write to exif metadata class POINT3D coord; # point3d class to hold current and next record coordinates local string imagePathOriginal$, imageTime$; local numeric i=0; local numeric BackupFlag = PopupYesNo("Would you like to create backup copies of the images?"); for i=0 to imageListNameAttached.GetNumItems()-1 { # get values to write imagePathOriginal$=imageListNameAttached.GetString(i); # grab input name coord.x =imageListData[imagePathOriginal$].Position.x; coord.y =imageListData[imagePathOriginal$].Position.y; coord.z =imageListData[imagePathOriginal$].Position.z; if(BackupFlag){ local string imagePath$ = sprintf( "%s/%s%s.%s", FileNameGetPath(imagePathOriginal$), FileNameGetName(imagePathOriginal$), "-SML",FileNameGetExt(imagePathOriginal$)); CopyFile(imagePathOriginal$, imagePath$); exifhandle.Open(imagePath$); # open jpeg to write } else exifhandle.Open(imagePathOriginal$); # writeexif: # Set Long Reference: indicates if West or East longitude if (coord.x < 0) exifhandle.AddDatumStr("Exif.GPSInfo.GPSLongitudeRef","W"); if (coord.x >= 0) exifhandle.AddDatumStr("Exif.GPSInfo.GPSLongitudeRef", "E"); # Set Lat Reference: indicates if South or North Latitude if (coord.y < 0) exifhandle.AddDatumStr("Exif.GPSInfo.GPSLatitudeRef","S"); if (coord.y >= 0) exifhandle.AddDatumStr("Exif.GPSInfo.GPSLatitudeRef", "N"); # Set altitude used as reference altitude exifhandle.AddDatumStr("Exif.GPSInfo.GPSAltitudeRef", "0"); # EXIF tags take decimal values as a fraction # Must convert altitude to a fraction string to write exifhandle.AddDatumStr("Exif.GPSInfo.GPSVersionID", "2 2 0 0"); exifhandle.AddDatumStr("Exif.GPSInfo.GPSMapDatum", "WGS-84"); local string Date$ = sprintf("%i:%i:%i", imageListTime[imagePathOriginal$].GetYear(), imageListTime[imagePathOriginal$].GetMonth(), imageListTime[imagePathOriginal$].GetDayOfMonth()); local string Time$ = sprintf("%s/1 %s/1 %s/1", imageTime$.substr(9,2), imageTime$.substr(12,2), imageTime$.substr(15,2)); exifhandle.AddDatumStr("Exif.GPSInfo.GPSDateStamp", Date$); exifhandle.AddDatumStr("Exif.GPSInfo.GPSTimeStamp", Time$); exifhandle.AddDatumStr("Exif.GPSInfo.GPSAltitude", sprintf("%i/1000",round(coord.z*1000))); coord.x = abs(coord.x); coord.y = abs(coord.y); numeric LatDeg = floor(coord.y); numeric LatMin = floor((coord.y - LatDeg)*60); numeric LatSec = floor( ((((coord.y - LatDeg)*60) - LatMin) *60)*10 ); exifhandle.AddDatumStr("Exif.GPSInfo.GPSLatitude", sprintf("%i/1 %i/1 %i/10",LatDeg,LatMin,LatSec)); numeric LonDeg = floor(coord.x); numeric LonMin = floor((coord.x - LonDeg)*60); numeric LonSec = floor( ((((coord.x - LonDeg)*60) - LonMin) *60)*10 ); exifhandle.AddDatumStr("Exif.GPSInfo.GPSLongitude", sprintf("%i/1 %i/1 %i/10",LonDeg,LonMin,LonSec)); exifhandle.Write(); } # for local string report$; if(BackupFlag) report$=sprintf("%i image(s) saved. Images written to new files appended with -SML to preserve originals.", imageListNameAttached.GetNumItems()); else report$=sprintf("%i image(s) saved.",imageListNameAttached.GetNumItems()); PopupMessage(report$); gpsdialog.SetCtrlValueStr( "status", NumToStr(imageListNameAttached.GetNumItems()) + " images' EXIF tags saved."); } # end proc WriteEXIF ############################# proc ApplyChanges() { AddGPS(""); } ############################# proc OpenScript() { # procedure called when starting # set default values gpsdialog.SetCtrlValueStr("cameraoffset", "+000:00:00"); gpsdialog.SetCtrlValueStr("timeoffset", "00:05:00"); PopulateImageMenu(); } ############################# proc ExitScript() { # procedure called when exiting print("Closing..."); CloseVector(GPSVector); # delete lock file local string lokfile$ = FileNameGetPath(filename$) + "/" + FileNameGetName(filename$) + "_rvc.lok"; if(fexists(lokfile$)==1) # check for lok file and delete { DeleteFile(lokfile$); print("Deleted ",lokfile$); } gpsdialog.Close(0); } ################################################# ############## Main Program ##################### ################################################# clear(); $warnings 3; # create string variable with XML specification for control dialog xml$=' '; ### parse XML text for the dialog into memory; ### return an error code (number < 0 ) if there are syntax errorsi err = dlgdoc.Parse(xml$); if ( err < 0 ) { PopupError( err ); # Popup an error dialog. "Details" button shows syntax errors. Exit(); } # get the dialog element from the parsed XML document and # show error message if the dialog element can't be found gpsdlgnode = dlgdoc.GetElementByID("gps"); if ( gpsdlgnode == 0 ) { PopupMessage("Could not find dialog node in XML document"); Exit(); } # Set the XML dialog element as the source for the GUI_DLG class instance # we are using for the dialog window. gpsdialog.SetXMLNode(gpsdlgnode); ret = gpsdialog.DoModal(); if ( ret == -1 ) { # exit script if Cancel button on dialog is pressed Exit(); }