#### 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$ = '<?xml version="1.0"?>
<root>
<dialog id = "exifdlg" Title = "Exif Information" OnOpen="PopulateEXIF()">
<listbox id="exifbox" Height="25" Width="50" Enabled="1"/>
</dialog>
</root>';
### 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$ = '<?xml version="1.0"?>
<root>
<dialog id = "logdlg" Title = "Select GPS Log Record" OnOpen="FillLog()" OnOK="SelectLog()">
<groupbox Name="Coordinate List: " ExtraBorder="4">
<pane Orientation="horizontal">
<listbox id="coordlistbox" Height="7" Width="43" Enabled="1"/>
</pane>
</groupbox>
</dialog>
</root>';
### 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$ = '<?xml version="1.0"?>
<root>
<dialog id = "transferdlg" Title = "Select Image" OnOpen="FillImage()" OnOK="SelectImage()">
<groupbox Name="Image to Transfer Coordiantes From: " ExtraBorder="4">
<pane Orientation="horizontal">
<listbox id="imagebox" Height="7" Width="43" Enabled="1"/>
</pane>
</groupbox>
</dialog>
</root>';
### 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$ = '<?xml version="1.0"?>
<root>
<dialog id = "latlondlg" Title = "Set Latitude/Longitude" OnOK="SetLatLon()">
<groupbox Name="Enter X,Y,Z: " ExtraBorder="4">
<pane Orientation="horizontal">
<label WidthGroup="labels">Latitude: </label>
<editnumber id="ycoorddeg" Justify="Right" Default="0" MinVal="-90" MaxVal="90" Width="3" Precision="0"/>
<editnumber id="ycoordmin" Justify="Right" Default="0" MinVal="0" MaxVal="60" Width="3" Precision="0"/>
<editnumber id="ycoordsec" Justify="Right" Default="0" MinVal="0" MaxVal="60" Widht="8" Precision="5"/>
</pane>
<pane Orientation="horizontal">
<label WidthGroup="labels">Longitude: </label>
<editnumber id="xcoorddeg" Justify="Right" Default="0" MinVal="-180" MaxVal="180" Width="3" Precision="0"/>
<editnumber id="xcoordmin" Justify="Right" Default="0" MinVal="0" MaxVal="60" Width="3" Precision="0"/>
<editnumber id="xcoordsec" Justify="Right" Default="0" MinVal="0" MaxVal="60" Widht="8" Precision="5"/>
</pane>
<pane Orientation="horizontal">
<label WidthGroup="labels">Elevation: </label>
<editnumber id="zcoord" Justify="Right" Default="0" Width="20" Precision="5"/>
</pane>
</groupbox>
</dialog>
</root>';
### 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$='<?xml version="1.0"?>
<root>
<dialog id="gps" Title="GPSphoto" VertResize="Relative" Buttons="" OnOpen="OpenScript()" OnClose="ExitScript()">
<book>
<page Name="Images">
<groupbox Name="Select Images" ExtraBorder="4" VertResize="Fixed">
<pane Orientation="horizontal" HorizResize="Fixed" ChildSpacing="6">
<menubutton id="imagemenu" Icon="EDIT_CONTROLS" ToolTip=" Image Options... " OnSelection="SelectImageMenu()"/>
<pushbutton Icon="RVCOBJ_DISP_SIM3D" ToolTip=" View Image " OnPressed="ViewImage()"/>
<pushbutton Icon="RVCOBJ_METADATA" ToolTIp=" EXIF Info " OnPressed="DisplayEXIF()"/>
<label></label>
<pushbutton Icon="CONTROL_ADD_CYAN" ToolTip=" Add Image " OnPressed="AddImage()"/>
<pushbutton Icon="CONTROL_SUBTRACT_CYAN" ToolTip=" Remove Image " OnPressed="RemoveImage()"/>
<pushbutton Icon="FOLDER_NEW" ToolTip=" Add Image Directory " OnPressed="AddImageDirectory()"/>
<pushbutton Icon="CONTROL_SUBTRACT_ALL_CYAN" ToolTip=" Remove All " OnPressed="RemoveAllImages()"/>
</pane>
<listbox id="imagebox" Height="14" Width="50" Enabled="1"/>
</groupbox>
</page>
<page Name="GPS Log">
<groupbox Name="GPS Logs: " ExtraBorder="4">
<pane Orientation="horizontal" HorizResize="Fixed" ChildSpacing="6">
<pushbutton Icon="CONTROL_ADD_CYAN" ToolTip=" Add Log " OnPressed="AddLog()"/>
<pushbutton Icon="CONTROL_SUBTRACT_CYAN" ToolTip=" Remove Log " OnPressed="RemoveLog()"/>
<pushbutton Icon="FOLDER_NEW" ToolTip=" Add Log Directory " OnPressed="AddLogDirectory()"/>
</pane>
<pane Orientation="horizontal">
<listbox id="loglistbox" Height="4" Enable="1"/>
</pane>
</groupbox>
<groupbox Name="Coordinate List: " ExtraBorder="4">
<pane Orientation="horizontal" HorizResize="Fixed" ChildSpacing="6">
<pushbutton Icon="CONTROL_SUBTRACT_CYAN" ToolTip=" Remove Coordinates " OnPressed="RemoveGPS()"/>
<pushbutton Icon="CONTROL_SUBTRACT_ALL_CYAN" ToolTip=" Remove All Coordinates " OnPressed="RemoveAll()"/>
</pane>
<pane Orientation="horizontal">
<listbox id="coordlistbox" Height="5" Width="43" Enabled="1"/>
</pane>
</groupbox>
<pane Orientation="horizontal" HorizAlignt="Left">
<label>Set Offset Time for Camera +/- (HHH:MM:SS)</label>
<edittext id="cameraoffset" WidthGroup="boxes" Justify="Right" MaxLength="10"/>
<pushbutton Icon="EDIT_APPLY_RED" ToolTip=" Apply Changes " OnPressed="ApplyChanges()"/>
</pane>
</page>
<page Name="Options">
<groupbox Name="Attaching Options" ExtraBorder="4" VertResize="Fixed">
<pane Orientation="vertical">
<groupbox Name="Select Method of Assigning Coordinates" ExtraBorder="4">
<radiogroup id="method">
<item Value="Interpolate" Name="Interpolate Coordinates"/>
<item Value="Closest" Name="Use Closest Coordinates"/>
</radiogroup>
</groupbox>
<groupbox Name="Select Constraints" ExtraBorder="4">
<pane Orientation="horizontal">
<label WidthGroup="labels">Set Max Difference in Time for GPS (HH:MM:SS)</label>
<edittext id="timeoffset" WidthGroup="boxes" Justify="Right" MaxLength="9"/>
</pane>
</groupbox>
</pane>
</groupbox>
<pane Orienation="horizontal" VertResize="Fixed" HorizAlign="Center">
<pushbutton Name=" Apply Changes " OnPressed="ApplyChanges()"/>
</pane>
</page>
</book>
<groupbox Name="Output Vector: " ExtraBorder="4" VertResize="Fixed">
<pane Orientation="horizontal" ChildSpacing="6">
<pushbutton Icon="RVCOBJ_VECTOR" ToolTip="Select Vector..." WidthGroup="Buttons" OnPressed="Save()"/>
<edittext id="filetext" Width="25" ReadOnly="true"/>
<pushbutton id="attach" Icon="FILE_SAVE" ToolTip=" Save Vector " Enabled="0" OnPressed="AttachImages()"/>
<pushbutton id="displaytable" Icon="RVCOBJ_DB_TABLE_INTERNAL" ToolTip=" Display GPS Table " Enabled="0" OnPressed="DisplayTable()"/>
<pushbutton id="changedirectory" Icon="FOLDER_NEW" ToolTip=" Change Directory of Images in Table " Enabled="0" OnPressed="ChangeDirectory()"/>
</pane>
</groupbox>
<pane Orientation="horizontal" ChildSpacing="6" HorizAlign="Right" >
<edittext id="status" ReadOnly="true"/>
<pushbutton id="writeexif" Icon="GRE_LAYER_SKETCH" ToolTip=" Write EXIF " Enabled="1" HorizResize="Fixed" OnPressed="WriteEXIF()"/>
<pushbutton id="close" Name=" Cancel " HorizResize="Fixed" OnPressed="ExitScript()"/>
</pane>
</dialog>
</root>';
### parse XML text for the dialog into memory;
### return an error code (number < 0 ) if there are syntax errorsi
err = dlgdoc.Parse(xml$);
if ( err < 0 ) {
PopupError( err ); # Popup an error dialog. "Details" button shows syntax errors.
Exit();
}
# get the dialog element from the parsed XML document and
# show error message if the dialog element can't be found
gpsdlgnode = dlgdoc.GetElementByID("gps");
if ( gpsdlgnode == 0 ) {
PopupMessage("Could not find dialog node in XML document");
Exit();
}
# Set the XML dialog element as the source for the GUI_DLG class instance
# we are using for the dialog window.
gpsdialog.SetXMLNode(gpsdlgnode);
ret = gpsdialog.DoModal();
if ( ret == -1 ) { # exit script if Cancel button on dialog is pressed
Exit();
}