]> gitweb.fperrin.net Git - GpsPrune.git/blobdiff - tim/prune/save/KmlExporter.java
Version 3, August 2007
[GpsPrune.git] / tim / prune / save / KmlExporter.java
index 16624ad1495dd26c549a0af26a3c087fbb0364c7..11474a6c8e8fb37f911f53f3f79e51edebc5fcf4 100644 (file)
 package tim.prune.save;
 
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
 import java.io.File;
-import java.io.FileWriter;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.io.Writer;
+import java.util.Iterator;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriter;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
 import javax.swing.JFileChooser;
 import javax.swing.JFrame;
+import javax.swing.JLabel;
 import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
 import javax.swing.filechooser.FileFilter;
 
-import tim.prune.App;
 import tim.prune.I18nManager;
 import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Track;
+import tim.prune.data.TrackInfo;
+import tim.prune.gui.ImageUtils;
 
 /**
  * Class to export track information
  * into a specified Kml file
  */
-public class KmlExporter
+public class KmlExporter implements Runnable
 {
-       private App _app = null;
        private JFrame _parentFrame = null;
+       private TrackInfo _trackInfo = null;
        private Track _track = null;
+       private JDialog _dialog = null;
+       private JTextField _descriptionField = null;
+       private JCheckBox _kmzCheckbox = null;
+       private JCheckBox _exportImagesCheckbox = null;
+       private JProgressBar _progressBar = null;
        private JFileChooser _fileChooser = null;
+       private File _exportFile = null;
+
+       // Filename of Kml file within zip archive
+       private static final String KML_FILENAME_IN_KMZ = "doc.kml";
+       // Width and height of thumbnail images in Kmz
+       private static final int THUMBNAIL_WIDTH = 240;
+       private static final int THUMBNAIL_HEIGHT = 180;
 
 
        /**
-        * Constructor giving App object, frame and track
-        * @param inApp application object to inform of success
+        * Constructor giving frame and track
         * @param inParentFrame parent frame
-        * @param inTrack track object to save
+        * @param inTrackInfo track info object to save
         */
-       public KmlExporter(App inApp, JFrame inParentFrame, Track inTrack)
+       public KmlExporter(JFrame inParentFrame, TrackInfo inTrackInfo)
        {
-               _app = inApp;
                _parentFrame = inParentFrame;
-               _track = inTrack;
+               _trackInfo = inTrackInfo;
+               _track = inTrackInfo.getTrack();
        }
 
 
        /**
         * Show the dialog to select options and export file
         */
-       public boolean showDialog()
+       public void showDialog()
        {
-               boolean fileSaved = false;
-               Object description = JOptionPane.showInputDialog(_parentFrame,
-                       I18nManager.getText("dialog.exportkml.text"),
-                       I18nManager.getText("dialog.exportkml.title"),
-                       JOptionPane.QUESTION_MESSAGE, null, null, "");
-               // TODO: Make dialog window including colour selection, line width, track description
-               if (description != null)
+               // Make dialog window including whether to compress to kmz (and include pictures) or not
+               if (_dialog == null)
                {
-                       // OK pressed, so choose output file
-                       if (_fileChooser == null)
-                               _fileChooser = new JFileChooser();
-                       _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
-                       _fileChooser.setFileFilter(new FileFilter() {
-                               public boolean accept(File f)
-                               {
-                                       return (f != null && (f.isDirectory() || f.getName().toLowerCase().endsWith(".kml")));
-                               }
-                               public String getDescription()
-                               {
-                                       return I18nManager.getText("dialog.exportkml.filetype");
-                               }
-                       });
-                       _fileChooser.setAcceptAllFileFilterUsed(false);
-                       // Allow choose again if an existing file is selected
-                       boolean chooseAgain = false;
-                       do
-                       {
-                               chooseAgain = false;
-                               if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
-                               {
-                                       // OK pressed and file chosen
-                                       File file = _fileChooser.getSelectedFile();
-                                       if (!file.getName().toLowerCase().endsWith(".kml"))
-                                       {
-                                               file = new File(file.getAbsolutePath() + ".kml");
-                                       }
-                                       // Check if file exists and if necessary prompt for overwrite
-                                       Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
-                                       if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame,
-                                                       I18nManager.getText("dialog.save.overwrite.text"),
-                                                       I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
-                                                       JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
-                                               == JOptionPane.YES_OPTION)
-                                       {
-                                               if (exportFile(file, description.toString()))
-                                               {
-                                                       fileSaved = true;
-                                               }
-                                               else
-                                               {
-                                                       chooseAgain = true;
-                                               }
-                                       }
-                                       else
-                                       {
-                                               chooseAgain = true;
-                                       }
-                               }
-                       } while (chooseAgain);
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.exportkml.title"), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
                }
-               return fileSaved;
+               enableCheckboxes();
+               _progressBar.setVisible(false);
+               _dialog.show();
        }
 
 
        /**
-        * Export the track data to the specified file with description
-        * @param inFile File object to save to
-        * @param inDescription description to use, if any
+        * Create dialog components
+        * @return Panel containing all gui elements in dialog
         */
