Index: /trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java	(revision 5714)
+++ /trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java	(revision 5715)
@@ -7,6 +7,8 @@
 import java.util.LinkedList;
 import java.util.Map;
+import org.openstreetmap.josm.Main;
 
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
 
 /**
@@ -122,3 +124,117 @@
         return result;
     }
+    
+     /**
+     * Makes a WayPoint at the projection of point P onto the track providing P is less than
+     * tolerance away from the track
+     *
+     * @param P : the point to determine the projection for
+     * @param tolerance : must be no further than this from the track
+     * @return the closest point on the track to P, which may be the first or last point if off the
+     * end of a segment, or may be null if nothing close enough
+     */
+    public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
+        /*
+         * assume the coordinates of P are xp,yp, and those of a section of track between two
+         * trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
+         *
+         * The equation of RS is Ax + By + C = 0 where A = ys - yr B = xr - xs C = - Axr - Byr
+         *
+         * Also, note that the distance RS^2 is A^2 + B^2
+         *
+         * If RS^2 == 0.0 ignore the degenerate section of track
+         *
+         * PN^2 = (Axp + Byp + C)^2 / RS^2 that is the distance from P to the line
+         *
+         * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject the line;
+         * otherwise... determine if the projected poijnt lies within the bounds of the line: PR^2 -
+         * PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
+         *
+         * where PR^2 = (xp - xr)^2 + (yp-yr)^2 and PS^2 = (xp - xs)^2 + (yp-ys)^2
+         *
+         * If so, calculate N as xn = xr + (RN/RS) B yn = y1 + (RN/RS) A
+         *
+         * where RN = sqrt(PR^2 - PN^2)
+         */
+
+        double PNminsq = tolerance * tolerance;
+        EastNorth bestEN = null;
+        double bestTime = 0.0;
+        double px = P.east();
+        double py = P.north();
+        double rx = 0.0, ry = 0.0, sx, sy, x, y;
+        if (tracks == null)
+            return null;
+        for (GpxTrack track : tracks) {
+            for (GpxTrackSegment seg : track.getSegments()) {
+                WayPoint R = null;
+                for (WayPoint S : seg.getWayPoints()) {
+                    EastNorth c = S.getEastNorth();
+                    if (R == null) {
+                        R = S;
+                        rx = c.east();
+                        ry = c.north();
+                        x = px - rx;
+                        y = py - ry;
+                        double PRsq = x * x + y * y;
+                        if (PRsq < PNminsq) {
+                            PNminsq = PRsq;
+                            bestEN = c;
+                            bestTime = R.time;
+                        }
+                    } else {
+                        sx = c.east();
+                        sy = c.north();
+                        double A = sy - ry;
+                        double B = rx - sx;
+                        double C = -A * rx - B * ry;
+                        double RSsq = A * A + B * B;
+                        if (RSsq == 0.0) {
+                            continue;
+                        }
+                        double PNsq = A * px + B * py + C;
+                        PNsq = PNsq * PNsq / RSsq;
+                        if (PNsq < PNminsq) {
+                            x = px - rx;
+                            y = py - ry;
+                            double PRsq = x * x + y * y;
+                            x = px - sx;
+                            y = py - sy;
+                            double PSsq = x * x + y * y;
+                            if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
+                                double RNoverRS = Math.sqrt((PRsq - PNsq) / RSsq);
+                                double nx = rx - RNoverRS * B;
+                                double ny = ry + RNoverRS * A;
+                                bestEN = new EastNorth(nx, ny);
+                                bestTime = R.time + RNoverRS * (S.time - R.time);
+                                PNminsq = PNsq;
+                            }
+                        }
+                        R = S;
+                        rx = sx;
+                        ry = sy;
+                    }
+                }
+                if (R != null) {
+                    EastNorth c = R.getEastNorth();
+                    /* if there is only one point in the seg, it will do this twice, but no matter */
+                    rx = c.east();
+                    ry = c.north();
+                    x = px - rx;
+                    y = py - ry;
+                    double PRsq = x * x + y * y;
+                    if (PRsq < PNminsq) {
+                        PNminsq = PRsq;
+                        bestEN = c;
+                        bestTime = R.time;
+                    }
+                }
+            }
+        }
+        if (bestEN == null)
+            return null;
+        WayPoint best = new WayPoint(Main.getProjection().eastNorth2latlon(bestEN));
+        best.time = bestTime;
+        return best;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 5714)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 5715)
@@ -3,5 +3,4 @@
 package org.openstreetmap.josm.gui.layer;
 
-import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
 import static org.openstreetmap.josm.tools.I18n.marktr;
 import static org.openstreetmap.josm.tools.I18n.tr;
@@ -10,61 +9,24 @@
 import java.awt.BasicStroke;
 import java.awt.Color;
-import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.Graphics2D;
-import java.awt.GridBagLayout;
 import java.awt.Point;
 import java.awt.RenderingHints;
 import java.awt.Stroke;
-import java.awt.Toolkit;
-import java.awt.event.ActionEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.geom.Area;
-import java.awt.geom.Rectangle2D;
 import java.io.File;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
 import java.text.DateFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Future;
-
-import javax.swing.AbstractAction;
+
 import javax.swing.Action;
-import javax.swing.BorderFactory;
 import javax.swing.Icon;
-import javax.swing.JComponent;
-import javax.swing.JFileChooser;
-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.JTable;
-import javax.swing.ListSelectionModel;
 import javax.swing.SwingUtilities;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-import javax.swing.filechooser.FileFilter;
-import javax.swing.table.TableCellRenderer;
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.AbstractMergeAction.LayerListCellRenderer;
-import org.openstreetmap.josm.actions.DiskAccessAction;
 import org.openstreetmap.josm.actions.RenameLayerAction;
 import org.openstreetmap.josm.actions.SaveActionBase;
-import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTaskList;
 import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.gpx.GpxConstants;
@@ -74,47 +36,24 @@
 import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
 import org.openstreetmap.josm.data.gpx.WayPoint;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
 import org.openstreetmap.josm.data.projection.Projection;
-import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
-import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.gui.HelpAwareOptionPane;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.NavigatableComponent;
-import org.openstreetmap.josm.gui.PleaseWaitRunnable;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
-import org.openstreetmap.josm.gui.layer.WMSLayer.PrecacheTask;
-import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
-import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
-import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
-import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
-import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
-import org.openstreetmap.josm.gui.progress.ProgressMonitor;
-import org.openstreetmap.josm.gui.progress.ProgressTaskId;
-import org.openstreetmap.josm.gui.progress.ProgressTaskIds;
+import org.openstreetmap.josm.gui.layer.gpx.ChooseTrackVisibilityAction;
+import org.openstreetmap.josm.gui.layer.gpx.ConvertToDataLayerAction;
+import org.openstreetmap.josm.gui.layer.gpx.CustomizeDrawingAction;
+import org.openstreetmap.josm.gui.layer.gpx.DownloadAlongTrackAction;
+import org.openstreetmap.josm.gui.layer.gpx.DownloadWmsAlongTrackAction;
+import org.openstreetmap.josm.gui.layer.gpx.ImportAudioAction;
+import org.openstreetmap.josm.gui.layer.gpx.ImportImagesAction;
+import org.openstreetmap.josm.gui.layer.gpx.MarkersFromNamedPointsAction;
 import org.openstreetmap.josm.gui.widgets.HtmlPanel;
-import org.openstreetmap.josm.gui.widgets.JFileChooserManager;
-import org.openstreetmap.josm.gui.widgets.JosmComboBox;
 import org.openstreetmap.josm.io.GpxImporter;
-import org.openstreetmap.josm.io.JpgImporter;
-import org.openstreetmap.josm.io.OsmTransferException;
-import org.openstreetmap.josm.tools.AudioUtil;
-import org.openstreetmap.josm.tools.DateUtils;
-import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.OpenBrowser;
-import org.openstreetmap.josm.tools.UrlLabel;
 import org.openstreetmap.josm.tools.Utils;
