System Overview and Design
This application is made up of six main classes:
- GeoMapperForm
- ExifForm
- ImageDialog
- ImageViewer
- ThumbnailController
- ThumbnailFlowLayoutPanel
The Helper Classes include:
- Configurator
- CountryInfo
- LatLongConversion
Figure 1 depicts these classes and their relationships.
GeoMapperForm is the main point of entry into the application. The Window Form components that make up the main window are Figure 2.
Configuration information is read on GeoMapperForm() object instantiation. I utilize the ConfigurationManager in the helper class, Configurator, to read custom information from an application configuration file. The format of this configuration file is:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="GoogleMapKey" value="Your Google Maps API Key here" />
<add key="WorldCountriesKey" value="c:\country.txt" />
<add key="AppKey" value="C:\exiv2\exiv2.exe"/>
</appSettings>
</configuration>
The configuration file name conforms to the following naming convention:
ApplicationName.ApplicationType.config
The value associated with a particular key is retrieved by calling:
ConfigurationManager.AppSettings.Get("GoogleMapKey");
where the key is the argument passed to the Get() method. The values associated with the definition of the keys in the configuration file has the following denotations:
- GoogleMapKey, your unique google map key when you sign up for the Google Maps API
- WorldCountriesKey, the location and name of a "country" file containing a list of countries and their three letter codes
- AppKey, the name and location of the exiv2 executable
The thumbnail view of all the images from the image folder appears in the ThumbnailFlowLayoutPanel. The user-selected image is shown in the PictureBox component. The TextBox area will display the extracted metadata of the selected image. If the selected image contains GPS location data, a street map view of the selected image will be displayed in the embedded browser (WebBrowser) window. The embedded browser will be empty if the selected image does not contain GPS information. GPS information can be added to images via the drop down menus; likewise image directory location selection is done via the drop-down menu.
EXIF metadata extraction is achieved using the ExifLibrary for .NET. An ExifFile object is first created via the static method call:
ExifFile file = ExifFile.Read(filename);
where filename is the selected thumbnail image. The various captured camera properties are retrieved via an indexed property. To retrieve the image exposure time, I index into the ExifFile object created earlier as follows:
file[ExifTag.ExposureTime].Value
Retrieving GPS location data follows the same rule. However, since not all cameras are equipped with GPS receivers, the GPS location data may not embedded within the EXIF metadata. In this case, I will have to check if the ExifFile object contains the GPS-related keys:
if (file.Properties.ContainsKey(ExifTag.GPSLatitude) &&
file.Properties.ContainsKey(ExifTag.GPSLongitude))
Only if this statement evaluates to True do I retrieve the latitude and longitude values and display the street map. Latitude and longitude are recovered as follows:
GPSLatitudeLongitude latitiude = file.Properties[ExifTag.GPSLatitude] as GPSLatitudeLongitude;
EXIF GPS data are stored in Sexagesimal format (i.e. base 60, degrees, minutes and seconds). This has to be converted to decimal format. This is where the helper class, LatLongConversion, comes in. Once the GPS data has been converted to decimal format, a webservice request is made to Google Maps using the helper method:
doDisplayGoogleMap(latitude, longitude)
If the Web Service request is successful, the street map will be displayed in the embedded web browser component. To display the street map in the embedded browser, I navigate to this URL address http://maps.google.com/maps?q=<latitude>,<longitude>&key=<GoogleMapAPIKey>&output=embed via the Navigate() method of the WebBrowser component. The Google Map API key is required. It lets you embed Google Maps in your web pages. The key can be obtained from the Google Map API Key. The output=embed (key,value) pair causes the side panel to be removed.
For images that do not contain embedded GPS location information, this application lets users retrieve GPS location data via Google Maps Web Services by passing the URL http://maps.google.com/maps/geo?q=<completeAddress>&output=csv&key=<GoogleMapAPIKey> to the WebClient DownloadString() method. The complete address would consists of the concatenation of address, postal code, and country name.
The country names and three-letter country codes are read from a file and stored in the helper class, CountryInfo. Adding in the postal code would give an increased level of accuracy to the GPS location returned. For more information on the (key, value) parameters for this URL, refer to Google Maps API Service. The output of this URL service is csv (comma separated value, which is the default) and the returned value has the syntax:status,accuracy,latitude,longitude. If this geocoding request is successful, the latitude, longitude results are then converted to sexagesimal format for display purposes.
Obtaining altitude information via a WebService call to http://ws.geonames.org/<elevation_model>?lat={0}&lng={1} is based on the same principle. Here the supplied elevation model can be either Global Topographic Data (GTOPO30) or Shuttle Radar Topography Mission (SRTM3) depending on the user selection. The resulting altitude value is returned as a string whose unit is in meters.
The latitude, longitude data returned from the Google Maps WebService request can then be embedded into the selected image. I use the command-line utility Exiv2 for this. This call is encapsulated within the method:
runExiv2Cmd(string appName, string appArgs)
A new Process() object (ProcessObj) is first instantiated. The application to be invoked (in this case, exiv2.exe) and its arguments are then associated with the Process object. To ensure that no DOS command window appear, I have to set the two attributes of the Process object (UseShellExecute and CreateNoWindow):
ProcessObj.StartInfo.UseShellExecute = false; ProcessObj.StartInfo.CreateNoWindow = true;
The process is then started by calling the Start() method. After the process has started, I have to call the method, WaitForExit(), to wait for the process to exit. Finally the output (if any) is read from the DOS application by making a call to ReadToEnd().