-       private boolean exportFile(File inFile, String inDescription)
+       private Component makeDialogComponents()
        {
-               FileWriter writer = null;
-               try
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout());
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+               // Make a central panel with the text box and checkboxes
+               JPanel descPanel = new JPanel();
+               descPanel.setLayout(new FlowLayout());
+               descPanel.add(new JLabel(I18nManager.getText("dialog.exportkml.text")));
+               _descriptionField = new JTextField(20);
+               descPanel.add(_descriptionField);
+               mainPanel.add(descPanel);
+               dialogPanel.add(mainPanel, BorderLayout.CENTER);
+               // Checkboxes for kmz export and image export
+               _kmzCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.kmz"));
+               _kmzCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
+               _kmzCheckbox.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               // enable image checkbox if kmz activated
+                               enableCheckboxes();
+                       }
+               });
+               mainPanel.add(_kmzCheckbox);
+               _exportImagesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.exportimages"));
+               _exportImagesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
+               mainPanel.add(_exportImagesCheckbox);
+               mainPanel.add(Box.createVerticalStrut(10));
+               _progressBar = new JProgressBar(0, 100);
+               _progressBar.setVisible(false);
+               mainPanel.add(_progressBar);
+               mainPanel.add(Box.createVerticalStrut(10));
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               JButton okButton = new JButton(I18nManager.getText("button.ok"));
+               ActionListener okListener = new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               startExport();
+                       }
+               };
+               okButton.addActionListener(okListener);
+               _descriptionField.addActionListener(okListener);
+               buttonPanel.add(okButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               return dialogPanel;
+       }
+
+
+       /**
+        * Enable the checkboxes according to data
+        */
+       private void enableCheckboxes()
+       {
+               boolean hasPhotos = _trackInfo.getPhotoList() != null && _trackInfo.getPhotoList().getNumPhotos() > 0;
+               _exportImagesCheckbox.setSelected(hasPhotos && _kmzCheckbox.isSelected());
+               _exportImagesCheckbox.setEnabled(hasPhotos && _kmzCheckbox.isSelected());
+       }
+
+
+       /**
+        * Start the export process based on the input parameters
+        */
+       private void startExport()
+       {
+               // OK pressed, so choose output file
+               if (_fileChooser == null)
+                       {_fileChooser = new JFileChooser();}
+               _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
+               _fileChooser.setFileFilter(new FileFilter() {
+                       public boolean accept(File f)
+                       {
+                               return (f != null && (f.isDirectory()
+                                       || f.getName().toLowerCase().endsWith(".kml") || f.getName().toLowerCase().endsWith(".kmz")));
+                       }
+                       public String getDescription()
+                       {
+                               return I18nManager.getText("dialog.exportkml.filetype");
+                       }
+               });
+               String requiredExtension = null, otherExtension = null;
+               if (_kmzCheckbox.isSelected())
                {
-                       writer = new FileWriter(inFile);
-                       writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://earth.google.com/kml/2.1\">\n<Folder>\n");
-                       writer.write("\t<name>");
-                       writer.write(inDescription);
-                       writer.write("</name>\n");
-
-                       int i = 0;
-                       DataPoint point = null;
-                       boolean hasTrackpoints = false;
-                       // Loop over waypoints
-                       boolean writtenPhotoHeader = false;
-                       int numPoints = _track.getNumPoints();
-                       for (i=0; i<numPoints; i++)
+                       requiredExtension = ".kmz"; otherExtension = ".kml";
+               }
+               else
+               {
+                       requiredExtension = ".kml"; otherExtension = ".kmz";
+               }
+               _fileChooser.setAcceptAllFileFilterUsed(false);
+               // Allow choose again if an existing file is selected
+               boolean chooseAgain = false;
+               do
+               {
+                       chooseAgain = false;
+                       if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
                        {
-                               point = _track.getPoint(i);
-                               if (point.isWaypoint())
+                               // OK pressed and file chosen
+                               File file = _fileChooser.getSelectedFile();
+                               if (file.getName().toLowerCase().endsWith(otherExtension))
+                               {
+                                       String path = file.getAbsolutePath();
+                                       file = new File(path.substring(0, path.length()-otherExtension.length()) + requiredExtension);
+                               }
+                               else if (!file.getName().toLowerCase().endsWith(requiredExtension))
                                {
-                                       exportWaypoint(point, writer);
+                                       file = new File(file.getAbsolutePath() + requiredExtension);
                                }
-                               else if (point.getPhoto() != null)
+                               // Check if file exists and if necessary prompt for overwrite
+                               Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
+                               if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame,
+                                               I18nManager.getText("dialog.save.overwrite.text"),
+                                               I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
+                                               JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
+                                       == JOptionPane.YES_OPTION)
                                {
-                                       if (!writtenPhotoHeader)
-                                       {
-                                               writer.write("<Style id=\"camera_icon\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/pal4/icon46.png</href></Icon></IconStyle></Style>");
-                                               writtenPhotoHeader = true;
-                                       }
-                                       exportPhotoPoint(point, writer);
+                                       // New file or overwrite confirmed, so initiate export in separate thread
+                                       _exportFile = file;
+                                       new Thread(this).start();
                                }
                                else
                                {
-                                       hasTrackpoints = true;
+                                       chooseAgain = true;
                                }
                        }
-                       if (hasTrackpoints)
+               } while (chooseAgain);
+       }
+
+
+       /**
+        * Run method for controlling separate thread for exporting
+        */
+       public void run()
+       {
+               // Initialise progress bar
+               _progressBar.setVisible(true);
+               _progressBar.setValue(0);
+               boolean exportToKmz = _kmzCheckbox.isSelected();
+               boolean exportImages = exportToKmz && _exportImagesCheckbox.isSelected();
+               _progressBar.setMaximum(exportImages?getNumPhotosToExport():1);
+               OutputStreamWriter writer = null;
+               ZipOutputStream zipOutputStream = null;
+               try
+               {
+                       // Select writer according to whether kmz requested or not
+                       if (!_kmzCheckbox.isSelected())
+                       {
+                               // normal writing to file
+                               writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
+                       }
+                       else
                        {
-                               writer.write("\t<Placemark>\n\t\t<name>track</name>\n\t\t<Style>\n\t\t\t<LineStyle>\n"
-                                       + "\t\t\t\t<color>cc0000cc</color>\n\t\t\t\t<width>4</width>\n\t\t\t</LineStyle>\n"
-                                       + "\t\t</Style>\n\t\t<LineString>\n\t\t\t<coordinates>");
-                               // Loop over track points
-                               for (i=0; i<numPoints; i++)
+                               // kmz requested - need zip output stream
+                               zipOutputStream = new ZipOutputStream(new FileOutputStream(_exportFile));
+                               writer = new OutputStreamWriter(zipOutputStream);
+                               // Make an entry in the zip file for the kml file
+                               ZipEntry kmlEntry = new ZipEntry(KML_FILENAME_IN_KMZ);
+                               zipOutputStream.putNextEntry(kmlEntry);
+                       }
+                       // write file
+                       int numPoints = exportData(writer, exportImages);
+                       // update progress bar
+                       _progressBar.setValue(1);
+
+                       // close zip entry if necessary
+                       if (zipOutputStream != null)
+                       {
+                               // Make sure all buffered data in writer is flushed
+                               writer.flush();
+                               // Close off this entry in the zip file
+                               zipOutputStream.closeEntry();
+                               // Export images into zip file too if requested
+                               if (exportImages)
                                {
-                                       point = _track.getPoint(i);
-                                       if (!point.isWaypoint())
-                                       {
-                                               exportTrackpoint(point, writer);
-                                       }
+                                       // Create thumbnails of each photo in turn and add to zip as images/image<n>.jpg
+                                       exportThumbnails(zipOutputStream);
                                }
-                               writer.write("\t\t\t</coordinates>\n\t\t</LineString>\n\t</Placemark>");
                        }
-                       writer.write("</Folder>\n</kml>");
+
+                       // close file
                        writer.close();
                        JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.ok1")
                                 + " " + numPoints + " " + I18nManager.getText("dialog.save.ok2")
-                                + " " + inFile.getAbsolutePath(),
+                                + " " + _exportFile.getAbsolutePath(),
                                I18nManager.getText("dialog.save.oktitle"), JOptionPane.INFORMATION_MESSAGE);
-                       return true;
+                       // export successful so need to close dialog and return
+                       _dialog.dispose();
+                       return;
                }
                catch (IOException ioe)
                {
+                       // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
                        try {
                                if (writer != null) writer.close();
                        }
                        catch (IOException ioe2) {}
-                       JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.save.failed") + ioe.getMessage(),
+                       JOptionPane.showMessageDialog(_parentFrame,
+                               I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
                                I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
                }
-               return false;
+               // if not returned already, export failed so need to recall the file selection
+               startExport();
+       }
+
+
+       /**
+        * Export the information to the given writer
+        * @param inWriter writer object
+        * @param inExportImages true if image thumbnails are to be referenced
+        * @return number of points written
+        */
+       private int exportData(OutputStreamWriter inWriter, boolean inExportImages)
+       throws IOException
+       {
+               // TODO: Look at segments of track, and split into separate lines in Kml if necessary
+               inWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://earth.google.com/kml/2.1\">\n<Folder>\n");
+               inWriter.write("\t<name>");
+               if (_descriptionField != null && _descriptionField.getText() != null && !_descriptionField.getText().equals(""))
+               {
+                       inWriter.write(_descriptionField.getText());
+               }
+               else
+               {
+                       inWriter.write("Export from Prune");
+               }
+               inWriter.write("</name>\n");
+
+               int i = 0;
+               DataPoint point = null;
+               boolean hasTrackpoints = false;
+               // Loop over waypoints
+               boolean writtenPhotoHeader = false;
+               int numPoints = _track.getNumPoints();
+               int photoNum = 0;
+               for (i=0; i<numPoints; i++)
+               {
+                       point = _track.getPoint(i);
+                       // Make a blob for each waypoint
+                       if (point.isWaypoint())
+                       {
+                               exportWaypoint(point, inWriter);
+                       }
+                       // Make a blob with description for each photo
+                       if (point.getPhoto() != null)
+                       {
+                               if (!writtenPhotoHeader)
+                               {
+                                       inWriter.write("<Style id=\"camera_icon\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/pal4/icon46.png</href></Icon></IconStyle></Style>");
+                                       writtenPhotoHeader = true;
+                               }
+                               photoNum++;
+                               exportPhotoPoint(point, inWriter, inExportImages, photoNum);
+                       }
+                       else
+                       {
+                               hasTrackpoints = true;
+                       }
+               }
+               // Make a line for the track, if there is one
+               if (hasTrackpoints)
+               {
+                       inWriter.write("\t<Placemark>\n\t\t<name>track</name>\n\t\t<Style>\n\t\t\t<LineStyle>\n"
+                               + "\t\t\t\t<color>cc0000cc</color>\n\t\t\t\t<width>4</width>\n\t\t\t</LineStyle>\n"
+                               + "\t\t</Style>\n\t\t<LineString>\n\t\t\t<coordinates>");
+                       // Loop over track points
+                       for (i=0; i<numPoints; i++)
+                       {
+                               point = _track.getPoint(i);
+                               if (!point.isWaypoint())
+                               {
+                                       exportTrackpoint(point, inWriter);
+                               }
+                       }
+                       inWriter.write("\t\t\t</coordinates>\n\t\t</LineString>\n\t</Placemark>");
+               }
+               inWriter.write("</Folder>\n</kml>");
+               return numPoints;
        }
 
 