-import org.openstreetmap.josm.tools.WindowGeometry;
-import org.xml.sax.SAXException;
 
 public class GpxLayer extends Layer {
-
-    private static final String PREF_DOWNLOAD_ALONG_TRACK_DISTANCE = "gpxLayer.downloadAlongTrack.distance";
-    private static final String PREF_DOWNLOAD_ALONG_TRACK_AREA = "gpxLayer.downloadAlongTrack.area";
-    private static final String PREF_DOWNLOAD_ALONG_TRACK_NEAR = "gpxLayer.downloadAlongTrack.near";
 
     public GpxData data;
@@ -128,13 +67,8 @@
     private boolean isLocalFile;
     // used by ChooseTrackVisibilityAction to determine which tracks to show/hide
-    private boolean[] trackVisibility = new boolean[0];
+    public boolean[] trackVisibility = new boolean[0];
 
     private final List<GpxTrack> lastTracks = new ArrayList<GpxTrack>(); // List of tracks at last paint
     private int lastUpdateCount;
-
-    private static class Markers {
-        public boolean timedMarkersOmitted = false;
-        public boolean untimedMarkersOmitted = false;
-    }
 
     public GpxLayer(GpxData d) {
@@ -159,5 +93,5 @@
      * returns a human readable string that shows the timespan of the given track
      */
-    private static String getTimespanForTrack(GpxTrack trk) {
+    public static String getTimespanForTrack(GpxTrack trk) {
         WayPoint earliest = null, latest = null;
 
@@ -286,5 +220,5 @@
     @Override
     public Action[] getMenuEntries() {
-        if (Main.applet)
+        if (Main.applet) {
             return new Action[] {
                 LayerListDialog.getInstance().createShowHideLayerAction(),
@@ -292,11 +226,12 @@
                 SeparatorLayerAction.INSTANCE,
                 new CustomizeColor(this),
-                new CustomizeDrawing(this),
-                new ConvertToDataLayerAction(),
+                new CustomizeDrawingAction(this),
+                new ConvertToDataLayerAction(this),
                 SeparatorLayerAction.INSTANCE,
-                new ChooseTrackVisibilityAction(),
+                new ChooseTrackVisibilityAction(this),
                 new RenameLayerAction(getAssociatedFile(), this),
                 SeparatorLayerAction.INSTANCE,
                 new LayerListPopup.InfoAction(this) };
+        }
         return new Action[] {
                 LayerListDialog.getInstance().createShowHideLayerAction(),
@@ -306,16 +241,20 @@
                 new LayerSaveAsAction(this),
                 new CustomizeColor(this),
-                new CustomizeDrawing(this),
-                new ImportImages(),
-                new ImportAudio(),
-                new MarkersFromNamedPoins(),
-                new ConvertToDataLayerAction(),
-                new DownloadAlongTrackAction(),
-                new DownloadWmsAlongTrackAction(),
+                new CustomizeDrawingAction(this),
+                new ImportImagesAction(this),
+                new ImportAudioAction(this),
+                new MarkersFromNamedPointsAction(this),
+                new ConvertToDataLayerAction(this),
+                new DownloadAlongTrackAction(data),
+                new DownloadWmsAlongTrackAction(data),
                 SeparatorLayerAction.INSTANCE,
-                new ChooseTrackVisibilityAction(),
+                new ChooseTrackVisibilityAction(this),
                 new RenameLayerAction(getAssociatedFile(), this),
                 SeparatorLayerAction.INSTANCE,
                 new LayerListPopup.InfoAction(this) };
+    }
+    
+    public boolean isLocalFile() {
+        return isLocalFile;
     }
 
@@ -829,46 +768,4 @@
     }
 
-    public class ConvertToDataLayerAction extends AbstractAction {
-        public ConvertToDataLayerAction() {
-            super(tr("Convert to data layer"), ImageProvider.get("converttoosm"));
-            putValue("help", ht("/Action/ConvertToDataLayer"));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            JPanel msg = new JPanel(new GridBagLayout());
-            msg
-            .add(
-                    new JLabel(
-                            tr("<html>Upload of unprocessed GPS data as map data is considered harmful.<br>If you want to upload traces, look here:</html>")),
-                            GBC.eol());
-            msg.add(new UrlLabel(tr("http://www.openstreetmap.org/traces"),2), GBC.eop());
-            if (!ConditionalOptionPaneUtil.showConfirmationDialog("convert_to_data", Main.parent, msg, tr("Warning"),
-                    JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, JOptionPane.OK_OPTION))
-                return;
-            DataSet ds = new DataSet();
-            for (GpxTrack trk : data.tracks) {
-                for (GpxTrackSegment segment : trk.getSegments()) {
-                    List<Node> nodes = new ArrayList<Node>();
-                    for (WayPoint p : segment.getWayPoints()) {
-                        Node n = new Node(p.getCoor());
-                        String timestr = p.getString("time");
-                        if (timestr != null) {
-                            n.setTimestamp(DateUtils.fromString(timestr));
-                        }
-                        ds.addPrimitive(n);
-                        nodes.add(n);
-                    }
-                    Way w = new Way();
-                    w.setNodes(nodes);
-                    ds.addPrimitive(w);
-                }
-            }
-            Main.main
-            .addLayer(new OsmDataLayer(ds, tr("Converted from: {0}", GpxLayer.this.getName()), getAssociatedFile()));
-            Main.main.removeLayer(GpxLayer.this);
-        }
-    }
-
     @Override
     public File getAssociatedFile() {
@@ -899,1122 +796,4 @@
     }
 
-    /**
-     * allows the user to choose which of the downloaded tracks should be displayed.
-     * they can be chosen from the gpx layer context menu.
-     */
-    public class ChooseTrackVisibilityAction extends AbstractAction {
-        public ChooseTrackVisibilityAction() {
-            super(tr("Choose visible tracks"), ImageProvider.get("dialogs/filter"));
-            putValue("help", ht("/Action/ChooseTrackVisibility"));
-        }
-
-        /**
-         * gathers all available data for the tracks and returns them as array of arrays
-         * in the expected column order  */
-        private Object[][] buildTableContents() {
-            Object[][] tracks = new Object[data.tracks.size()][5];
-            int i = 0;
-            for (GpxTrack trk : data.tracks) {
-                Map<String, Object> attr = trk.getAttributes();
-                String name = (String) (attr.containsKey("name") ? attr.get("name") : "");
-                String desc = (String) (attr.containsKey("desc") ? attr.get("desc") : "");
-                String time = getTimespanForTrack(trk);
-                String length = NavigatableComponent.getSystemOfMeasurement().getDistText(trk.length());
-                String url = (String) (attr.containsKey("url") ? attr.get("url") : "");
-                tracks[i] = new String[] {name, desc, time, length, url};
-                i++;
-            }
-            return tracks;
-        }
-
-        /**
-         * Builds an non-editable table whose 5th column will open a browser when double clicked.
-         * The table will fill its parent. */
-        private JTable buildTable(String[] headers, Object[][] content) {
-            final JTable t = new JTable(content, headers) {
-                @Override
-                public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
-                    Component c = super.prepareRenderer(renderer, row, col);
-                    if (c instanceof JComponent) {
-                        JComponent jc = (JComponent)c;
-                        jc.setToolTipText((String)getValueAt(row, col));
-                    }
-                    return c;
-                }
-
-                @Override
-                public boolean isCellEditable(int rowIndex, int colIndex) {
-                    return false;
-                }
-            };
-            // default column widths
-            t.getColumnModel().getColumn(0).setPreferredWidth(220);
-            t.getColumnModel().getColumn(1).setPreferredWidth(300);
-            t.getColumnModel().getColumn(2).setPreferredWidth(200);
-            t.getColumnModel().getColumn(3).setPreferredWidth(50);
-            t.getColumnModel().getColumn(4).setPreferredWidth(100);
-            // make the link clickable
-            final MouseListener urlOpener = new MouseAdapter() {
-                @Override
-                public void mouseClicked(MouseEvent e) {
-                    if (e.getClickCount() != 2)
-                        return;
-                    JTable t = (JTable)e.getSource();
-                    int col = t.convertColumnIndexToModel(t.columnAtPoint(e.getPoint()));
-                    if(col != 4) // only accept clicks on the URL column
-                        return;
-                    int row = t.rowAtPoint(e.getPoint());
-                    String url = (String) t.getValueAt(row, col);
-                    if (url == null || url.isEmpty())
-                        return;
-                    OpenBrowser.displayUrl(url);
-                }
-            };
-            t.addMouseListener(urlOpener);
-            t.setFillsViewportHeight(true);
-            return t;
-        }
-
-        /** selects all rows (=tracks) in the table that are currently visible */
-        private void selectVisibleTracksInTable(JTable table) {
-            // don't select any tracks if the layer is not visible
-            if(!isVisible())
-                return;
-            ListSelectionModel s = table.getSelectionModel();
-            s.clearSelection();
-            for(int i=0; i < trackVisibility.length; i++)
-                if(trackVisibility[i]) {
-                    s.addSelectionInterval(i, i);
-                }
-        }
-
-        /** listens to selection changes in the table and redraws the map */
-        private void listenToSelectionChanges(JTable table) {
-            table.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
-                public void valueChanged(ListSelectionEvent e) {
-                    if(!(e.getSource() instanceof ListSelectionModel))
-                        return;
-
-                    ListSelectionModel s =  (ListSelectionModel) e.getSource();
-                    for(int i = 0; i < data.tracks.size(); i++) {
-                        trackVisibility[i] = s.isSelectedIndex(i);
-                    }
-                    Main.map.mapView.preferenceChanged(null);
-                    Main.map.repaint(100);
-                }
-            });
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent arg0) {
-            final JPanel msg = new JPanel(new GridBagLayout());
-            msg.add(new JLabel(tr("<html>Select all tracks that you want to be displayed. You can drag select a "
-                    + "range of tracks or use CTRL+Click to select specific ones. The map is updated live in the "
-                    + "background. Open the URLs by double clicking them.</html>")),
-                    GBC.eol().fill(GBC.HORIZONTAL));
-
-            // build table
-            final boolean[] trackVisibilityBackup = trackVisibility.clone();
-            final String[] headers = {tr("Name"), tr("Description"), tr("Timespan"), tr("Length"), tr("URL")};
-            final JTable table = buildTable(headers, buildTableContents());
-            selectVisibleTracksInTable(table);
-            listenToSelectionChanges(table);
-
-            // make the table scrollable
-            JScrollPane scrollPane = new JScrollPane(table);
-            msg.add(scrollPane, GBC.eol().fill(GBC.BOTH));
-
-            // build dialog
-            ExtendedDialog ed = new ExtendedDialog(
-                    Main.parent, tr("Set track visibility for {0}", getName()),
-                    new String[] {tr("Show all"), tr("Show selected only"), tr("Cancel")});
-            ed.setButtonIcons(new String[] {"dialogs/layerlist/eye", "dialogs/filter", "cancel"});
-            ed.setContent(msg, false);
-            ed.setDefaultButton(2);
-            ed.setCancelButton(3);
-            ed.configureContextsensitiveHelp("/Action/ChooseTrackVisibility", true);
-            ed.setRememberWindowGeometry(
-                    getClass().getName() + ".geometry",
-                    WindowGeometry.centerInWindow(Main.parent, new Dimension(1000, 500))
-                    );
-            ed.showDialog();
-            int v = ed.getValue();
-            // cancel for unknown buttons and copy back original settings
-            if(v != 1 && v != 2) {
-                for(int i = 0; i < data.tracks.size(); i++) {
-                    trackVisibility[i] = trackVisibilityBackup[i];
-                }
-                Main.map.repaint();
-                return;
-            }
-
-            // set visibility (1 = show all, 2 = filter). If no tracks are selected
-            // set all of them visible and...
-            ListSelectionModel s = table.getSelectionModel();
-            final boolean all = v == 1 || s.isSelectionEmpty();
-            for(int i = 0; i < data.tracks.size(); i++) {
-                trackVisibility[i] = all || s.isSelectedIndex(i);
-            }
-            // ...sync with layer visibility instead to avoid having two ways to hide everything
-            setVisible(v == 1 || !s.isSelectionEmpty());
-            Main.map.repaint();
-        }
-    }
-
-    /**
-     * Action that issues a series of download requests to the API, following the GPX track.
-     *
-     * @author fred
-     */
-    public class DownloadAlongTrackAction extends AbstractAction {
-        final static int NEAR_TRACK=0;
-        final static int NEAR_WAYPOINTS=1;
-        final static int NEAR_BOTH=2;
-        final Integer dist[] = { 5000, 500, 50 };
-        final Integer area[] = { 20, 10, 5, 1 };
-
-        public DownloadAlongTrackAction() {
-            super(tr("Download from OSM along this track"), ImageProvider.get("downloadalongtrack"));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            /*
-             * build selection dialog
-             */
-            JPanel msg = new JPanel(new GridBagLayout());
-
-            msg.add(new JLabel(tr("Download everything within:")), GBC.eol());
-            String s[] = new String[dist.length];
-            for (int i = 0; i < dist.length; ++i) {
-                s[i] = tr("{0} meters", dist[i]);
-            }
-            JList buffer = new JList(s);
-            buffer.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, 0));
-            msg.add(buffer, GBC.eol());
-
-            msg.add(new JLabel(tr("Maximum area per request:")), GBC.eol());
-            s = new String[area.length];
-            for (int i = 0; i < area.length; ++i) {
-                s[i] = tr("{0} sq km", area[i]);
-            }
-            JList maxRect = new JList(s);
-            maxRect.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, 0));
-            msg.add(maxRect, GBC.eol());
-
-            msg.add(new JLabel(tr("Download near:")), GBC.eol());
-            JList downloadNear = new JList(new String[] { tr("track only"), tr("waypoints only"), tr("track and waypoints") });
-
-            downloadNear.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, 0));
-            msg.add(downloadNear, GBC.eol());
-
-            int ret = JOptionPane.showConfirmDialog(
-                    Main.parent,
-                    msg,
-                    tr("Download from OSM along this track"),
-                    JOptionPane.OK_CANCEL_OPTION,
-                    JOptionPane.QUESTION_MESSAGE
-                    );
-            switch(ret) {
-            case JOptionPane.CANCEL_OPTION:
-            case JOptionPane.CLOSED_OPTION:
-                return;
-            default:
-                // continue
-            }
-
-            Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, buffer.getSelectedIndex());
-            Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, maxRect.getSelectedIndex());
-            final int near = downloadNear.getSelectedIndex();
-            Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, near);
-
-            /*
-             * Find the average latitude for the data we're contemplating, so we can know how many
-             * metres per degree of longitude we have.
-             */
-            double latsum = 0;
-            int latcnt = 0;
-
-            if (near == NEAR_TRACK || near == NEAR_BOTH) {
-                for (GpxTrack trk : data.tracks) {
-                    for (GpxTrackSegment segment : trk.getSegments()) {
-                        for (WayPoint p : segment.getWayPoints()) {
-                            latsum += p.getCoor().lat();
-                            latcnt++;
-                        }
-                    }
-                }
-            }
-
-            if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
-                for (WayPoint p : data.waypoints) {
-                    latsum += p.getCoor().lat();
-                    latcnt++;
-                }
-            }
-
-            double avglat = latsum / latcnt;
-            double scale = Math.cos(Math.toRadians(avglat));
-
-            /*
-             * Compute buffer zone extents and maximum bounding box size. Note that the maximum we
-             * ever offer is a bbox area of 0.002, while the API theoretically supports 0.25, but as
-             * soon as you touch any built-up area, that kind of bounding box will download forever
-             * and then stop because it has more than 50k nodes.
-             */
-            Integer i = buffer.getSelectedIndex();
-            final int buffer_dist = dist[i < 0 ? 0 : i];
-            i = maxRect.getSelectedIndex();
-            final double max_area = area[i < 0 ? 0 : i] / 10000.0 / scale;
-            final double buffer_y = buffer_dist / 100000.0;
-            final double buffer_x = buffer_y / scale;
-
-            final int totalTicks = latcnt;
-            // guess if a progress bar might be useful.
-            final boolean displayProgress = totalTicks > 2000 && buffer_y < 0.01;
-
-            class CalculateDownloadArea extends PleaseWaitRunnable {
-                private Area a = new Area();
-                private boolean cancel = false;
-                private int ticks = 0;
-                private Rectangle2D r = new Rectangle2D.Double();
-
-                public CalculateDownloadArea() {
-                    super(tr("Calculating Download Area"),
-                            (displayProgress ? null : NullProgressMonitor.INSTANCE),
-                            false);
-                }
-
-                @Override
-                protected void cancel() {
-                    cancel = true;
-                }
-
-                @Override
-                protected void finish() {
-                }
-
-                @Override
-                protected void afterFinish() {
-                    if(cancel)
-                        return;
-                    confirmAndDownloadAreas(a, max_area, progressMonitor);
-                }
-
-                /**
-                 * increase tick count by one, report progress every 100 ticks
-                 */
-                private void tick() {
-                    ticks++;
-                    if(ticks % 100 == 0) {
-                        progressMonitor.worked(100);
-                    }
-                }
-
-                /**
-                 * calculate area for single, given way point and return new LatLon if the
-                 * way point has been used to modify the area.
-                 */
-                private LatLon calcAreaForWayPoint(WayPoint p, LatLon previous) {
-                    tick();
-                    LatLon c = p.getCoor();
-                    if (previous == null || c.greatCircleDistance(previous) > buffer_dist) {
-                        // we add a buffer around the point.
-                        r.setRect(c.lon() - buffer_x, c.lat() - buffer_y, 2 * buffer_x, 2 * buffer_y);
-                        a.add(new Area(r));
-                        return c;
-                    }
-                    return previous;
-                }
-
-                @Override
-                protected void realRun() {
-                    progressMonitor.setTicksCount(totalTicks);
-                    /*
-                     * Collect the combined area of all gpx points plus buffer zones around them. We ignore
-                     * points that lie closer to the previous point than the given buffer size because
-                     * otherwise this operation takes ages.
-                     */
-                    LatLon previous = null;
-                    if (near == NEAR_TRACK || near == NEAR_BOTH) {
-                        for (GpxTrack trk : data.tracks) {
-                            for (GpxTrackSegment segment : trk.getSegments()) {
-                                for (WayPoint p : segment.getWayPoints()) {
-                                    if(cancel)
-                                        return;
-                                    previous = calcAreaForWayPoint(p, previous);
-                                }
-                            }
-                        }
-                    }
-                    if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
-                        for (WayPoint p : data.waypoints) {
-                            if(cancel)
-                                return;
-                            previous = calcAreaForWayPoint(p, previous);
-                        }
-                    }
-                }
-            }
-
-            Main.worker.submit(new CalculateDownloadArea());
-        }
-
-
-        /**
-         * Area "a" contains the hull that we would like to download data for. however we
-         * can only download rectangles, so the following is an attempt at finding a number of
-         * rectangles to download.
-         *
-         * The idea is simply: Start out with the full bounding box. If it is too large, then
-         * split it in half and repeat recursively for each half until you arrive at something
-         * small enough to download. The algorithm is improved by always using the intersection
-         * between the rectangle and the actual desired area. For example, if you have a track
-         * that goes like this: +----+ | /| | / | | / | |/ | +----+ then we would first look at
-         * downloading the whole rectangle (assume it's too big), after that we split it in half
-         * (upper and lower half), but we donot request the full upper and lower rectangle, only
-         * the part of the upper/lower rectangle that actually has something in it.
-         *
-         * This functions calculates the rectangles, asks the user to continue and downloads
-         * the areas if applicable.
-         */
-        private void confirmAndDownloadAreas(Area a, double max_area, ProgressMonitor progressMonitor) {
-            List<Rectangle2D> toDownload = new ArrayList<Rectangle2D>();
-
-            addToDownload(a, a.getBounds(), toDownload, max_area);
-
-            if(toDownload.size() == 0)
-                return;
-
-            JPanel msg = new JPanel(new GridBagLayout());
-
-            msg.add(new JLabel(
-                    tr("<html>This action will require {0} individual<br>"
-                            + "download requests. Do you wish<br>to continue?</html>",
-                            toDownload.size())), GBC.eol());
-
-            if (toDownload.size() > 1) {
-                int ret = JOptionPane.showConfirmDialog(
-                        Main.parent,
-                        msg,
-                        tr("Download from OSM along this track"),
-                        JOptionPane.OK_CANCEL_OPTION,
-                        JOptionPane.PLAIN_MESSAGE
-                        );
-                switch(ret) {
-                case JOptionPane.CANCEL_OPTION:
-                case JOptionPane.CLOSED_OPTION:
-                    return;
-                default:
-                    // continue
-                }
-            }
-            final PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Download data"));
-            final Future<?> future = new DownloadOsmTaskList().download(false, toDownload, monitor);
-            Main.worker.submit(
-                    new Runnable() {
-                        @Override
-                        public void run() {
-                            try {
-                                future.get();
-                            } catch(Exception e) {
-                                e.printStackTrace();
-                                return;
-                            }
-                            monitor.close();
-                        }
-                    }
-                    );
-        }
-    }
-
-
-    public class DownloadWmsAlongTrackAction extends AbstractAction {
-        public DownloadWmsAlongTrackAction() {
-            super(tr("Precache imagery tiles along this track"), ImageProvider.get("downloadalongtrack"));
-        }
-
-        public void actionPerformed(ActionEvent e) {
-
-            final List<LatLon> points = new ArrayList<LatLon>();
-
-            for (GpxTrack trk : data.tracks) {
-                for (GpxTrackSegment segment : trk.getSegments()) {
-                    for (WayPoint p : segment.getWayPoints()) {
-                        points.add(p.getCoor());
-                    }
-                }
-            }
-            for (WayPoint p : data.waypoints) {
-                points.add(p.getCoor());
-            }
-
-
-            final WMSLayer layer = askWMSLayer();
-            if (layer != null) {
-                PleaseWaitRunnable task = new PleaseWaitRunnable(tr("Precaching WMS")) {
-
-                    private PrecacheTask precacheTask;
-
-                    @Override
-                    protected void realRun() throws SAXException, IOException, OsmTransferException {
-                        precacheTask = new PrecacheTask(progressMonitor);
-                        layer.downloadAreaToCache(precacheTask, points, 0, 0);
-                        while (!precacheTask.isFinished() && !progressMonitor.isCanceled()) {
-                            synchronized (this) {
-                                try {
-                                    wait(200);
-                                } catch (InterruptedException e) {
-                                    e.printStackTrace();
-                                }
-                            }
-                        }
-                    }
-
-                    @Override
-                    protected void finish() {
-                    }
-
-                    @Override
-                    protected void cancel() {
-                        precacheTask.cancel();
-                    }
-
-                    @Override
-                    public ProgressTaskId canRunInBackground() {
-                        return ProgressTaskIds.PRECACHE_WMS;
-                    }
-                };
-                Main.worker.execute(task);
-            }
-
-
-        }
-
-        protected WMSLayer askWMSLayer() {
-            List<WMSLayer> targetLayers = Main.map.mapView.getLayersOfType(WMSLayer.class);
-
-            if (targetLayers.isEmpty()) {
-                warnNoImageryLayers();
-                return null;
-            }
-
-            JosmComboBox layerList = new JosmComboBox(targetLayers.toArray());
-            layerList.setRenderer(new LayerListCellRenderer());
-            layerList.setSelectedIndex(0);
-
-            JPanel pnl = new JPanel(new GridBagLayout());
-            pnl.add(new JLabel(tr("Please select the imagery layer.")), GBC.eol());
-            pnl.add(layerList, GBC.eol());
-
-            ExtendedDialog ed = new ExtendedDialog(Main.parent,
-                    tr("Select imagery layer"),
-                    new String[] { tr("Download"), tr("Cancel") });
-            ed.setButtonIcons(new String[] { "dialogs/down", "cancel" });
-            ed.setContent(pnl);
-            ed.showDialog();
-            if (ed.getValue() != 1)
-                return null;
-
-            return (WMSLayer) layerList.getSelectedItem();
-        }
-
-        protected void warnNoImageryLayers() {
-            JOptionPane.showMessageDialog(Main.parent,
-                    tr("There are no imagery layers."),
-                    tr("No imagery layers"), JOptionPane.WARNING_MESSAGE);
-        }
-    }
-
-    private static void addToDownload(Area a, Rectangle2D r, Collection<Rectangle2D> results, double max_area) {
-        Area tmp = new Area(r);
-        // intersect with sought-after area
-        tmp.intersect(a);
-        if (tmp.isEmpty())
-            return;
-        Rectangle2D bounds = tmp.getBounds2D();
-        if (bounds.getWidth() * bounds.getHeight() > max_area) {
-            // the rectangle gets too large; split it and make recursive call.
-            Rectangle2D r1;
-            Rectangle2D r2;
-            if (bounds.getWidth() > bounds.getHeight()) {
-                // rectangles that are wider than high are split into a left and right half,
-                r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth() / 2, bounds.getHeight());
-                r2 = new Rectangle2D.Double(bounds.getX() + bounds.getWidth() / 2, bounds.getY(),
-                        bounds.getWidth() / 2, bounds.getHeight());
-            } else {
-                // others into a top and bottom half.
-                r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() / 2);
-                r2 = new Rectangle2D.Double(bounds.getX(), bounds.getY() + bounds.getHeight() / 2, bounds.getWidth(),
-                        bounds.getHeight() / 2);
-            }
-            addToDownload(a, r1, results, max_area);
-            addToDownload(a, r2, results, max_area);
-        } else {
-            results.add(bounds);
-        }
-    }
-
-    /**
-     * Makes a new marker layer derived from this GpxLayer containing at least one audio marker
-     * which the given audio file is associated with. Markers are derived from the following (a)
-     * explict waypoints in the GPX layer, or (b) named trackpoints in the GPX layer, or (d)
-     * timestamp on the wav file (e) (in future) voice recognised markers in the sound recording (f)
-     * a single marker at the beginning of the track
-     * @param wavFile : the file to be associated with the markers in the new marker layer
-     * @param markers : keeps track of warning messages to avoid repeated warnings
-     */
-    private void importAudio(File wavFile, MarkerLayer ml, double firstStartTime, Markers markers) {
-        URL url = null;
-        try {
-            url = wavFile.toURI().toURL();
-        } catch (MalformedURLException e) {
-            System.err.println("Unable to convert filename " + wavFile.getAbsolutePath() + " to URL");
-        }
-        Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
-        boolean timedMarkersOmitted = false;
-        boolean untimedMarkersOmitted = false;
-        double snapDistance = Main.pref.getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3); /*
-         * about
-         * 25
-         * m
-         */
-        WayPoint wayPointFromTimeStamp = null;
-
-        // determine time of first point in track
-        double firstTime = -1.0;
-        if (data.tracks != null && !data.tracks.isEmpty()) {
-            for (GpxTrack track : data.tracks) {
-                for (GpxTrackSegment seg : track.getSegments()) {
-                    for (WayPoint w : seg.getWayPoints()) {
-                        firstTime = w.time;
-                        break;
-                    }
-                    if (firstTime >= 0.0) {
-                        break;
-                    }
-                }
-                if (firstTime >= 0.0) {
-                    break;
-                }
-            }
-        }
-        if (firstTime < 0.0) {
-            JOptionPane.showMessageDialog(
-                    Main.parent,
-                    tr("No GPX track available in layer to associate audio with."),
-                    tr("Error"),
-                    JOptionPane.ERROR_MESSAGE
-                    );
-            return;
-        }
-
-        // (a) try explicit timestamped waypoints - unless suppressed
-        if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) && data.waypoints != null
-                && !data.waypoints.isEmpty()) {
-            for (WayPoint w : data.waypoints) {
-                if (w.time > firstTime) {
-                    waypoints.add(w);
-                } else if (w.time > 0.0) {
-                    timedMarkersOmitted = true;
-                }
-            }
-        }
-
-        // (b) try explicit waypoints without timestamps - unless suppressed
-        if (Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true) && data.waypoints != null
-                && !data.waypoints.isEmpty()) {
-            for (WayPoint w : data.waypoints) {
-                if (waypoints.contains(w)) {
-                    continue;
-                }
-                WayPoint wNear = nearestPointOnTrack(w.getEastNorth(), snapDistance);
-                if (wNear != null) {
-                    WayPoint wc = new WayPoint(w.getCoor());
-                    wc.time = wNear.time;
-                    if (w.attr.containsKey("name")) {
-                        wc.attr.put("name", w.getString("name"));
-                    }
-                    waypoints.add(wc);
-                } else {
-                    untimedMarkersOmitted = true;
-                }
-            }
-        }
-
-        // (c) use explicitly named track points, again unless suppressed
-        if ((Main.pref.getBoolean("marker.audiofromnamedtrackpoints", false)) && data.tracks != null
-                && !data.tracks.isEmpty()) {
-            for (GpxTrack track : data.tracks) {
-                for (GpxTrackSegment seg : track.getSegments()) {
-                    for (WayPoint w : seg.getWayPoints()) {
-                        if (w.attr.containsKey("name") || w.attr.containsKey("desc")) {
-                            waypoints.add(w);
-                        }
-                    }
-                }
-            }
-        }
-
-        // (d) use timestamp of file as location on track
-        if ((Main.pref.getBoolean("marker.audiofromwavtimestamps", false)) && data.tracks != null
-                && !data.tracks.isEmpty()) {
-            double lastModified = wavFile.lastModified() / 1000.0; // lastModified is in
-            // milliseconds
-            double duration = AudioUtil.getCalibratedDuration(wavFile);
-            double startTime = lastModified - duration;
-            startTime = firstStartTime + (startTime - firstStartTime)
-                    / Main.pref.getDouble("audio.calibration", "1.0" /* default, ratio */);
-            WayPoint w1 = null;
-            WayPoint w2 = null;
-
-            for (GpxTrack track : data.tracks) {
-                for (GpxTrackSegment seg : track.getSegments()) {
-                    for (WayPoint w : seg.getWayPoints()) {
-                        if (startTime < w.time) {
-                            w2 = w;
-                            break;
-                        }
-                        w1 = w;
-                    }
-                    if (w2 != null) {
-                        break;
-                    }
-                }
-            }
-
-            if (w1 == null || w2 == null) {
-                timedMarkersOmitted = true;
-            } else {
-                wayPointFromTimeStamp = new WayPoint(w1.getCoor().interpolate(w2.getCoor(),
-                        (startTime - w1.time) / (w2.time - w1.time)));
-                wayPointFromTimeStamp.time = startTime;
-                String name = wavFile.getName();
-                int dot = name.lastIndexOf(".");
-                if (dot > 0) {
-                    name = name.substring(0, dot);
-                }
-                wayPointFromTimeStamp.attr.put("name", name);
-                waypoints.add(wayPointFromTimeStamp);
-            }
-        }
-
-        // (e) analyse audio for spoken markers here, in due course
-
-        // (f) simply add a single marker at the start of the track
-        if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) && data.tracks != null
-                && !data.tracks.isEmpty()) {
-            boolean gotOne = false;
-            for (GpxTrack track : data.tracks) {
-                for (GpxTrackSegment seg : track.getSegments()) {
-                    for (WayPoint w : seg.getWayPoints()) {
-                        WayPoint wStart = new WayPoint(w.getCoor());
-                        wStart.attr.put("name", "start");
-                        wStart.time = w.time;
-                        waypoints.add(wStart);
-                        gotOne = true;
-                        break;
-                    }
-                    if (gotOne) {
-                        break;
-                    }
-                }
-                if (gotOne) {
-                    break;
-                }
-            }
-        }
-
-        /* we must have got at least one waypoint now */
-
-        Collections.sort((ArrayList<WayPoint>) waypoints, new Comparator<WayPoint>() {
-            @Override
-            public int compare(WayPoint a, WayPoint b) {
-                return a.time <= b.time ? -1 : 1;
-            }
-        });
-
-        firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
-        for (WayPoint w : waypoints) {
-            if (firstTime < 0.0) {
-                firstTime = w.time;
-            }
-            double offset = w.time - firstTime;
-            AudioMarker am = new AudioMarker(w.getCoor(), w, url, ml, w.time, offset);
-            /*
-             * timeFromAudio intended for future use to shift markers of this type on
-             * synchronization
-             */
-            if (w == wayPointFromTimeStamp) {
-                am.timeFromAudio = true;
-            }
-            ml.data.add(am);
-        }
-
-        if (timedMarkersOmitted && !markers.timedMarkersOmitted) {
-            JOptionPane
-            .showMessageDialog(
-                    Main.parent,
-                    tr("Some waypoints with timestamps from before the start of the track or after the end were omitted or moved to the start."));
-            markers.timedMarkersOmitted = timedMarkersOmitted;
-        }
-        if (untimedMarkersOmitted && !markers.untimedMarkersOmitted) {
-            JOptionPane
-            .showMessageDialog(
-                    Main.parent,
-                    tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
-            markers.untimedMarkersOmitted = untimedMarkersOmitted;
-        }
-    }
-
-    /**
-     * Makes a WayPoint at the projection of point P onto the track providing P is less than
-     * tolerance away from the track
-     *
-     * @param P : the point to determine the projection for
-     * @param tolerance : must be no further than this from the track
-     * @return the closest point on the track to P, which may be the first or last point if off the
-     * end of a segment, or may be null if nothing close enough
-     */
-    public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
-        /*
-         * assume the coordinates of P are xp,yp, and those of a section of track between two
-         * trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
-         *
-         * The equation of RS is Ax + By + C = 0 where A = ys - yr B = xr - xs C = - Axr - Byr
-         *
-         * Also, note that the distance RS^2 is A^2 + B^2
-         *
-         * If RS^2 == 0.0 ignore the degenerate section of track
-         *
-         * PN^2 = (Axp + Byp + C)^2 / RS^2 that is the distance from P to the line
-         *
-         * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject the line;
-         * otherwise... determine if the projected poijnt lies within the bounds of the line: PR^2 -
-         * PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
-         *
-         * where PR^2 = (xp - xr)^2 + (yp-yr)^2 and PS^2 = (xp - xs)^2 + (yp-ys)^2
-         *
-         * If so, calculate N as xn = xr + (RN/RS) B yn = y1 + (RN/RS) A
-         *
-         * where RN = sqrt(PR^2 - PN^2)
-         */
-
-        double PNminsq = tolerance * tolerance;
-        EastNorth bestEN = null;
-        double bestTime = 0.0;
-        double px = P.east();
-        double py = P.north();
-        double rx = 0.0, ry = 0.0, sx, sy, x, y;
-        if (data.tracks == null)
-            return null;
-        for (GpxTrack track : data.tracks) {
-            for (GpxTrackSegment seg : track.getSegments()) {
-                WayPoint R = null;
-                for (WayPoint S : seg.getWayPoints()) {
-                    EastNorth c = S.getEastNorth();
-                    if (R == null) {
-                        R = S;
-                        rx = c.east();
-                        ry = c.north();
-                        x = px - rx;
-                        y = py - ry;
-                        double PRsq = x * x + y * y;
-                        if (PRsq < PNminsq) {
-                            PNminsq = PRsq;
-                            bestEN = c;
-                            bestTime = R.time;
-                        }
-                    } else {
-                        sx = c.east();
-                        sy = c.north();
-                        double A = sy - ry;
-                        double B = rx - sx;
-                        double C = -A * rx - B * ry;
-                        double RSsq = A * A + B * B;
-                        if (RSsq == 0.0) {
-                            continue;
-                        }
-                        double PNsq = A * px + B * py + C;
-                        PNsq = PNsq * PNsq / RSsq;
-                        if (PNsq < PNminsq) {
-                            x = px - rx;
-                            y = py - ry;
-                            double PRsq = x * x + y * y;
-                            x = px - sx;
-                            y = py - sy;
-                            double PSsq = x * x + y * y;
-                            if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
-                                double RNoverRS = Math.sqrt((PRsq - PNsq) / RSsq);
-                                double nx = rx - RNoverRS * B;
-                                double ny = ry + RNoverRS * A;
-                                bestEN = new EastNorth(nx, ny);
-                                bestTime = R.time + RNoverRS * (S.time - R.time);
-                                PNminsq = PNsq;
-                            }
-                        }
-                        R = S;
-                        rx = sx;
-                        ry = sy;
-                    }
-                }
-                if (R != null) {
-                    EastNorth c = R.getEastNorth();
-                    /* if there is only one point in the seg, it will do this twice, but no matter */
-                    rx = c.east();
-                    ry = c.north();
-                    x = px - rx;
-                    y = py - ry;
-                    double PRsq = x * x + y * y;
-                    if (PRsq < PNminsq) {
-                        PNminsq = PRsq;
-                        bestEN = c;
-                        bestTime = R.time;
-                    }
-                }
-            }
-        }
-        if (bestEN == null)
-            return null;
-        WayPoint best = new WayPoint(Main.getProjection().eastNorth2latlon(bestEN));
-        best.time = bestTime;
-        return best;
-    }
-
-    private class CustomizeDrawing extends AbstractAction implements LayerAction, MultiLayerAction {
-        List<Layer> layers;
-
-        public CustomizeDrawing(List<Layer> l) {
-            this();
-            layers = l;
-        }
-
-        public CustomizeDrawing(Layer l) {
-            this();
-            layers = new LinkedList<Layer>();
-            layers.add(l);
-        }
-
-        private CustomizeDrawing() {
-            super(tr("Customize track drawing"), ImageProvider.get("mapmode/addsegment"));
-            putValue("help", ht("/Action/GPXLayerCustomizeLineDrawing"));
-        }
-
-        @Override
-        public boolean supportLayers(List<Layer> layers) {
-            for(Layer layer: layers) {
-                if(!(layer instanceof GpxLayer))
-                    return false;
-            }
-            return true;
-        }
-
-        @Override
-        public Component createMenuComponent() {
-            return new JMenuItem(this);
-        }
-
-        @Override
-        public Action getMultiLayerAction(List<Layer> layers) {
-            return new CustomizeDrawing(layers);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            boolean hasLocal = false, hasNonlocal = false;
-            for (Layer layer : layers) {
-                if (layer instanceof GpxLayer) {
-                    if (((GpxLayer) layer).isLocalFile) {
-                        hasLocal = true;
-                    } else {
-                        hasNonlocal = true;
-                    }
-                }
-            }
-            GPXSettingsPanel panel=new GPXSettingsPanel(getName(), hasLocal, hasNonlocal);
-            JScrollPane scrollpane = new JScrollPane(panel,
-                    JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
-            scrollpane.setBorder(BorderFactory.createEmptyBorder( 0, 0, 0, 0 ));
-            int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
-            if (screenHeight < 700) { // to fit on screen 800x600
-                scrollpane.setPreferredSize(new Dimension(panel.getPreferredSize().width, Math.min(panel.getPreferredSize().height,450)));
-            }
-            int answer = JOptionPane.showConfirmDialog(Main.parent, scrollpane,
-                    tr("Customize track drawing"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
-            if (answer == JOptionPane.CANCEL_OPTION || answer == JOptionPane.CLOSED_OPTION) return;
-            for(Layer layer : layers) {
-                // save preferences for all layers
-                boolean f=false;
-                if (layer instanceof GpxLayer) {
-                    f=((GpxLayer)layer).isLocalFile;
-                }
-                panel.savePreferences(layer.getName(),f);
-            }
-            Main.map.repaint();
-        }
-    }
-
-    private class MarkersFromNamedPoins extends AbstractAction {
-
-        public MarkersFromNamedPoins() {
-            super(tr("Markers From Named Points"), ImageProvider.get("addmarkers"));
-            putValue("help", ht("/Action/MarkersFromNamedPoints"));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            GpxData namedTrackPoints = new GpxData();
-            for (GpxTrack track : data.tracks) {
-                for (GpxTrackSegment seg : track.getSegments()) {
-                    for (WayPoint point : seg.getWayPoints())
-                        if (point.attr.containsKey("name") || point.attr.containsKey("desc")) {
-                            namedTrackPoints.waypoints.add(point);
-                        }
-                }
-            }
-
-            MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", getName()),
-                    getAssociatedFile(), GpxLayer.this);
-            if (ml.data.size() > 0) {
-                Main.main.addLayer(ml);
-            }
-
-        }
-    }
-
-    private class ImportAudio extends AbstractAction {
-
-        public ImportAudio() {
-            super(tr("Import Audio"), ImageProvider.get("importaudio"));
-            putValue("help", ht("/Action/ImportAudio"));
-        }
-
-        private void warnCantImportIntoServerLayer(GpxLayer layer) {
-            String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>"
-                    + "Because its way points do not include a timestamp we cannot correlate them with audio data.</html>",
-                    layer.getName()
-                    );
-            HelpAwareOptionPane.showOptionDialog(
-                    Main.parent,
-                    msg,
-                    tr("Import not possible"),
-                    JOptionPane.WARNING_MESSAGE,
-                    ht("/Action/ImportAudio#CantImportIntoGpxLayerFromServer")
-                    );
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            if (GpxLayer.this.data.fromServer) {
-                warnCantImportIntoServerLayer(GpxLayer.this);
-                return;
-            }
-            FileFilter filter = new FileFilter() {
-                @Override
-                public boolean accept(File f) {
-                    return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
-                }
-
-                @Override
-                public String getDescription() {
-                    return tr("Wave Audio files (*.wav)");
-                }
-            };
-            JFileChooser fc = DiskAccessAction.createAndOpenFileChooser(true, true, null, filter, JFileChooser.FILES_ONLY, "markers.lastaudiodirectory");
-            if (fc != null) {
-                File sel[] = fc.getSelectedFiles();
-                // sort files in increasing order of timestamp (this is the end time, but so
-                // long as they don't overlap, that's fine)
-                if (sel.length > 1) {
-                    Arrays.sort(sel, new Comparator<File>() {
-                        @Override
-                        public int compare(File a, File b) {
-                            return a.lastModified() <= b.lastModified() ? -1 : 1;
-                        }
-                    });
-                }
-
-                String names = null;
-                for (int i = 0; i < sel.length; i++) {
-                    if (names == null) {
-                        names = " (";
-                    } else {
-                        names += ", ";
-                    }
-                    names += sel[i].getName();
-                }
-                if (names != null) {
-                    names += ")";
-                } else {
-                    names = "";
-                }
-                MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", getName()) + names,
-                        getAssociatedFile(), GpxLayer.this);
-                double firstStartTime = sel[0].lastModified() / 1000.0 /* ms -> seconds */
-                        - AudioUtil.getCalibratedDuration(sel[0]);
-
-                Markers m = new Markers();
-                for (int i = 0; i < sel.length; i++) {
-                    importAudio(sel[i], ml, firstStartTime, m);
-                }
-                Main.main.addLayer(ml);
-                Main.map.repaint();
-            }
-        }
-    }
-
-    private class ImportImages extends AbstractAction {
-
-        public ImportImages() {
-            super(tr("Import images"), ImageProvider.get("dialogs/geoimage"));
-            putValue("help", ht("/Action/ImportImages"));
-        }
-
-        private void warnCantImportIntoServerLayer(GpxLayer layer) {
-            String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>"
-                    + "Because its way points do not include a timestamp we cannot correlate them with images.</html>",
-                    layer.getName()
-                    );
-            HelpAwareOptionPane.showOptionDialog(
-                    Main.parent,
-                    msg,
-                    tr("Import not possible"),
-                    JOptionPane.WARNING_MESSAGE,
-                    ht("/Action/ImportImages#CantImportIntoGpxLayerFromServer")
-                    );
-        }
-
-        private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
-            for (File f : sel) {
-                if (f.isDirectory()) {
-                    addRecursiveFiles(files, f.listFiles());
-                } else if (f.getName().toLowerCase().endsWith(".jpg")) {
-                    files.add(f);
-                }
-            }
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-
-            if (GpxLayer.this.data.fromServer) {
-                warnCantImportIntoServerLayer(GpxLayer.this);
-                return;
-            }
-            
-            JpgImporter importer = new JpgImporter(GpxLayer.this);
-            JFileChooser fc = new JFileChooserManager(true, "geoimage.lastdirectory", Main.pref.get("lastDirectory")).
-                    createFileChooser(true, null, importer.filter, JFileChooser.FILES_AND_DIRECTORIES).openFileChooser();
-            if (fc != null) {
-                File[] sel = fc.getSelectedFiles();
-                if (sel != null && sel.length > 0) {
-                    LinkedList<File> files = new LinkedList<File>();
-                    addRecursiveFiles(files, sel);
-                    importer.importDataHandleExceptions(files, NullProgressMonitor.INSTANCE);
-                }
-            }
-        }
-    }
-
     @Override
     public void projectionChanged(Projection oldValue, Projection newValue) {
Index: /trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java	(revision 5714)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java	(revision 5715)
@@ -84,5 +84,5 @@
         }
 
