Index: applications/editors/josm/plugins/photo_geotagging/README
===================================================================
--- applications/editors/josm/plugins/photo_geotagging/README	(revision 19664)
+++ applications/editors/josm/plugins/photo_geotagging/README	(revision 19671)
@@ -1,3 +1,15 @@
 README 
 ======
-nothing here yet
+This plugin is used to write latitude and longitude information
+to the EXIF header of jpg files.
+It extends the core geoimage feature of JOSM by adding a new entry
+to the right click menu of any image layer.
+
+The real work (writing lat/lon values to file) is done by the pure Java
+sanselan library.
+
+Note: Although sanselan is a proper apache commons project, there was not much activity recently (End of 2009). So let's hope, it is stable enough and gets fixed, if necessary.
+
+Author: Sebastian Klein
+
+License: GPL v2 or above. (Note: When used together with sanselan library only GPL v3 or above can be used.)
Index: applications/editors/josm/plugins/photo_geotagging/README.template
===================================================================
--- applications/editors/josm/plugins/photo_geotagging/README.template	(revision 19664)
+++ 	(revision )
@@ -1,75 +1,0 @@
-README 
-======
-
-This is a template project structure for a JOSM plugin.
-
-Layout
-======
-+--- src                                source of your plugin
-  |- images                             images your plugin needs
-  |- resources                          resources your plugin needs
-
-  LICENSE                               license file 
-  README                                README for your plugin
-  
-  README.template                       this file 
-  
-  
-Build
-=====  
-A JOSM plugin is built as a single jar. We use ant to build.
-
-See build.xml in this directory and update the plugin specific properties in the
-configuration section.
-  
-
-Maintaining versions
-====================
-There are two versions maintained with each plugin:
-   1) the main version
-      You have to manually set the plugins main version in the build script.
-      Set the property plugin.main.version in build.xml accordingly. 
-
-   2) the build version
-      The build version is unique for every build of the plugin. It is equal
-      to the SVN revision of your plugin directory. 
-
- Both the main version and the build version are included in properties of the plugins
- manifest:
-    Plugin-Version      the build version
-    Plugin-Mainversion  the main version
-
- JOSM automatically detects whether a plugin needs to be upgraded. It compares the build
- version of the currently installed plugin jar with the build version of the plugin jar in 
- the SVN. The main version is irrelevant for this process.  
- 
- Making your plugin available to JOSM users
- ===========================================
- When a plugin jar is checked into SVN a script updates the plugins list on the JOSM wiki:
-   http://josm.openstreetmap.de/wiki/Plugins
- JOSM retrieves the list of available plugins and their build versions from this list.
-
-            commit      publish               read
-                       meta data              meta data 
-      Build  ==>  SVN  =======>  JOSM Wiki   <======= JOSM 
-                   ^ 
-                   ==================================
-                            fetch current plugin jar 
- 
- Note that you have to manually publish (commit) your plugin jar. There is no nightly build
- in place. Everything else (pulishing meta data, updating plugins in the client) is then handled 
- by automatic processes. 
-
-See also
-========
-* Developing Plugins 
-  http://josm.openstreetmap.de/wiki/DevelopingPlugins
-  
-* List of JOSM Plugins
-  http://josm.openstreetmap.de/wiki/Plugins
-  
-  
- 
-     
-
- 
Index: applications/editors/josm/plugins/photo_geotagging/build.xml
===================================================================
--- applications/editors/josm/plugins/photo_geotagging/build.xml	(revision 19664)
+++ applications/editors/josm/plugins/photo_geotagging/build.xml	(revision 19671)
@@ -1,10 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-** This is a template build file for a JOSM  plugin.
-**
-** Maintaining versions
-** ====================
-** see README.template
-**
 ** Usage
 ** =====
@@ -63,8 +57,5 @@
     -->
     <target name="dist" depends="compile,revision">
-        <echo message="creating ${plugin.jar.name} ... "/>
-        <copy todir="${plugin.build.dir}/resources">
-            <fileset dir="resources"/>
-        </copy>
+        <echo message="creating ${ant.project.name}.jar ... "/>
         <copy todir="${plugin.build.dir}/images">
             <fileset dir="images"/>
@@ -73,5 +64,4 @@
             <fileset dir=".">
                 <include name="README" />
-                <include name="LICENSE" />
             </fileset>
         </copy>
@@ -86,10 +76,10 @@
     -->
             <manifest>
-                <attribute name="Author" value="..."/>
-                <attribute name="Plugin-Class" value="..."/>
+                <attribute name="Author" value="Sebastian Klein"/>
+                <attribute name="Plugin-Class" value="org.openstreetmap.josm.plugins.photo_geotagging.GeotaggingPlugin"/>
                 <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