@@ -214,14 +415,26 @@ public class KmlExporter
         * Export the specified photo into the file
         * @param inPoint data point including photo
         * @param inWriter writer object
+        * @param inImageLink flag to set whether to export image links or not
+        * @param inImageNumber number of image for filename
         * @throws IOException on write failure
         */
-       private void exportPhotoPoint(DataPoint inPoint, Writer inWriter) throws IOException
+       private void exportPhotoPoint(DataPoint inPoint, Writer inWriter, boolean inImageLink, int inImageNumber)
+       throws IOException
        {
-               // TODO: Export photos to KML too - for photos need kmz!
                inWriter.write("\t<Placemark>\n\t\t<name>");
                inWriter.write(inPoint.getPhoto().getFile().getName());
                inWriter.write("</name>\n");
+               if (inImageLink)
+               {
+                       // Work out image dimensions of thumbnail
+                       Dimension picSize = inPoint.getPhoto().getSize();
+                       Dimension thumbSize = ImageUtils.getThumbnailSize(picSize.width, picSize.height, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
+                       // Write out some html for the thumbnail images
+                       inWriter.write("<description><![CDATA[<br/><table border='0'><tr><td><center><img src='images/image"
+                               + inImageNumber + ".jpg' width='" + thumbSize.width + "' height='" + thumbSize.height + "'></center></td></tr>"
+                               + "<tr><td><center>Caption for the photo</center></td></tr></table>]]></description>");
+               }
                inWriter.write("<styleUrl>#camera_icon</styleUrl>\n");
                inWriter.write("\t\t<Point>\n\t\t\t<coordinates>");
                inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL));