-        boolean isFinished() {
+        public boolean isFinished() {
             return totalCount == processedCount;
         }
Index: /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java	(revision 5715)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java	(revision 5715)
@@ -0,0 +1,193 @@
+package org.openstreetmap.josm.gui.layer.gpx;
+
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.util.Map;
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellRenderer;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.NavigatableComponent;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.OpenBrowser;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+
+/**
+ * allows the user to choose which of the downloaded tracks should be displayed.
+ * they can be chosen from the gpx layer context menu.
+ */
+public class ChooseTrackVisibilityAction extends AbstractAction {
+    private final GpxLayer layer;
+    
+    public ChooseTrackVisibilityAction(final GpxLayer layer) {
+        super(tr("Choose visible tracks"), ImageProvider.get("dialogs/filter"));
+        this.layer = layer;
+        putValue("help", ht("/Action/ChooseTrackVisibility"));
+    }
+
+    /**
+     * gathers all available data for the tracks and returns them as array of arrays
+     * in the expected column order  */
+    private Object[][] buildTableContents() {
+        Object[][] tracks = new Object[layer.data.tracks.size()][5];
+        int i = 0;
+        for (GpxTrack trk : layer.data.tracks) {
+            Map<String, Object> attr = trk.getAttributes();
+            String name = (String) (attr.containsKey("name") ? attr.get("name") : "");
+            String desc = (String) (attr.containsKey("desc") ? attr.get("desc") : "");
+            String time = GpxLayer.getTimespanForTrack(trk);
+            String length = NavigatableComponent.getSystemOfMeasurement().getDistText(trk.length());
+            String url = (String) (attr.containsKey("url") ? attr.get("url") : "");
+            tracks[i] = new String[]{name, desc, time, length, url};
+            i++;
+        }
+        return tracks;
+    }
+
+    /**
+     * Builds an non-editable table whose 5th column will open a browser when double clicked.
+     * The table will fill its parent. */
+    private JTable buildTable(String[] headers, Object[][] content) {
+        final JTable t = new JTable(content, headers) {
+            @Override
+            public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
+                Component c = super.prepareRenderer(renderer, row, col);
+                if (c instanceof JComponent) {
+                    JComponent jc = (JComponent) c;
+                    jc.setToolTipText((String) getValueAt(row, col));
+                }
+                return c;
+            }
+
+            @Override
+            public boolean isCellEditable(int rowIndex, int colIndex) {
+                return false;
+            }
+        };
+        // default column widths
+        t.getColumnModel().getColumn(0).setPreferredWidth(220);
+        t.getColumnModel().getColumn(1).setPreferredWidth(300);
+        t.getColumnModel().getColumn(2).setPreferredWidth(200);
+        t.getColumnModel().getColumn(3).setPreferredWidth(50);
+        t.getColumnModel().getColumn(4).setPreferredWidth(100);
+        // make the link clickable
+        final MouseListener urlOpener = new MouseAdapter() {
+            @Override
+            public void mouseClicked(MouseEvent e) {
+                if (e.getClickCount() != 2) {
+                    return;
+                }
+                JTable t = (JTable) e.getSource();
+                int col = t.convertColumnIndexToModel(t.columnAtPoint(e.getPoint()));
+                if (col != 4) {
+                    return;
+                }
+                int row = t.rowAtPoint(e.getPoint());
+                String url = (String) t.getValueAt(row, col);
+                if (url == null || url.isEmpty()) {
+                    return;
+                }
+                OpenBrowser.displayUrl(url);
+            }
+        };
+        t.addMouseListener(urlOpener);
+        t.setFillsViewportHeight(true);
+        return t;
+    }
+
+    /** selects all rows (=tracks) in the table that are currently visible */
+    private void selectVisibleTracksInTable(JTable table) {
+        // don't select any tracks if the layer is not visible
+        if (!layer.isVisible()) {
+            return;
+        }
+        ListSelectionModel s = table.getSelectionModel();
+        s.clearSelection();
+        for (int i = 0; i < layer.trackVisibility.length; i++) {
+            if (layer.trackVisibility[i]) {
+                s.addSelectionInterval(i, i);
+            }
+        }
+    }
+
+    /** listens to selection changes in the table and redraws the map */
+    private void listenToSelectionChanges(JTable table) {
+        table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+            @Override
+            public void valueChanged(ListSelectionEvent e) {
+                if (!(e.getSource() instanceof ListSelectionModel)) {
+                    return;
+                }
+                ListSelectionModel s = (ListSelectionModel) e.getSource();
+                for (int i = 0; i < layer.trackVisibility.length; i++) {
+                    layer.trackVisibility[i] = s.isSelectedIndex(i);
+                }
+                Main.map.mapView.preferenceChanged(null);
+                Main.map.repaint(100);
+            }
+        });
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent arg0) {
+        final JPanel msg = new JPanel(new GridBagLayout());
+        msg.add(new JLabel(tr("<html>Select all tracks that you want to be displayed. You can drag select a " + "range of tracks or use CTRL+Click to select specific ones. The map is updated live in the " + "background. Open the URLs by double clicking them.</html>")), GBC.eol().fill(GBC.HORIZONTAL));
+        // build table
+        final boolean[] trackVisibilityBackup = layer.trackVisibility.clone();
+        final String[] headers = {tr("Name"), tr("Description"), tr("Timespan"), tr("Length"), tr("URL")};
+        final JTable table = buildTable(headers, buildTableContents());
+        selectVisibleTracksInTable(table);
+        listenToSelectionChanges(table);
+        // make the table scrollable
+        JScrollPane scrollPane = new JScrollPane(table);
+        msg.add(scrollPane, GBC.eol().fill(GBC.BOTH));
+        // build dialog
+        ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Set track visibility for {0}", layer.getName()), new String[]{tr("Show all"), tr("Show selected only"), tr("Cancel")});
+        ed.setButtonIcons(new String[]{"dialogs/layerlist/eye", "dialogs/filter", "cancel"});
+        ed.setContent(msg, false);
+        ed.setDefaultButton(2);
+        ed.setCancelButton(3);
+        ed.configureContextsensitiveHelp("/Action/ChooseTrackVisibility", true);
+        ed.setRememberWindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(Main.parent, new Dimension(1000, 500)));
+        ed.showDialog();
+        int v = ed.getValue();
+        // cancel for unknown buttons and copy back original settings
+        if (v != 1 && v != 2) {
+            for (int i = 0; i < layer.trackVisibility.length; i++) {
+                layer.trackVisibility[i] = trackVisibilityBackup[i];
+            }
+            Main.map.repaint();
+            return;
+        }
+        // set visibility (1 = show all, 2 = filter). If no tracks are selected
+        // set all of them visible and...
+        ListSelectionModel s = table.getSelectionModel();
+        final boolean all = v == 1 || s.isSelectionEmpty();
+        for (int i = 0; i < layer.trackVisibility.length; i++) {
+            layer.trackVisibility[i] = all || s.isSelectedIndex(i);
+        }
+        // ...sync with layer visibility instead to avoid having two ways to hide everything
+        layer.setVisible(v == 1 || !s.isSelectionEmpty());
+        Main.map.repaint();
+    }
+    
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java	(revision 5715)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java	(revision 5715)
@@ -0,0 +1,68 @@
+package org.openstreetmap.josm.gui.layer.gpx;
+
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.AbstractAction;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.tools.DateUtils;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.UrlLabel;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class ConvertToDataLayerAction extends AbstractAction {
+    private final GpxLayer layer;
+
+    public ConvertToDataLayerAction(final GpxLayer layer) {
+        super(tr("Convert to data layer"), ImageProvider.get("converttoosm"));
+        this.layer = layer;
+        putValue("help", ht("/Action/ConvertToDataLayer"));
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        JPanel msg = new JPanel(new GridBagLayout());
+        msg.add(new JLabel(tr("<html>Upload of unprocessed GPS data as map data is considered harmful.<br>If you want to upload traces, look here:</html>")), GBC.eol());
+        msg.add(new UrlLabel(tr("http://www.openstreetmap.org/traces"), 2), GBC.eop());
+        if (!ConditionalOptionPaneUtil.showConfirmationDialog("convert_to_data", Main.parent, msg, tr("Warning"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, JOptionPane.OK_OPTION)) {
+            return;
+        }
+        DataSet ds = new DataSet();
+        for (GpxTrack trk : layer.data.tracks) {
+            for (GpxTrackSegment segment : trk.getSegments()) {
+                List<Node> nodes = new ArrayList<Node>();
+                for (WayPoint p : segment.getWayPoints()) {
+                    Node n = new Node(p.getCoor());
+                    String timestr = p.getString("time");
+                    if (timestr != null) {
+                        n.setTimestamp(DateUtils.fromString(timestr));
+                    }
+                    ds.addPrimitive(n);
+                    nodes.add(n);
+                }
+                Way w = new Way();
+                w.setNodes(nodes);
+                ds.addPrimitive(w);
+            }
+        }
+        Main.main.addLayer(new OsmDataLayer(ds, tr("Converted from: {0}", layer.getName()), layer.getAssociatedFile()));
+        Main.main.removeLayer(layer);
+    }
+    
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/gpx/CustomizeDrawingAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/gpx/CustomizeDrawingAction.java	(revision 5715)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/gpx/CustomizeDrawingAction.java	(revision 5715)
@@ -0,0 +1,101 @@
+package org.openstreetmap.josm.gui.layer.gpx;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.util.LinkedList;
+import java.util.List;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
+import org.openstreetmap.josm.gui.layer.Layer.MultiLayerAction;
+import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class CustomizeDrawingAction extends AbstractAction implements LayerAction, MultiLayerAction {
+    List<Layer> layers;
+
+    public CustomizeDrawingAction(List<Layer> l) {
+        this();
+        layers = l;
+    }
+
+    public CustomizeDrawingAction(Layer l) {
+        this();
+        layers = new LinkedList<Layer>();
+        layers.add(l);
+    }
+
+    private CustomizeDrawingAction() {
+        super(tr("Customize track drawing"), ImageProvider.get("mapmode/addsegment"));
+        putValue("help", ht("/Action/GPXLayerCustomizeLineDrawing"));
+    }
+
+    @Override
+    public boolean supportLayers(List<Layer> layers) {
+        for (Layer layer : layers) {
+            if (!(layer instanceof GpxLayer)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public Component createMenuComponent() {
+        return new JMenuItem(this);
+    }
+
+    @Override
+    public Action getMultiLayerAction(List<Layer> layers) {
+        return new CustomizeDrawingAction(layers);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        boolean hasLocal = false;
+        boolean hasNonlocal = false;
+        for (Layer layer : layers) {
+            if (layer instanceof GpxLayer) {
+                if (((GpxLayer) layer).isLocalFile()) {
+                    hasLocal = true;
+                } else {
+                    hasNonlocal = true;
+                }
+            }
+        }
+        GPXSettingsPanel panel = new GPXSettingsPanel(layers.get(0).getName(), hasLocal, hasNonlocal);
+        JScrollPane scrollpane = new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+        scrollpane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
+        int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
+        if (screenHeight < 700) {
+            // to fit on screen 800x600
+            scrollpane.setPreferredSize(new Dimension(panel.getPreferredSize().width, Math.min(panel.getPreferredSize().height, 450)));
+        }
+        int answer = JOptionPane.showConfirmDialog(Main.parent, scrollpane, tr("Customize track drawing"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
+        if (answer == JOptionPane.CANCEL_OPTION || answer == JOptionPane.CLOSED_OPTION) {
+            return;
+        }
+        for (Layer layer : layers) {
+            // save preferences for all layers
+            boolean f = false;
+            if (layer instanceof GpxLayer) {
+                f = ((GpxLayer) layer).isLocalFile();
+            }
+            panel.savePreferences(layer.getName(), f);
+        }
+        Main.map.repaint();
+    }
+    
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/gpx/DownloadAlongTrackAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/gpx/DownloadAlongTrackAction.java	(revision 5715)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/gpx/DownloadAlongTrackAction.java	(revision 5715)
@@ -0,0 +1,303 @@
+package org.openstreetmap.josm.gui.layer.gpx;
+
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.geom.Area;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Future;
+import javax.swing.AbstractAction;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTaskList;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.tools.GBC;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Action that issues a series of download requests to the API, following the GPX track.
+ *
+ * @author fred
+ */
+public class DownloadAlongTrackAction extends AbstractAction {
+    static final int NEAR_TRACK = 0;
+    static final int NEAR_WAYPOINTS = 1;
+    static final int NEAR_BOTH = 2;
+    
+    private static final String PREF_DOWNLOAD_ALONG_TRACK_DISTANCE = "gpxLayer.downloadAlongTrack.distance";
+    private static final String PREF_DOWNLOAD_ALONG_TRACK_AREA = "gpxLayer.downloadAlongTrack.area";
+    private static final String PREF_DOWNLOAD_ALONG_TRACK_NEAR = "gpxLayer.downloadAlongTrack.near";
+
+    
+    private final Integer[] dist = {5000, 500, 50};
+    private final Integer[] area = {20, 10, 5, 1};
+    
+    private final GpxData data;
+
+    public DownloadAlongTrackAction(GpxData data) {
+        super(tr("Download from OSM along this track"), ImageProvider.get("downloadalongtrack"));
+        this.data = data;
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        /*
+         * build selection dialog
+         */
+        JPanel msg = new JPanel(new GridBagLayout());
+        msg.add(new JLabel(tr("Download everything within:")), GBC.eol());
+        String[] s = new String[dist.length];
+        for (int i = 0; i < dist.length; ++i) {
+            s[i] = tr("{0} meters", dist[i]);
+        }
+        JList buffer = new JList(s);
+        buffer.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, 0));
+        msg.add(buffer, GBC.eol());
+        msg.add(new JLabel(tr("Maximum area per request:")), GBC.eol());
+        s = new String[area.length];
+        for (int i = 0; i < area.length; ++i) {
+            s[i] = tr("{0} sq km", area[i]);
+        }
+        JList maxRect = new JList(s);
+        maxRect.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, 0));
+        msg.add(maxRect, GBC.eol());
+        msg.add(new JLabel(tr("Download near:")), GBC.eol());
+        JList downloadNear = new JList(new String[]{tr("track only"), tr("waypoints only"), tr("track and waypoints")});
+        downloadNear.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, 0));
+        msg.add(downloadNear, GBC.eol());
+        int ret = JOptionPane.showConfirmDialog(Main.parent, msg, tr("Download from OSM along this track"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
+        switch (ret) {
+            case JOptionPane.CANCEL_OPTION:
+            case JOptionPane.CLOSED_OPTION:
+                return;
+            default:
+        // continue
+        }
+        Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, buffer.getSelectedIndex());
+        Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, maxRect.getSelectedIndex());
+        final int near = downloadNear.getSelectedIndex();
+        Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, near);
+        /*
+         * Find the average latitude for the data we're contemplating, so we can know how many
+         * metres per degree of longitude we have.
+         */
+        double latsum = 0;
+        int latcnt = 0;
+        if (near == NEAR_TRACK || near == NEAR_BOTH) {
+            for (GpxTrack trk : data.tracks) {
+                for (GpxTrackSegment segment : trk.getSegments()) {
+                    for (WayPoint p : segment.getWayPoints()) {
+                        latsum += p.getCoor().lat();
+                        latcnt++;
+                    }
+                }
+            }
+        }
+        if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
+            for (WayPoint p : data.waypoints) {
+                latsum += p.getCoor().lat();
+                latcnt++;
+            }
+        }
+        double avglat = latsum / latcnt;
+        double scale = Math.cos(Math.toRadians(avglat));
+        /*
+         * Compute buffer zone extents and maximum bounding box size. Note that the maximum we
+         * ever offer is a bbox area of 0.002, while the API theoretically supports 0.25, but as
+         * soon as you touch any built-up area, that kind of bounding box will download forever
+         * and then stop because it has more than 50k nodes.
+         */
+        Integer i = buffer.getSelectedIndex();
+        final int buffer_dist = dist[i < 0 ? 0 : i];
+        i = maxRect.getSelectedIndex();
+        final double max_area = area[i < 0 ? 0 : i] / 10000.0 / scale;
+        final double buffer_y = buffer_dist / 100000.0;
+        final double buffer_x = buffer_y / scale;
+        final int totalTicks = latcnt;
+        // guess if a progress bar might be useful.
+        final boolean displayProgress = totalTicks > 2000 && buffer_y < 0.01;
+
+        class CalculateDownloadArea extends PleaseWaitRunnable {
+
+            private Area a = new Area();
+            private boolean cancel = false;
+            private int ticks = 0;
+            private Rectangle2D r = new Rectangle2D.Double();
+
+            public CalculateDownloadArea() {
+                super(tr("Calculating Download Area"), displayProgress ? null : NullProgressMonitor.INSTANCE, false);
+            }
+
+            @Override
+            protected void cancel() {
+                cancel = true;
+            }
+
+            @Override
+            protected void finish() {
+            }
+
+            @Override
+            protected void afterFinish() {
+                if (cancel) {
+                    return;
+                }
+                confirmAndDownloadAreas(a, max_area, progressMonitor);
+            }
+
+            /**
+             * increase tick count by one, report progress every 100 ticks
+             */
+            private void tick() {
+                ticks++;
+                if (ticks % 100 == 0) {
+                    progressMonitor.worked(100);
+                }
+            }
+
+            /**
+             * calculate area for single, given way point and return new LatLon if the
+             * way point has been used to modify the area.
+             */
+            private LatLon calcAreaForWayPoint(WayPoint p, LatLon previous) {
+                tick();
+                LatLon c = p.getCoor();
+                if (previous == null || c.greatCircleDistance(previous) > buffer_dist) {
+                    // we add a buffer around the point.
+                    r.setRect(c.lon() - buffer_x, c.lat() - buffer_y, 2 * buffer_x, 2 * buffer_y);
+                    a.add(new Area(r));
+                    return c;
+                }
+                return previous;
+            }
+
+            @Override
+            protected void realRun() {
+                progressMonitor.setTicksCount(totalTicks);
+                /*
+                 * Collect the combined area of all gpx points plus buffer zones around them. We ignore
+                 * points that lie closer to the previous point than the given buffer size because
+                 * otherwise this operation takes ages.
+                 */
+                LatLon previous = null;
+                if (near == NEAR_TRACK || near == NEAR_BOTH) {
+                    for (GpxTrack trk : data.tracks) {
+                        for (GpxTrackSegment segment : trk.getSegments()) {
+                            for (WayPoint p : segment.getWayPoints()) {
+                                if (cancel) {
+                                    return;
+                                }
+                                previous = calcAreaForWayPoint(p, previous);
+                            }
+                        }
+                    }
+                }
+                if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
+                    for (WayPoint p : data.waypoints) {
+                        if (cancel) {
+                            return;
+                        }
+                        previous = calcAreaForWayPoint(p, previous);
+                    }
+                }
+            }
+        }
+        Main.worker.submit(new CalculateDownloadArea());
+    }
+
+    /**
+     * Area "a" contains the hull that we would like to download data for. however we
+     * can only download rectangles, so the following is an attempt at finding a number of
+     * rectangles to download.
+     *
+     * The idea is simply: Start out with the full bounding box. If it is too large, then
+     * split it in half and repeat recursively for each half until you arrive at something
+     * small enough to download. The algorithm is improved by always using the intersection
+     * between the rectangle and the actual desired area. For example, if you have a track
+     * that goes like this: +----+ | /| | / | | / | |/ | +----+ then we would first look at
+     * downloading the whole rectangle (assume it's too big), after that we split it in half
+     * (upper and lower half), but we donot request the full upper and lower rectangle, only
+     * the part of the upper/lower rectangle that actually has something in it.
+     *
+     * This functions calculates the rectangles, asks the user to continue and downloads
+     * the areas if applicable.
+     */
+    private void confirmAndDownloadAreas(Area a, double max_area, ProgressMonitor progressMonitor) {
+        List<Rectangle2D> toDownload = new ArrayList<Rectangle2D>();
+        addToDownload(a, a.getBounds(), toDownload, max_area);
+        if (toDownload.isEmpty()) {
+            return;
+        }
+        JPanel msg = new JPanel(new GridBagLayout());
+        msg.add(new JLabel(tr("<html>This action will require {0} individual<br>" + "download requests. Do you wish<br>to continue?</html>", toDownload.size())), GBC.eol());
+        if (toDownload.size() > 1) {
+            int ret = JOptionPane.showConfirmDialog(Main.parent, msg, tr("Download from OSM along this track"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
+            switch (ret) {
+                case JOptionPane.CANCEL_OPTION:
+                case JOptionPane.CLOSED_OPTION:
+                    return;
+                default:
+            // continue
+            }
+        }
+        final PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Download data"));
+        final Future<?> future = new DownloadOsmTaskList().download(false, toDownload, monitor);
+        Main.worker.submit(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    future.get();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    return;
+                }
+                monitor.close();
+            }
+        });
+    }
+    
+     private static void addToDownload(Area a, Rectangle2D r, Collection<Rectangle2D> results, double max_area) {
+        Area tmp = new Area(r);
+        // intersect with sought-after area
+        tmp.intersect(a);
+        if (tmp.isEmpty()) {
+             return;
+         }
+        Rectangle2D bounds = tmp.getBounds2D();
+        if (bounds.getWidth() * bounds.getHeight() > max_area) {
+            // the rectangle gets too large; split it and make recursive call.
+            Rectangle2D r1;
+            Rectangle2D r2;
+            if (bounds.getWidth() > bounds.getHeight()) {
+                // rectangles that are wider than high are split into a left and right half,
+                r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth() / 2, bounds.getHeight());
+                r2 = new Rectangle2D.Double(bounds.getX() + bounds.getWidth() / 2, bounds.getY(),
+                        bounds.getWidth() / 2, bounds.getHeight());
+            } else {
+                // others into a top and bottom half.
+                r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() / 2);
+                r2 = new Rectangle2D.Double(bounds.getX(), bounds.getY() + bounds.getHeight() / 2, bounds.getWidth(),
+                        bounds.getHeight() / 2);
+            }
+            addToDownload(a, r1, results, max_area);
+            addToDownload(a, r2, results, max_area);
+        } else {
+            results.add(bounds);
+        }
+    }
+    
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/gpx/DownloadWmsAlongTrackAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/gpx/DownloadWmsAlongTrackAction.java	(revision 5715)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/gpx/DownloadWmsAlongTrackAction.java	(revision 5715)
@@ -0,0 +1,119 @@
+package org.openstreetmap.josm.gui.layer.gpx;
+
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.AbstractAction;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.AbstractMergeAction.LayerListCellRenderer;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.layer.WMSLayer;
+import org.openstreetmap.josm.gui.layer.WMSLayer.PrecacheTask;
+import org.openstreetmap.josm.gui.progress.ProgressTaskId;
+import org.openstreetmap.josm.gui.progress.ProgressTaskIds;
+import org.openstreetmap.josm.gui.widgets.JosmComboBox;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.xml.sax.SAXException;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class DownloadWmsAlongTrackAction extends AbstractAction {
+
+    private final GpxData data;
+    
+    public DownloadWmsAlongTrackAction(final GpxData data) {
+        super(tr("Precache imagery tiles along this track"), ImageProvider.get("downloadalongtrack"));
+        this.data = data;
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        final List<LatLon> points = new ArrayList<LatLon>();
+        for (GpxTrack trk : data.tracks) {
+            for (GpxTrackSegment segment : trk.getSegments()) {
+                for (WayPoint p : segment.getWayPoints()) {
+                    points.add(p.getCoor());
+                }
+            }
+        }
+        for (WayPoint p : data.waypoints) {
+            points.add(p.getCoor());
+        }
+        final WMSLayer layer = askWMSLayer();
+        if (layer != null) {
+            PleaseWaitRunnable task = new PleaseWaitRunnable(tr("Precaching WMS")) {
+                private PrecacheTask precacheTask;
+
+                @Override
+                protected void realRun() throws SAXException, IOException, OsmTransferException {
+                    precacheTask = new PrecacheTask(progressMonitor);
+                    layer.downloadAreaToCache(precacheTask, points, 0, 0);
+                    while (!precacheTask.isFinished() && !progressMonitor.isCanceled()) {
+                        synchronized (this) {
+                            try {
+                                wait(200);
+                            } catch (InterruptedException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                }
+
+                @Override
+                protected void finish() {
+                }
+
+                @Override
+                protected void cancel() {
+                    precacheTask.cancel();
+                }
+
+                @Override
+                public ProgressTaskId canRunInBackground() {
+                    return ProgressTaskIds.PRECACHE_WMS;
+                }
+            };
+            Main.worker.execute(task);
+        }
+    }
+
+    protected WMSLayer askWMSLayer() {
+        List<WMSLayer> targetLayers = Main.map.mapView.getLayersOfType(WMSLayer.class);
+        if (targetLayers.isEmpty()) {
+            warnNoImageryLayers();
+            return null;
+        }
+        JosmComboBox layerList = new JosmComboBox(targetLayers.toArray());
+        layerList.setRenderer(new LayerListCellRenderer());
+        layerList.setSelectedIndex(0);
+        JPanel pnl = new JPanel(new GridBagLayout());
+        pnl.add(new JLabel(tr("Please select the imagery layer.")), GBC.eol());
+        pnl.add(layerList, GBC.eol());
+        ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Select imagery layer"), new String[]{tr("Download"), tr("Cancel")});
+        ed.setButtonIcons(new String[]{"dialogs/down", "cancel"});
+        ed.setContent(pnl);
+        ed.showDialog();
+        if (ed.getValue() != 1) {
+            return null;
+        }
+        return (WMSLayer) layerList.getSelectedItem();
+    }
+
+    protected void warnNoImageryLayers() {
+        JOptionPane.showMessageDialog(Main.parent, tr("There are no imagery layers."), tr("No imagery layers"), JOptionPane.WARNING_MESSAGE);
+    }
+    
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java	(revision 5715)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java	(revision 5715)
@@ -0,0 +1,318 @@
+package org.openstreetmap.josm.gui.layer.gpx;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.filechooser.FileFilter;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.DiskAccessAction;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
+import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
+import org.openstreetmap.josm.tools.AudioUtil;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class ImportAudioAction extends AbstractAction {
+    private final GpxLayer layer;
+    
+    private static class Markers {
+        public boolean timedMarkersOmitted = false;
+        public boolean untimedMarkersOmitted = false;
+    }
+
+    public ImportAudioAction(final GpxLayer layer) {
+        super(tr("Import Audio"), ImageProvider.get("importaudio"));
+        this.layer = layer;
+        putValue("help", ht("/Action/ImportAudio"));
+    }
+
+    private void warnCantImportIntoServerLayer(GpxLayer layer) {
+        String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>" + "Because its way points do not include a timestamp we cannot correlate them with audio data.</html>", layer.getName());
+        HelpAwareOptionPane.showOptionDialog(Main.parent, msg, tr("Import not possible"), JOptionPane.WARNING_MESSAGE, ht("/Action/ImportAudio#CantImportIntoGpxLayerFromServer"));
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (layer.data.fromServer) {
+            warnCantImportIntoServerLayer(layer);
+            return;
+        }
+        FileFilter filter = new FileFilter() {
+            @Override
+            public boolean accept(File f) {
+                return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
+            }
+
+            @Override
+            public String getDescription() {
+                return tr("Wave Audio files (*.wav)");
+            }
+        };
+        JFileChooser fc = DiskAccessAction.createAndOpenFileChooser(true, true, null, filter, JFileChooser.FILES_ONLY, "markers.lastaudiodirectory");
+        if (fc != null) {
+            File[] sel = fc.getSelectedFiles();
+            // sort files in increasing order of timestamp (this is the end time, but so
+            // long as they don't overlap, that's fine)
+            if (sel.length > 1) {
+                Arrays.sort(sel, new Comparator<File>() {
+                    @Override
+                    public int compare(File a, File b) {
+                        return a.lastModified() <= b.lastModified() ? -1 : 1;
+                    }
+                });
+            }
+            String names = null;
+            for (int i = 0; i < sel.length; i++) {
+                if (names == null) {
+                    names = " (";
+                } else {
+                    names += ", ";
+                }
+                names += sel[i].getName();
+            }
+            if (names != null) {
+                names += ")";
+            } else {
+                names = "";
+            }
+            MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", layer.getName()) + names, layer.getAssociatedFile(), layer);
+            double firstStartTime = sel[0].lastModified() / 1000.0 - AudioUtil.getCalibratedDuration(sel[0]);
+            Markers m = new Markers();
+            for (int i = 0; i < sel.length; i++) {
+                importAudio(sel[i], ml, firstStartTime, m);
+            }
+            Main.main.addLayer(ml);
+            Main.map.repaint();
+        }
+    }
+    
+        /**
+     * Makes a new marker layer derived from this GpxLayer containing at least one audio marker
+     * which the given audio file is associated with. Markers are derived from the following (a)
+     * explict waypoints in the GPX layer, or (b) named trackpoints in the GPX layer, or (d)
+     * timestamp on the wav file (e) (in future) voice recognised markers in the sound recording (f)
+     * a single marker at the beginning of the track
+     * @param wavFile : the file to be associated with the markers in the new marker layer
+     * @param markers : keeps track of warning messages to avoid repeated warnings
+     */
+    private void importAudio(File wavFile, MarkerLayer ml, double firstStartTime, Markers markers) {
+        URL url = null;
+        boolean hasTracks = layer.data.tracks != null && !layer.data.tracks.isEmpty();
+        boolean hasWaypoints = layer.data.waypoints != null && !layer.data.waypoints.isEmpty();
+        try {
+            url = wavFile.toURI().toURL();
+        } catch (MalformedURLException e) {
+            System.err.println("Unable to convert filename " + wavFile.getAbsolutePath() + " to URL");
+        }
+        Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
+        boolean timedMarkersOmitted = false;
+        boolean untimedMarkersOmitted = false;
+        double snapDistance = Main.pref.getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3); /*
+         * about
+         * 25
+         * m
+         */
+        WayPoint wayPointFromTimeStamp = null;
+
+        // determine time of first point in track
+        double firstTime = -1.0;
+        if (hasTracks) {
+            for (GpxTrack track : layer.data.tracks) {
+                for (GpxTrackSegment seg : track.getSegments()) {
+                    for (WayPoint w : seg.getWayPoints()) {
+                        firstTime = w.time;
+                        break;
+                    }
+                    if (firstTime >= 0.0) {
+                        break;
+                    }
+                }
+                if (firstTime >= 0.0) {
+                    break;
+                }
+            }
+        }
+        if (firstTime < 0.0) {
+            JOptionPane.showMessageDialog(
+                    Main.parent,
+                    tr("No GPX track available in layer to associate audio with."),
+                    tr("Error"),
+                    JOptionPane.ERROR_MESSAGE
+                    );
+            return;
+        }
+
+        // (a) try explicit timestamped waypoints - unless suppressed
+        if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) && hasWaypoints) {
+            for (WayPoint w : layer.data.waypoints) {
+                if (w.time > firstTime) {
+                    waypoints.add(w);
+                } else if (w.time > 0.0) {
+                    timedMarkersOmitted = true;
+                }
+            }
+        }
+
+        // (b) try explicit waypoints without timestamps - unless suppressed
+        if (Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true) && hasWaypoints) {
+            for (WayPoint w : layer.data.waypoints) {
+                if (waypoints.contains(w)) {
+                    continue;
+                }
+                WayPoint wNear = layer.data.nearestPointOnTrack(w.getEastNorth(), snapDistance);
+                if (wNear != null) {
+                    WayPoint wc = new WayPoint(w.getCoor());
+                    wc.time = wNear.time;
+                    if (w.attr.containsKey("name")) {
+                        wc.attr.put("name", w.getString("name"));
+                    }
+                    waypoints.add(wc);
+                } else {
+                    untimedMarkersOmitted = true;
+                }
+            }
+        }
+
+        // (c) use explicitly named track points, again unless suppressed
+        if ((Main.pref.getBoolean("marker.audiofromnamedtrackpoints", false)) && layer.data.tracks != null
+                && !layer.data.tracks.isEmpty()) {
+            for (GpxTrack track : layer.data.tracks) {
+                for (GpxTrackSegment seg : track.getSegments()) {
+                    for (WayPoint w : seg.getWayPoints()) {
+                        if (w.attr.containsKey("name") || w.attr.containsKey("desc")) {
+                            waypoints.add(w);
+                        }
+                    }
+                }
+            }
+        }
+
+        // (d) use timestamp of file as location on track
+        if ((Main.pref.getBoolean("marker.audiofromwavtimestamps", false)) && hasTracks) {
+            double lastModified = wavFile.lastModified() / 1000.0; // lastModified is in
+            // milliseconds
+            double duration = AudioUtil.getCalibratedDuration(wavFile);
+            double startTime = lastModified - duration;
+            startTime = firstStartTime + (startTime - firstStartTime)
+                    / Main.pref.getDouble("audio.calibration", "1.0" /* default, ratio */);
+            WayPoint w1 = null;
+            WayPoint w2 = null;
+
+            for (GpxTrack track : layer.data.tracks) {
+                for (GpxTrackSegment seg : track.getSegments()) {
+                    for (WayPoint w : seg.getWayPoints()) {
+                        if (startTime < w.time) {
+                            w2 = w;
+                            break;
+                        }
+                        w1 = w;
+                    }
+                    if (w2 != null) {
+                        break;
+                    }
+                }
+            }
+
+            if (w1 == null || w2 == null) {
+                timedMarkersOmitted = true;
+            } else {
+                wayPointFromTimeStamp = new WayPoint(w1.getCoor().interpolate(w2.getCoor(),
+                        (startTime - w1.time) / (w2.time - w1.time)));
+                wayPointFromTimeStamp.time = startTime;
+                String name = wavFile.getName();
+                int dot = name.lastIndexOf(".");
+                if (dot > 0) {
+                    name = name.substring(0, dot);
+                }
+                wayPointFromTimeStamp.attr.put("name", name);
+                waypoints.add(wayPointFromTimeStamp);
+            }
+        }
+
+        // (e) analyse audio for spoken markers here, in due course
+
+        // (f) simply add a single marker at the start of the track
+        if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) && hasTracks) {
+            boolean gotOne = false;
+            for (GpxTrack track : layer.data.tracks) {
+                for (GpxTrackSegment seg : track.getSegments()) {
+                    for (WayPoint w : seg.getWayPoints()) {
+                        WayPoint wStart = new WayPoint(w.getCoor());
+                        wStart.attr.put("name", "start");
+                        wStart.time = w.time;
+                        waypoints.add(wStart);
+                        gotOne = true;
+                        break;
+                    }
+                    if (gotOne) {
+                        break;
+                    }
+                }
+                if (gotOne) {
+                    break;
+                }
+            }
+        }
+
+        /* we must have got at least one waypoint now */
+
+        Collections.sort((ArrayList<WayPoint>) waypoints, new Comparator<WayPoint>() {
+            @Override
+            public int compare(WayPoint a, WayPoint b) {
+                return a.time <= b.time ? -1 : 1;
+            }
+        });
+
+        firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
+        for (WayPoint w : waypoints) {
+            if (firstTime < 0.0) {
+                firstTime = w.time;
+            }
+            double offset = w.time - firstTime;
+            AudioMarker am = new AudioMarker(w.getCoor(), w, url, ml, w.time, offset);
+            /*
+             * timeFromAudio intended for future use to shift markers of this type on
+             * synchronization
+             */
+            if (w == wayPointFromTimeStamp) {
+                am.timeFromAudio = true;
+            }
+            ml.data.add(am);
+        }
+
+        if (timedMarkersOmitted && !markers.timedMarkersOmitted) {
+            JOptionPane
+            .showMessageDialog(
+                    Main.parent,
+                    tr("Some waypoints with timestamps from before the start of the track or after the end were omitted or moved to the start."));
+            markers.timedMarkersOmitted = timedMarkersOmitted;
+        }
+        if (untimedMarkersOmitted && !markers.untimedMarkersOmitted) {
+            JOptionPane
+            .showMessageDialog(
+                    Main.parent,
+                    tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
+            markers.untimedMarkersOmitted = untimedMarkersOmitted;
+        }
+    }
+
+    
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportImagesAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportImagesAction.java	(revision 5715)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportImagesAction.java	(revision 5715)
@@ -0,0 +1,63 @@
+package org.openstreetmap.josm.gui.layer.gpx;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.LinkedList;
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.gui.widgets.JFileChooserManager;
+import org.openstreetmap.josm.io.JpgImporter;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class ImportImagesAction extends AbstractAction {
+    private final GpxLayer layer;
+
+    public ImportImagesAction(final GpxLayer layer) {
+        super(tr("Import images"), ImageProvider.get("dialogs/geoimage"));
+        this.layer = layer;
+        putValue("help", ht("/Action/ImportImages"));
+    }
+
+    private void warnCantImportIntoServerLayer(GpxLayer layer) {
+        String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>" + "Because its way points do not include a timestamp we cannot correlate them with images.</html>", layer.getName());
+        HelpAwareOptionPane.showOptionDialog(Main.parent, msg, tr("Import not possible"), JOptionPane.WARNING_MESSAGE, ht("/Action/ImportImages#CantImportIntoGpxLayerFromServer"));
+    }
+
+    private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
+        for (File f : sel) {
+            if (f.isDirectory()) {
+                addRecursiveFiles(files, f.listFiles());
+            } else if (f.getName().toLowerCase().endsWith(".jpg")) {
+                files.add(f);
+            }
+        }
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (layer.data.fromServer) {
+            warnCantImportIntoServerLayer(layer);
+            return;
+        }
+        JpgImporter importer = new JpgImporter(layer);
+        JFileChooser fc = new JFileChooserManager(true, "geoimage.lastdirectory", Main.pref.get("lastDirectory")).createFileChooser(true, null, importer.filter, JFileChooser.FILES_AND_DIRECTORIES).openFileChooser();
+        if (fc != null) {
+            File[] sel = fc.getSelectedFiles();
+            if (sel != null && sel.length > 0) {
+                LinkedList<File> files = new LinkedList<File>();
+                addRecursiveFiles(files, sel);
+                importer.importDataHandleExceptions(files, NullProgressMonitor.INSTANCE);
+            }
+        }
+    }
+    
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/gpx/MarkersFromNamedPointsAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/gpx/MarkersFromNamedPointsAction.java	(revision 5715)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/gpx/MarkersFromNamedPointsAction.java	(revision 5715)
@@ -0,0 +1,46 @@
+package org.openstreetmap.josm.gui.layer.gpx;
+
+import java.awt.event.ActionEvent;
+import javax.swing.AbstractAction;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+
+
+public class MarkersFromNamedPointsAction extends AbstractAction {
+    private final GpxLayer layer;
+
+    public MarkersFromNamedPointsAction(final GpxLayer layer) {
+        super(tr("Markers From Named Points"), ImageProvider.get("addmarkers"));
+        this.layer = layer;
+        putValue("help", ht("/Action/MarkersFromNamedPoints"));
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        GpxData namedTrackPoints = new GpxData();
+        for (GpxTrack track : layer.data.tracks) {
+            for (GpxTrackSegment seg : track.getSegments()) {
+                for (WayPoint point : seg.getWayPoints()) {
+                    if (point.attr.containsKey("name") || point.attr.containsKey("desc")) {
+                        namedTrackPoints.waypoints.add(point);
+                    }
+                }
+            }
+        }
+        MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", layer.getName()), layer.getAssociatedFile(), layer);
+        if (ml.data.size() > 0) {
+            Main.main.addLayer(ml);
+        }
+    }
+    
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java	(revision 5714)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java	(revision 5715)
@@ -143,5 +143,5 @@
             Point p = Main.map.mapView.getPoint(en);
             EastNorth enPlus25px = Main.map.mapView.getEastNorth(p.x+dropTolerance, p.y);
-            cw = recent.parentLayer.fromLayer.nearestPointOnTrack(en, enPlus25px.east() - en.east());
+            cw = recent.parentLayer.fromLayer.data.nearestPointOnTrack(en, enPlus25px.east() - en.east());
         }
 
@@ -219,5 +219,5 @@
             Point p = Main.map.mapView.getPoint(en);
             EastNorth enPlus25px = Main.map.mapView.getEastNorth(p.x+dropTolerance, p.y);
-            WayPoint cw = recent.parentLayer.fromLayer.nearestPointOnTrack(en, enPlus25px.east() - en.east());
+            WayPoint cw = recent.parentLayer.fromLayer.data.nearestPointOnTrack(en, enPlus25px.east() - en.east());
             if (cw == null) {
                 JOptionPane.showMessageDialog(