-                <attribute name="Plugin-Description" value="..."/>
-                <attribute name="Plugin-Link" value="..."/>
-                <attribute name="Plugin-Mainversion" value="..."/>
+                <attribute name="Plugin-Description" value="Write gps position info to the image file header. Run this feature from the right click menu of the image layer."/>
+                <attribute name="Plugin-Link" value="http://wiki.openstreetmap.org/wiki/JOSM/Plugins/Photo_Geotagging"/>
+                <attribute name="Plugin-Mainversion" value="2904"/>
                 <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
             </manifest>
Index: applications/editors/josm/plugins/photo_geotagging/src/org/openstreetmap/josm/plugins/photo_geotagging/ExifGPSTagger.java
===================================================================
--- applications/editors/josm/plugins/photo_geotagging/src/org/openstreetmap/josm/plugins/photo_geotagging/ExifGPSTagger.java	(revision 19671)
+++ applications/editors/josm/plugins/photo_geotagging/src/org/openstreetmap/josm/plugins/photo_geotagging/ExifGPSTagger.java	(revision 19671)
@@ -0,0 +1,105 @@
+// This is from a file of the sanselan project that is supposed to show, how the library can be used:
+// https://svn.apache.org/repos/asf/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/sampleUsage/WriteExifMetadataExample.java
+package org.openstreetmap.josm.plugins.photo_geotagging;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+
+import org.apache.sanselan.ImageReadException;
+import org.apache.sanselan.ImageWriteException;
+import org.apache.sanselan.Sanselan;
+import org.apache.sanselan.common.IImageMetadata;
+import org.apache.sanselan.formats.jpeg.JpegImageMetadata;
+import org.apache.sanselan.formats.jpeg.exifRewrite.ExifRewriter;
+import org.apache.sanselan.formats.tiff.TiffImageMetadata;
+import org.apache.sanselan.formats.tiff.write.TiffOutputSet;
+
+public class ExifGPSTagger {
+    /**
+     * Set the GPS values in JPEG EXIF metadata.
+     * This is taken from one of the examples of the sanselan project.
+     * 
+     * @param jpegImageFile
+     *            A source image file.
+     * @param dst
+     *            The output file.
+     * @throws IOException
+     * @throws ImageReadException
+     * @throws ImageWriteException
+     */
+    public static void setExifGPSTag(File jpegImageFile, File dst, double lat, double lon) throws IOException {
+        try {
+            setExifGPSTagWorker(jpegImageFile, dst, lat, lon);
+        } catch (ImageReadException ire) {
+            throw new IOException(tr("Read error!"));
+        } catch (ImageWriteException ire2) {
+            throw new IOException(tr("Write error!"));
+        }
+    }       
+   
+    public static void setExifGPSTagWorker(File jpegImageFile, File dst, double lat, double lon) throws IOException,
+            ImageReadException, ImageWriteException
+    {
+        OutputStream os = null;
+        try
+        {
+            TiffOutputSet outputSet = null;
+
+            // note that metadata might be null if no metadata is found.
+            IImageMetadata metadata = Sanselan.getMetadata(jpegImageFile);
+            JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
+            if (null != jpegMetadata)
+            {
+                // note that exif might be null if no Exif metadata is found.
+                TiffImageMetadata exif = jpegMetadata.getExif();
+
+                if (null != exif)
+                {
+                    // TiffImageMetadata class is immutable (read-only).
+                    // TiffOutputSet class represents the Exif data to write.
+                    //
+                    // Usually, we want to update existing Exif metadata by
+                    // changing
+                    // the values of a few fields, or adding a field.
+                    // In these cases, it is easiest to use getOutputSet() to
+                    // start with a "copy" of the fields read from the image.
+                    outputSet = exif.getOutputSet();
+                }
+            }
+
+            // if file does not contain any exif metadata, we create an empty
+            // set of exif metadata. Otherwise, we keep all of the other
+            // existing tags.
+            if (null == outputSet)
+                outputSet = new TiffOutputSet();
+
+            {
+                outputSet.setGPSInDegrees(lon, lat);
+            }
+
+            os = new FileOutputStream(dst);
+            os = new BufferedOutputStream(os);
+
+            new ExifRewriter().updateExifMetadataLossless(jpegImageFile, os,
+                    outputSet);
+
+            os.close();
+            os = null;
+        } finally
+        {
+            if (os != null)
+                try
+                {
+                    os.close();
+                } catch (IOException e)
+                {
+
+                }
+        }
+    }
+}
Index: applications/editors/josm/plugins/photo_geotagging/src/org/openstreetmap/josm/plugins/photo_geotagging/GeotaggingPlugin.java
===================================================================
--- applications/editors/josm/plugins/photo_geotagging/src/org/openstreetmap/josm/plugins/photo_geotagging/GeotaggingPlugin.java	(revision 19671)
+++ applications/editors/josm/plugins/photo_geotagging/src/org/openstreetmap/josm/plugins/photo_geotagging/GeotaggingPlugin.java	(revision 19671)
@@ -0,0 +1,305 @@
+//License: GPL (v2 or above)
+package org.openstreetmap.josm.plugins.photo_geotagging;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.io.IOException;
+import java.io.File;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractListModel;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.UIManager;
+
+import java.text.DecimalFormat;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
+import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginInformation;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * This plugin is used to write latitude and longitude information
+ * to the EXIF header of jpg files.
+ * It extends the core geoimage feature of JOSM by adding a new entry
+ * to the right click menu of any image layer.
+ * 
+ * The real work (writing lat/lon values to file) is done by the pure Java
+ * sanselan library.
+ */
+public class GeotaggingPlugin extends Plugin {
+    final static boolean debug = false;
+    final static String KEEP_BACKUP = "plugins.photo_geotagging.keep_backup";
+    
+    public GeotaggingPlugin(PluginInformation info) {
+        super(info);
+        GeoImageLayer.registerMenuAddition(new GeotaggingMenuAddition());
+    }
+    
+    class GeotaggingMenuAddition implements GeoImageLayer.LayerMenuAddition {
+        public Component getComponent(Layer layer) {
+            JMenuItem geotaggingItem = new JMenuItem(tr("Write coordinates to image header"), ImageProvider.get("geotagging"));;
+            geotaggingItem.addActionListener(new GeotagImages((GeoImageLayer) layer));
+            return geotaggingItem;
+        }
+    }
+    
+    class GeotagImages implements ActionListener {
+        final private GeoImageLayer layer;
+        public GeotagImages(GeoImageLayer layer) {
+            this.layer = layer;
+        }
+        
+        public void actionPerformed(ActionEvent arg0) {
+            final List<ImageEntry> images = new ArrayList<ImageEntry>();
+            for (ImageEntry e : layer.getImages()) {
+                if (e.getPos() != null) {
+                    images.add(e);
+                }
+            }
+
+            final JPanel cont = new JPanel(new GridBagLayout());
+            cont.add(new JLabel(tr("Write position information into the exif header of the following files:")), GBC.eol());
+            
+            FileList files = new FileList();
+            files.setVisibleRowCount(Math.min(files.getModel().getSize(), 10));
+            final List<String> strs = new ArrayList<String>();
+            DecimalFormat cDdFormatter = new DecimalFormat("###0.000000");
+
+            for (ImageEntry e : images) {
+                strs.add(e.getFile().getAbsolutePath()+" ("+cDdFormatter.format(e.getPos().lat())+","+cDdFormatter.format(e.getPos().lon())+")");
+            }
+            files.getFileListModel().setFiles(strs);
+            JScrollPane scroll = new JScrollPane(files);
+            scroll.setPreferredSize(new Dimension(300, 250));
+            cont.add(scroll, GBC.eol().fill(GBC.BOTH));
+            
+            final JCheckBox backups = new JCheckBox(tr("keep backup files"), Main.pref.getBoolean(KEEP_BACKUP, true));
+            cont.add(backups, GBC.eol());
+            
+            int result = new ExtendedDialog(
+                    Main.parent,
+                    tr("Photo Geotagging Plugin"),
+                    new String[] {tr("OK"), tr("Cancel")})
+                .setButtonIcons(new String[] {"ok.png", "cancel.png"})
+                .setContent(cont)
+                .setCancelButton(2)
+                .setDefaultButton(1)
+                .showDialog()
+                .getValue();
+
+            if (result != 1) 
+                return;
+            
+            final boolean keep_backup = backups.isSelected();
+            Main.pref.put(KEEP_BACKUP, keep_backup);
+
+            Main.worker.execute(new GeoTaggingRunnable(images, keep_backup));
+        }
+    }
+
+    class GeoTaggingRunnable extends PleaseWaitRunnable {
+        private boolean cancelled = false;
+        final private boolean keep_backup;
+        final List<ImageEntry> images;
+        private Boolean override_backup = null;
+
+        private File fileFrom;
+        private File fileTo;
+        private File fileDelete;
+        
+        public GeoTaggingRunnable(List<ImageEntry> images, boolean keep_backup) {
+            super(tr("Photo Geotagging Plugin"));
+            this.images = images;
+            this.keep_backup = keep_backup;
+        }
+        @Override 
+        protected void realRun() {
+            progressMonitor.subTask(tr("Writing position information to image files..."));
+            progressMonitor.setTicksCount(images.size());
+        
+            
+            for (int i=0; i<images.size(); ++i) {
+                if (cancelled) return;
+
+                ImageEntry e = images.get(i);
+                if (debug) {
+                    System.err.print("i:"+i+" "+e.getFile().getName()+" ");
+                }
+                
+                fileFrom = null;
+                fileTo = null;
+                fileDelete = null;
+                    
+                try {
+                    chooseFiles(e.getFile());
+                    if (cancelled) return;
+                    ExifGPSTagger.setExifGPSTag(fileFrom, fileTo, e.getPos().lat(), e.getPos().lon());
+                    cleanupFiles();
+                } catch (IOException ioe) {
+                    ioe.printStackTrace();
+                    JOptionPane.showMessageDialog(Main.parent, tr("Error: ")+ioe.getMessage(), tr("Error"), JOptionPane.ERROR_MESSAGE);
+                    return;
+                }
+                progressMonitor.worked(1);
+                if (debug) {
+                System.err.println("");
+                }
+            }
+        }
+
+        private void chooseFiles(File file) throws IOException {
+            if (debug) {                    
+            System.err.println("f: "+file.getAbsolutePath());
+            }
+
+            if (!keep_backup) {
+                chooseFilesNoBackup(file);
+                return;
+            }
+
+            File fileBackup = new File(file.getParentFile(),file.getName()+"_");
+            if (fileBackup.exists()) {
+                if (debug) {
+                    System.err.println("FILE EXISTS");
+                }
+
+                confirm_override();
+                if (cancelled) 
+                    return;
+                
+                if (override_backup) {
+                    if (!fileBackup.delete())
+                        throw new IOException(tr("File could not be deleted!"));
+                } else {
+                    chooseFilesNoBackup(file);
+                    return;
+                }
+            }
+            if (!file.renameTo(fileBackup))
+                throw new IOException(tr("Could not rename file!"));
+                
+            fileFrom = fileBackup;
+            fileTo = file;
+            fileDelete = null;
+        }
+
+        private void chooseFilesNoBackup(File file) throws IOException {
+            File fileTmp;
+            fileTmp = File.createTempFile("img", ".jpg", file.getParentFile());
+            if (debug) {
+                System.err.println("TMP: "+fileTmp.getAbsolutePath());
+            }
+            if (! file.renameTo(fileTmp))
+                throw new IOException(tr("Could not rename file!"));
+                
+            fileFrom = fileTmp;
+            fileTo = file;
+            fileDelete = fileTmp;
+        }        
+
+        private void confirm_override() {
+            if (override_backup == null) {
+                JLabel l = new JLabel(tr("<html><h3>There are old backup files in the image directory!</h3>"));
+                l.setIcon(UIManager.getIcon("OptionPane.warningIcon"));
+                int override = new ExtendedDialog(
+                        Main.parent,
+                        tr("Override old backup files?"),
+                        new String[] {tr("Cancel"), tr("Keep old backups and continue"), tr("Override")})
+                    .setButtonIcons(new String[] {"cancel.png", "ok.png", "dialogs/delete.png"})
+                    .setContent(l)
+                    .setCancelButton(1)
+                    .setDefaultButton(2)
+                    .showDialog()
+                    .getValue();
+                if (override == 2) {
+                    override_backup = false;
+                } else if (override == 3) {
+                    override_backup = true;
+                } else {
+                    cancelled = true;
+                    return;
+                }
+            }
+        }
+
+        private void cleanupFiles() throws IOException {
+            if (fileDelete != null) {
+                if (!fileDelete.delete()) 
+                    throw new IOException(tr("Could not delete temporary file!"));
+            }
+        }
+        
+        @Override 
+        protected void finish() {
+        }
+        
+        @Override 
+        protected void cancel() {
+            cancelled = true;
+        }
+    }
+    
+    static class FileList extends JList {
+        public FileList() {
+            super(new FileListModel());
+        }
+
+        public FileListModel getFileListModel() {
+            return (FileListModel)getModel();
+        }
+    }
+
+    static class FileListModel extends AbstractListModel{
+        private List<String> files;
+
+        public FileListModel() {
+            files = new ArrayList<String>();
+        }
+
+        public FileListModel(List<String> files) {
+            setFiles(files);
+        }
+
+        public void setFiles(List<String> files) {
+            if (files == null) {
+                this.files = new ArrayList<String>();
+            } else {
+                this.files = files;
+            }
+            fireContentsChanged(this,0,getSize());
+        }
+
+        public Object getElementAt(int index) {
+            if (files == null) return null;
+            return files.get(index);
+        }
+
+        public int getSize() {
+            if (files == null) return 0;
+            return files.size();
+        }
+    }   
+}