@@ -244,4 +457,72 @@ public class KmlExporter
                // Altitude not exported, locked to ground by Google Earth
                inWriter.write(",0\n");
        }
+
+
+       /**
+        * Loop through the photos and create thumbnails
+        * @param inZipStream zip stream to save image files to
+        */
+       private void exportThumbnails(ZipOutputStream inZipStream) throws IOException
+       {
+               // set up image writer
+               Iterator writers = ImageIO.getImageWritersByFormatName("jpg");
+               if (writers == null || !writers.hasNext())
+               {
+                       throw new IOException("no JPEG writer found");
+               }
+               ImageWriter imageWriter = (ImageWriter) writers.next();
+
+               int numPoints = _track.getNumPoints();
+               DataPoint point = null;
+               int photoNum = 0;
+               // Loop over all points in track
+               for (int i=0; i<numPoints; i++)
+               {
+                       point = _track.getPoint(i);
+                       if (point.getPhoto() != null)
+                       {
+                               photoNum++;
+                               // Make a new entry in zip file
+                               ZipEntry entry = new ZipEntry("images/image" + photoNum + ".jpg");
+                               inZipStream.putNextEntry(entry);
+                               // Load image and write to outstream
+                               ImageIcon icon = new ImageIcon(point.getPhoto().getFile().getAbsolutePath());
+
+                               // Scale and smooth image to required size
+                               Dimension outputSize = ImageUtils.getThumbnailSize(
+                                       point.getPhoto().getWidth(), point.getPhoto().getHeight(),
+                                       THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
+                               BufferedImage bufferedImage = ImageUtils.createScaledImage(icon.getImage(), outputSize.width, outputSize.height);
+
+                               imageWriter.setOutput(ImageIO.createImageOutputStream(inZipStream));
+                               imageWriter.write(bufferedImage);
+                               // Close zip file entry
+                               inZipStream.closeEntry();
+                               // Update progress bar
+                               _progressBar.setValue(photoNum+1);
+                       }
+               }
+       }
+
+
+       /**
+        * @return number of correlated photos in the track
+        */
+       private int getNumPhotosToExport()
+       {
+               int numPoints = _track.getNumPoints();
+               int numPhotos = 0;
+               DataPoint point = null;
+               // Loop over all points in track
+               for (int i=0; i<numPoints; i++)
+               {
+                       point = _track.getPoint(i);
+                       if (point.getPhoto() != null)
+                       {
+                               numPhotos++;
+                       }
+               }
+               return numPhotos;
+       }
 }