Changeset 5715 in josm for trunk/src


Ignore:
Timestamp:
2013-02-13T19:12:38+01:00 (11 years ago)
Author:
akks
Message:

see #8416. GpxLayer refactoring: inner classes goes to org.openstreetmap.josm.gui.layer.gpx
Any change of behavior is a bug!

Location:
trunk/src/org/openstreetmap/josm
Files:
9 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java

    r5681 r5715  
    77import java.util.LinkedList;
    88import java.util.Map;
     9import org.openstreetmap.josm.Main;
    910
    1011import org.openstreetmap.josm.data.Bounds;
     12import org.openstreetmap.josm.data.coor.EastNorth;
    1113
    1214/**
     
    122124        return result;
    123125    }
     126   
     127     /**
     128     * Makes a WayPoint at the projection of point P onto the track providing P is less than
     129     * tolerance away from the track
     130     *
     131     * @param P : the point to determine the projection for
     132     * @param tolerance : must be no further than this from the track
     133     * @return the closest point on the track to P, which may be the first or last point if off the
     134     * end of a segment, or may be null if nothing close enough
     135     */
     136    public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
     137        /*
     138         * assume the coordinates of P are xp,yp, and those of a section of track between two
     139         * trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
     140         *
     141         * The equation of RS is Ax + By + C = 0 where A = ys - yr B = xr - xs C = - Axr - Byr
     142         *
     143         * Also, note that the distance RS^2 is A^2 + B^2
     144         *
     145         * If RS^2 == 0.0 ignore the degenerate section of track
     146         *
     147         * PN^2 = (Axp + Byp + C)^2 / RS^2 that is the distance from P to the line
     148         *
     149         * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject the line;
     150         * otherwise... determine if the projected poijnt lies within the bounds of the line: PR^2 -
     151         * PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
     152         *
     153         * where PR^2 = (xp - xr)^2 + (yp-yr)^2 and PS^2 = (xp - xs)^2 + (yp-ys)^2
     154         *
     155         * If so, calculate N as xn = xr + (RN/RS) B yn = y1 + (RN/RS) A
     156         *
     157         * where RN = sqrt(PR^2 - PN^2)
     158         */
     159
     160        double PNminsq = tolerance * tolerance;
     161        EastNorth bestEN = null;
     162        double bestTime = 0.0;
     163        double px = P.east();
     164        double py = P.north();
     165        double rx = 0.0, ry = 0.0, sx, sy, x, y;
     166        if (tracks == null)
     167            return null;
     168        for (GpxTrack track : tracks) {
     169            for (GpxTrackSegment seg : track.getSegments()) {
     170                WayPoint R = null;
     171                for (WayPoint S : seg.getWayPoints()) {
     172                    EastNorth c = S.getEastNorth();
     173                    if (R == null) {
     174                        R = S;
     175                        rx = c.east();
     176                        ry = c.north();
     177                        x = px - rx;
     178                        y = py - ry;
     179                        double PRsq = x * x + y * y;
     180                        if (PRsq < PNminsq) {
     181                            PNminsq = PRsq;
     182                            bestEN = c;
     183                            bestTime = R.time;
     184                        }
     185                    } else {
     186                        sx = c.east();
     187                        sy = c.north();
     188                        double A = sy - ry;
     189                        double B = rx - sx;
     190                        double C = -A * rx - B * ry;
     191                        double RSsq = A * A + B * B;
     192                        if (RSsq == 0.0) {
     193                            continue;
     194                        }
     195                        double PNsq = A * px + B * py + C;
     196                        PNsq = PNsq * PNsq / RSsq;
     197                        if (PNsq < PNminsq) {
     198                            x = px - rx;
     199                            y = py - ry;
     200                            double PRsq = x * x + y * y;
     201                            x = px - sx;
     202                            y = py - sy;
     203                            double PSsq = x * x + y * y;
     204                            if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
     205                                double RNoverRS = Math.sqrt((PRsq - PNsq) / RSsq);
     206                                double nx = rx - RNoverRS * B;
     207                                double ny = ry + RNoverRS * A;
     208                                bestEN = new EastNorth(nx, ny);
     209                                bestTime = R.time + RNoverRS * (S.time - R.time);
     210                                PNminsq = PNsq;
     211                            }
     212                        }
     213                        R = S;
     214                        rx = sx;
     215                        ry = sy;
     216                    }
     217                }
     218                if (R != null) {
     219                    EastNorth c = R.getEastNorth();
     220                    /* if there is only one point in the seg, it will do this twice, but no matter */
     221                    rx = c.east();
     222                    ry = c.north();
     223                    x = px - rx;
     224                    y = py - ry;
     225                    double PRsq = x * x + y * y;
     226                    if (PRsq < PNminsq) {
     227                        PNminsq = PRsq;
     228                        bestEN = c;
     229                        bestTime = R.time;
     230                    }
     231                }
     232            }
     233        }
     234        if (bestEN == null)
     235            return null;
     236        WayPoint best = new WayPoint(Main.getProjection().eastNorth2latlon(bestEN));
     237        best.time = bestTime;
     238        return best;
     239    }
    124240}
  • trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java

    r5681 r5715  
    33package org.openstreetmap.josm.gui.layer;
    44
    5 import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
    65import static org.openstreetmap.josm.tools.I18n.marktr;
    76import static org.openstreetmap.josm.tools.I18n.tr;
     
    109import java.awt.BasicStroke;
    1110import java.awt.Color;
    12 import java.awt.Component;
    1311import java.awt.Dimension;
    1412import java.awt.Graphics2D;
    15 import java.awt.GridBagLayout;
    1613import java.awt.Point;
    1714import java.awt.RenderingHints;
    1815import java.awt.Stroke;
    19 import java.awt.Toolkit;
    20 import java.awt.event.ActionEvent;
    21 import java.awt.event.MouseAdapter;
    22 import java.awt.event.MouseEvent;
    23 import java.awt.event.MouseListener;
    24 import java.awt.geom.Area;
    25 import java.awt.geom.Rectangle2D;
    2616import java.io.File;
    27 import java.io.IOException;
    28 import java.net.MalformedURLException;
    29 import java.net.URL;
    3017import java.text.DateFormat;
    3118import java.util.ArrayList;
    32 import java.util.Arrays;
    33 import java.util.Collection;
    34 import java.util.Collections;
    35 import java.util.Comparator;
    3619import java.util.LinkedList;
    3720import java.util.List;
    38 import java.util.Map;
    39 import java.util.concurrent.Future;
    40 
    41 import javax.swing.AbstractAction;
     21
    4222import javax.swing.Action;
    43 import javax.swing.BorderFactory;
    4423import javax.swing.Icon;
    45 import javax.swing.JComponent;
    46 import javax.swing.JFileChooser;
    47 import javax.swing.JLabel;
    48 import javax.swing.JList;
    49 import javax.swing.JMenuItem;
    50 import javax.swing.JOptionPane;
    51 import javax.swing.JPanel;
    5224import javax.swing.JScrollPane;
    53 import javax.swing.JTable;
    54 import javax.swing.ListSelectionModel;
    5525import javax.swing.SwingUtilities;
    56 import javax.swing.event.ListSelectionEvent;
    57 import javax.swing.event.ListSelectionListener;
    58 import javax.swing.filechooser.FileFilter;
    59 import javax.swing.table.TableCellRenderer;
    6026
    6127import org.openstreetmap.josm.Main;
    62 import org.openstreetmap.josm.actions.AbstractMergeAction.LayerListCellRenderer;
    63 import org.openstreetmap.josm.actions.DiskAccessAction;
    6428import org.openstreetmap.josm.actions.RenameLayerAction;
    6529import org.openstreetmap.josm.actions.SaveActionBase;
    66 import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTaskList;
    6730import org.openstreetmap.josm.data.Bounds;
    68 import org.openstreetmap.josm.data.coor.EastNorth;
    6931import org.openstreetmap.josm.data.coor.LatLon;
    7032import org.openstreetmap.josm.data.gpx.GpxConstants;
     
    7436import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
    7537import org.openstreetmap.josm.data.gpx.WayPoint;
    76 import org.openstreetmap.josm.data.osm.DataSet;
    77 import org.openstreetmap.josm.data.osm.Node;
    78 import org.openstreetmap.josm.data.osm.Way;
    7938import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    8039import org.openstreetmap.josm.data.projection.Projection;
    81 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    82 import org.openstreetmap.josm.gui.ExtendedDialog;
    83 import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    8440import org.openstreetmap.josm.gui.MapView;
    8541import org.openstreetmap.josm.gui.NavigatableComponent;
    86 import org.openstreetmap.josm.gui.PleaseWaitRunnable;
    8742import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
    8843import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
    89 import org.openstreetmap.josm.gui.layer.WMSLayer.PrecacheTask;
    90 import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
    91 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
    92 import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
    93 import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    94 import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
    95 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    96 import org.openstreetmap.josm.gui.progress.ProgressTaskId;
    97 import org.openstreetmap.josm.gui.progress.ProgressTaskIds;
     44import org.openstreetmap.josm.gui.layer.gpx.ChooseTrackVisibilityAction;
     45import org.openstreetmap.josm.gui.layer.gpx.ConvertToDataLayerAction;
     46import org.openstreetmap.josm.gui.layer.gpx.CustomizeDrawingAction;
     47import org.openstreetmap.josm.gui.layer.gpx.DownloadAlongTrackAction;
     48import org.openstreetmap.josm.gui.layer.gpx.DownloadWmsAlongTrackAction;
     49import org.openstreetmap.josm.gui.layer.gpx.ImportAudioAction;
     50import org.openstreetmap.josm.gui.layer.gpx.ImportImagesAction;
     51import org.openstreetmap.josm.gui.layer.gpx.MarkersFromNamedPointsAction;
    9852import org.openstreetmap.josm.gui.widgets.HtmlPanel;
    99 import org.openstreetmap.josm.gui.widgets.JFileChooserManager;
    100 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
    10153import org.openstreetmap.josm.io.GpxImporter;
    102 import org.openstreetmap.josm.io.JpgImporter;
    103 import org.openstreetmap.josm.io.OsmTransferException;
    104 import org.openstreetmap.josm.tools.AudioUtil;
    105 import org.openstreetmap.josm.tools.DateUtils;
    106 import org.openstreetmap.josm.tools.GBC;
    10754import org.openstreetmap.josm.tools.ImageProvider;
    108 import org.openstreetmap.josm.tools.OpenBrowser;
    109 import org.openstreetmap.josm.tools.UrlLabel;
    11055import org.openstreetmap.josm.tools.Utils;
    111 import org.openstreetmap.josm.tools.WindowGeometry;
    112 import org.xml.sax.SAXException;
    11356
    11457public class GpxLayer extends Layer {
    115 
    116     private static final String PREF_DOWNLOAD_ALONG_TRACK_DISTANCE = "gpxLayer.downloadAlongTrack.distance";
    117     private static final String PREF_DOWNLOAD_ALONG_TRACK_AREA = "gpxLayer.downloadAlongTrack.area";
    118     private static final String PREF_DOWNLOAD_ALONG_TRACK_NEAR = "gpxLayer.downloadAlongTrack.near";
    11958
    12059    public GpxData data;
     
    12867    private boolean isLocalFile;
    12968    // used by ChooseTrackVisibilityAction to determine which tracks to show/hide
    130     private boolean[] trackVisibility = new boolean[0];
     69    public boolean[] trackVisibility = new boolean[0];
    13170
    13271    private final List<GpxTrack> lastTracks = new ArrayList<GpxTrack>(); // List of tracks at last paint
    13372    private int lastUpdateCount;
    134 
    135     private static class Markers {
    136         public boolean timedMarkersOmitted = false;
    137         public boolean untimedMarkersOmitted = false;
    138     }
    13973
    14074    public GpxLayer(GpxData d) {
     
    15993     * returns a human readable string that shows the timespan of the given track
    16094     */
    161     private static String getTimespanForTrack(GpxTrack trk) {
     95    public static String getTimespanForTrack(GpxTrack trk) {
    16296        WayPoint earliest = null, latest = null;
    16397
     
    286220    @Override
    287221    public Action[] getMenuEntries() {
    288         if (Main.applet)
     222        if (Main.applet) {
    289223            return new Action[] {
    290224                LayerListDialog.getInstance().createShowHideLayerAction(),
     
    292226                SeparatorLayerAction.INSTANCE,
    293227                new CustomizeColor(this),
    294                 new CustomizeDrawing(this),
    295                 new ConvertToDataLayerAction(),
     228                new CustomizeDrawingAction(this),
     229                new ConvertToDataLayerAction(this),
    296230                SeparatorLayerAction.INSTANCE,
    297                 new ChooseTrackVisibilityAction(),
     231                new ChooseTrackVisibilityAction(this),
    298232                new RenameLayerAction(getAssociatedFile(), this),
    299233                SeparatorLayerAction.INSTANCE,
    300234                new LayerListPopup.InfoAction(this) };
     235        }
    301236        return new Action[] {
    302237                LayerListDialog.getInstance().createShowHideLayerAction(),
     
    306241                new LayerSaveAsAction(this),
    307242                new CustomizeColor(this),
    308                 new CustomizeDrawing(this),
    309                 new ImportImages(),
    310                 new ImportAudio(),
    311                 new MarkersFromNamedPoins(),
    312                 new ConvertToDataLayerAction(),
    313                 new DownloadAlongTrackAction(),
    314                 new DownloadWmsAlongTrackAction(),
     243                new CustomizeDrawingAction(this),
     244                new ImportImagesAction(this),
     245                new ImportAudioAction(this),
     246                new MarkersFromNamedPointsAction(this),
     247                new ConvertToDataLayerAction(this),
     248                new DownloadAlongTrackAction(data),
     249                new DownloadWmsAlongTrackAction(data),
    315250                SeparatorLayerAction.INSTANCE,
    316                 new ChooseTrackVisibilityAction(),
     251                new ChooseTrackVisibilityAction(this),
    317252                new RenameLayerAction(getAssociatedFile(), this),
    318253                SeparatorLayerAction.INSTANCE,
    319254                new LayerListPopup.InfoAction(this) };
     255    }
     256   
     257    public boolean isLocalFile() {
     258        return isLocalFile;
    320259    }
    321260
     
    829768    }
    830769
    831     public class ConvertToDataLayerAction extends AbstractAction {
    832         public ConvertToDataLayerAction() {
    833             super(tr("Convert to data layer"), ImageProvider.get("converttoosm"));
    834             putValue("help", ht("/Action/ConvertToDataLayer"));
    835         }
    836 
    837         @Override
    838         public void actionPerformed(ActionEvent e) {
    839             JPanel msg = new JPanel(new GridBagLayout());
    840             msg
    841             .add(
    842                     new JLabel(
    843                             tr("<html>Upload of unprocessed GPS data as map data is considered harmful.<br>If you want to upload traces, look here:</html>")),
    844                             GBC.eol());
    845             msg.add(new UrlLabel(tr("http://www.openstreetmap.org/traces"),2), GBC.eop());
    846             if (!ConditionalOptionPaneUtil.showConfirmationDialog("convert_to_data", Main.parent, msg, tr("Warning"),
    847                     JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, JOptionPane.OK_OPTION))
    848                 return;
    849             DataSet ds = new DataSet();
    850             for (GpxTrack trk : data.tracks) {
    851                 for (GpxTrackSegment segment : trk.getSegments()) {
    852                     List<Node> nodes = new ArrayList<Node>();
    853                     for (WayPoint p : segment.getWayPoints()) {
    854                         Node n = new Node(p.getCoor());
    855                         String timestr = p.getString("time");
    856                         if (timestr != null) {
    857                             n.setTimestamp(DateUtils.fromString(timestr));
    858                         }
    859                         ds.addPrimitive(n);
    860                         nodes.add(n);
    861                     }
    862                     Way w = new Way();
    863                     w.setNodes(nodes);
    864                     ds.addPrimitive(w);
    865                 }
    866             }
    867             Main.main
    868             .addLayer(new OsmDataLayer(ds, tr("Converted from: {0}", GpxLayer.this.getName()), getAssociatedFile()));
    869             Main.main.removeLayer(GpxLayer.this);
    870         }
    871     }
    872 
    873770    @Override
    874771    public File getAssociatedFile() {
     
    899796    }
    900797
    901     /**
    902      * allows the user to choose which of the downloaded tracks should be displayed.
    903      * they can be chosen from the gpx layer context menu.
    904      */
    905     public class ChooseTrackVisibilityAction extends AbstractAction {
    906         public ChooseTrackVisibilityAction() {
    907             super(tr("Choose visible tracks"), ImageProvider.get("dialogs/filter"));
    908             putValue("help", ht("/Action/ChooseTrackVisibility"));
    909         }
    910 
    911         /**
    912          * gathers all available data for the tracks and returns them as array of arrays
    913          * in the expected column order  */
    914         private Object[][] buildTableContents() {
    915             Object[][] tracks = new Object[data.tracks.size()][5];
    916             int i = 0;
    917             for (GpxTrack trk : data.tracks) {
    918                 Map<String, Object> attr = trk.getAttributes();
    919                 String name = (String) (attr.containsKey("name") ? attr.get("name") : "");
    920                 String desc = (String) (attr.containsKey("desc") ? attr.get("desc") : "");
    921                 String time = getTimespanForTrack(trk);
    922                 String length = NavigatableComponent.getSystemOfMeasurement().getDistText(trk.length());
    923                 String url = (String) (attr.containsKey("url") ? attr.get("url") : "");
    924                 tracks[i] = new String[] {name, desc, time, length, url};
    925                 i++;
    926             }
    927             return tracks;
    928         }
    929 
    930         /**
    931          * Builds an non-editable table whose 5th column will open a browser when double clicked.
    932          * The table will fill its parent. */
    933         private JTable buildTable(String[] headers, Object[][] content) {
    934             final JTable t = new JTable(content, headers) {
    935                 @Override
    936                 public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
    937                     Component c = super.prepareRenderer(renderer, row, col);
    938                     if (c instanceof JComponent) {
    939                         JComponent jc = (JComponent)c;
    940                         jc.setToolTipText((String)getValueAt(row, col));
    941                     }
    942                     return c;
    943                 }
    944 
    945                 @Override
    946                 public boolean isCellEditable(int rowIndex, int colIndex) {
    947                     return false;
    948                 }
    949             };
    950             // default column widths
    951             t.getColumnModel().getColumn(0).setPreferredWidth(220);
    952             t.getColumnModel().getColumn(1).setPreferredWidth(300);
    953             t.getColumnModel().getColumn(2).setPreferredWidth(200);
    954             t.getColumnModel().getColumn(3).setPreferredWidth(50);
    955             t.getColumnModel().getColumn(4).setPreferredWidth(100);
    956             // make the link clickable
    957             final MouseListener urlOpener = new MouseAdapter() {
    958                 @Override
    959                 public void mouseClicked(MouseEvent e) {
    960                     if (e.getClickCount() != 2)
    961                         return;
    962                     JTable t = (JTable)e.getSource();
    963                     int col = t.convertColumnIndexToModel(t.columnAtPoint(e.getPoint()));
    964                     if(col != 4) // only accept clicks on the URL column
    965                         return;
    966                     int row = t.rowAtPoint(e.getPoint());
    967                     String url = (String) t.getValueAt(row, col);
    968                     if (url == null || url.isEmpty())
    969                         return;
    970                     OpenBrowser.displayUrl(url);
    971                 }
    972             };
    973             t.addMouseListener(urlOpener);
    974             t.setFillsViewportHeight(true);
    975             return t;
    976         }
    977 
    978         /** selects all rows (=tracks) in the table that are currently visible */
    979         private void selectVisibleTracksInTable(JTable table) {
    980             // don't select any tracks if the layer is not visible
    981             if(!isVisible())
    982                 return;
    983             ListSelectionModel s = table.getSelectionModel();
    984             s.clearSelection();
    985             for(int i=0; i < trackVisibility.length; i++)
    986                 if(trackVisibility[i]) {
    987                     s.addSelectionInterval(i, i);
    988                 }
    989         }
    990 
    991         /** listens to selection changes in the table and redraws the map */
    992         private void listenToSelectionChanges(JTable table) {
    993             table.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
    994                 public void valueChanged(ListSelectionEvent e) {
    995                     if(!(e.getSource() instanceof ListSelectionModel))
    996                         return;
    997 
    998                     ListSelectionModel s =  (ListSelectionModel) e.getSource();
    999                     for(int i = 0; i < data.tracks.size(); i++) {
    1000                         trackVisibility[i] = s.isSelectedIndex(i);
    1001                     }
    1002                     Main.map.mapView.preferenceChanged(null);
    1003                     Main.map.repaint(100);
    1004                 }
    1005             });
    1006         }
    1007 
    1008         @Override
    1009         public void actionPerformed(ActionEvent arg0) {
    1010             final JPanel msg = new JPanel(new GridBagLayout());
    1011             msg.add(new JLabel(tr("<html>Select all tracks that you want to be displayed. You can drag select a "
    1012                     + "range of tracks or use CTRL+Click to select specific ones. The map is updated live in the "
    1013                     + "background. Open the URLs by double clicking them.</html>")),
    1014                     GBC.eol().fill(GBC.HORIZONTAL));
    1015 
    1016             // build table
    1017             final boolean[] trackVisibilityBackup = trackVisibility.clone();
    1018             final String[] headers = {tr("Name"), tr("Description"), tr("Timespan"), tr("Length"), tr("URL")};
    1019             final JTable table = buildTable(headers, buildTableContents());
    1020             selectVisibleTracksInTable(table);
    1021             listenToSelectionChanges(table);
    1022 
    1023             // make the table scrollable
    1024             JScrollPane scrollPane = new JScrollPane(table);
    1025             msg.add(scrollPane, GBC.eol().fill(GBC.BOTH));
    1026 
    1027             // build dialog
    1028             ExtendedDialog ed = new ExtendedDialog(
    1029                     Main.parent, tr("Set track visibility for {0}", getName()),
    1030                     new String[] {tr("Show all"), tr("Show selected only"), tr("Cancel")});
    1031             ed.setButtonIcons(new String[] {"dialogs/layerlist/eye", "dialogs/filter", "cancel"});
    1032             ed.setContent(msg, false);
    1033             ed.setDefaultButton(2);
    1034             ed.setCancelButton(3);
    1035             ed.configureContextsensitiveHelp("/Action/ChooseTrackVisibility", true);
    1036             ed.setRememberWindowGeometry(
    1037                     getClass().getName() + ".geometry",
    1038                     WindowGeometry.centerInWindow(Main.parent, new Dimension(1000, 500))
    1039                     );
    1040             ed.showDialog();
    1041             int v = ed.getValue();
    1042             // cancel for unknown buttons and copy back original settings
    1043             if(v != 1 && v != 2) {
    1044                 for(int i = 0; i < data.tracks.size(); i++) {
    1045                     trackVisibility[i] = trackVisibilityBackup[i];
    1046                 }
    1047                 Main.map.repaint();
    1048                 return;
    1049             }
    1050 
    1051             // set visibility (1 = show all, 2 = filter). If no tracks are selected
    1052             // set all of them visible and...
    1053             ListSelectionModel s = table.getSelectionModel();
    1054             final boolean all = v == 1 || s.isSelectionEmpty();
    1055             for(int i = 0; i < data.tracks.size(); i++) {
    1056                 trackVisibility[i] = all || s.isSelectedIndex(i);
    1057             }
    1058             // ...sync with layer visibility instead to avoid having two ways to hide everything
    1059             setVisible(v == 1 || !s.isSelectionEmpty());
    1060             Main.map.repaint();
    1061         }
    1062     }
    1063 
    1064     /**
    1065      * Action that issues a series of download requests to the API, following the GPX track.
    1066      *
    1067      * @author fred
    1068      */
    1069     public class DownloadAlongTrackAction extends AbstractAction {
    1070         final static int NEAR_TRACK=0;
    1071         final static int NEAR_WAYPOINTS=1;
    1072         final static int NEAR_BOTH=2;
    1073         final Integer dist[] = { 5000, 500, 50 };
    1074         final Integer area[] = { 20, 10, 5, 1 };
    1075 
    1076         public DownloadAlongTrackAction() {
    1077             super(tr("Download from OSM along this track"), ImageProvider.get("downloadalongtrack"));
    1078         }
    1079 
    1080         @Override
    1081         public void actionPerformed(ActionEvent e) {
    1082             /*
    1083              * build selection dialog
    1084              */
    1085             JPanel msg = new JPanel(new GridBagLayout());
    1086 
    1087             msg.add(new JLabel(tr("Download everything within:")), GBC.eol());
    1088             String s[] = new String[dist.length];
    1089             for (int i = 0; i < dist.length; ++i) {
    1090                 s[i] = tr("{0} meters", dist[i]);
    1091             }
    1092             JList buffer = new JList(s);
    1093             buffer.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, 0));
    1094             msg.add(buffer, GBC.eol());
    1095 
    1096             msg.add(new JLabel(tr("Maximum area per request:")), GBC.eol());
    1097             s = new String[area.length];
    1098             for (int i = 0; i < area.length; ++i) {
    1099                 s[i] = tr("{0} sq km", area[i]);
    1100             }
    1101             JList maxRect = new JList(s);
    1102             maxRect.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, 0));
    1103             msg.add(maxRect, GBC.eol());
    1104 
    1105             msg.add(new JLabel(tr("Download near:")), GBC.eol());
    1106             JList downloadNear = new JList(new String[] { tr("track only"), tr("waypoints only"), tr("track and waypoints") });
    1107 
    1108             downloadNear.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, 0));
    1109             msg.add(downloadNear, GBC.eol());
    1110 
    1111             int ret = JOptionPane.showConfirmDialog(
    1112                     Main.parent,
    1113                     msg,
    1114                     tr("Download from OSM along this track"),
    1115                     JOptionPane.OK_CANCEL_OPTION,
    1116                     JOptionPane.QUESTION_MESSAGE
    1117                     );
    1118             switch(ret) {
    1119             case JOptionPane.CANCEL_OPTION:
    1120             case JOptionPane.CLOSED_OPTION:
    1121                 return;
    1122             default:
    1123                 // continue
    1124             }
    1125 
    1126             Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, buffer.getSelectedIndex());
    1127             Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, maxRect.getSelectedIndex());
    1128             final int near = downloadNear.getSelectedIndex();
    1129             Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, near);
    1130 
    1131             /*
    1132              * Find the average latitude for the data we're contemplating, so we can know how many
    1133              * metres per degree of longitude we have.
    1134              */
    1135             double latsum = 0;
    1136             int latcnt = 0;
    1137 
    1138             if (near == NEAR_TRACK || near == NEAR_BOTH) {
    1139                 for (GpxTrack trk : data.tracks) {
    1140                     for (GpxTrackSegment segment : trk.getSegments()) {
    1141                         for (WayPoint p : segment.getWayPoints()) {
    1142                             latsum += p.getCoor().lat();
    1143                             latcnt++;
    1144                         }
    1145                     }
    1146                 }
    1147             }
    1148 
    1149             if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
    1150                 for (WayPoint p : data.waypoints) {
    1151                     latsum += p.getCoor().lat();
    1152                     latcnt++;
    1153                 }
    1154             }
    1155 
    1156             double avglat = latsum / latcnt;
    1157             double scale = Math.cos(Math.toRadians(avglat));
    1158 
    1159             /*
    1160              * Compute buffer zone extents and maximum bounding box size. Note that the maximum we
    1161              * ever offer is a bbox area of 0.002, while the API theoretically supports 0.25, but as
    1162              * soon as you touch any built-up area, that kind of bounding box will download forever
    1163              * and then stop because it has more than 50k nodes.
    1164              */
    1165             Integer i = buffer.getSelectedIndex();
    1166             final int buffer_dist = dist[i < 0 ? 0 : i];
    1167             i = maxRect.getSelectedIndex();
    1168             final double max_area = area[i < 0 ? 0 : i] / 10000.0 / scale;
    1169             final double buffer_y = buffer_dist / 100000.0;
    1170             final double buffer_x = buffer_y / scale;
    1171 
    1172             final int totalTicks = latcnt;
    1173             // guess if a progress bar might be useful.
    1174             final boolean displayProgress = totalTicks > 2000 && buffer_y < 0.01;
    1175 
    1176             class CalculateDownloadArea extends PleaseWaitRunnable {
    1177                 private Area a = new Area();
    1178                 private boolean cancel = false;
    1179                 private int ticks = 0;
    1180                 private Rectangle2D r = new Rectangle2D.Double();
    1181 
    1182                 public CalculateDownloadArea() {
    1183                     super(tr("Calculating Download Area"),
    1184                             (displayProgress ? null : NullProgressMonitor.INSTANCE),
    1185                             false);
    1186                 }
    1187 
    1188                 @Override
    1189                 protected void cancel() {
    1190                     cancel = true;
    1191                 }
    1192 
    1193                 @Override
    1194                 protected void finish() {
    1195                 }
    1196 
    1197                 @Override
    1198                 protected void afterFinish() {
    1199                     if(cancel)
    1200                         return;
    1201                     confirmAndDownloadAreas(a, max_area, progressMonitor);
    1202                 }
    1203 
    1204                 /**
    1205                  * increase tick count by one, report progress every 100 ticks
    1206                  */
    1207                 private void tick() {
    1208                     ticks++;
    1209                     if(ticks % 100 == 0) {
    1210                         progressMonitor.worked(100);
    1211                     }
    1212                 }
    1213 
    1214                 /**
    1215                  * calculate area for single, given way point and return new LatLon if the
    1216                  * way point has been used to modify the area.
    1217                  */
    1218                 private LatLon calcAreaForWayPoint(WayPoint p, LatLon previous) {
    1219                     tick();
    1220                     LatLon c = p.getCoor();
    1221                     if (previous == null || c.greatCircleDistance(previous) > buffer_dist) {
    1222                         // we add a buffer around the point.
    1223                         r.setRect(c.lon() - buffer_x, c.lat() - buffer_y, 2 * buffer_x, 2 * buffer_y);
    1224                         a.add(new Area(r));
    1225                         return c;
    1226                     }
    1227                     return previous;
    1228                 }
    1229 
    1230                 @Override
    1231                 protected void realRun() {
    1232                     progressMonitor.setTicksCount(totalTicks);
    1233                     /*
    1234                      * Collect the combined area of all gpx points plus buffer zones around them. We ignore
    1235                      * points that lie closer to the previous point than the given buffer size because
    1236                      * otherwise this operation takes ages.
    1237                      */
    1238                     LatLon previous = null;
    1239                     if (near == NEAR_TRACK || near == NEAR_BOTH) {
    1240                         for (GpxTrack trk : data.tracks) {
    1241                             for (GpxTrackSegment segment : trk.getSegments()) {
    1242                                 for (WayPoint p : segment.getWayPoints()) {
    1243                                     if(cancel)
    1244                                         return;
    1245                                     previous = calcAreaForWayPoint(p, previous);
    1246                                 }
    1247                             }
    1248                         }
    1249                     }
    1250                     if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
    1251                         for (WayPoint p : data.waypoints) {
    1252                             if(cancel)
    1253                                 return;
    1254                             previous = calcAreaForWayPoint(p, previous);
    1255                         }
    1256                     }
    1257                 }
    1258             }
    1259 
    1260             Main.worker.submit(new CalculateDownloadArea());
    1261         }
    1262 
    1263 
    1264         /**
    1265          * Area "a" contains the hull that we would like to download data for. however we
    1266          * can only download rectangles, so the following is an attempt at finding a number of
    1267          * rectangles to download.
    1268          *
    1269          * The idea is simply: Start out with the full bounding box. If it is too large, then
    1270          * split it in half and repeat recursively for each half until you arrive at something
    1271          * small enough to download. The algorithm is improved by always using the intersection
    1272          * between the rectangle and the actual desired area. For example, if you have a track
    1273          * that goes like this: +----+ | /| | / | | / | |/ | +----+ then we would first look at
    1274          * downloading the whole rectangle (assume it's too big), after that we split it in half
    1275          * (upper and lower half), but we donot request the full upper and lower rectangle, only
    1276          * the part of the upper/lower rectangle that actually has something in it.
    1277          *
    1278          * This functions calculates the rectangles, asks the user to continue and downloads
    1279          * the areas if applicable.
    1280          */
    1281         private void confirmAndDownloadAreas(Area a, double max_area, ProgressMonitor progressMonitor) {
    1282             List<Rectangle2D> toDownload = new ArrayList<Rectangle2D>();
    1283 
    1284             addToDownload(a, a.getBounds(), toDownload, max_area);
    1285 
    1286             if(toDownload.size() == 0)
    1287                 return;
    1288 
    1289             JPanel msg = new JPanel(new GridBagLayout());
    1290 
    1291             msg.add(new JLabel(
    1292                     tr("<html>This action will require {0} individual<br>"
    1293                             + "download requests. Do you wish<br>to continue?</html>",
    1294                             toDownload.size())), GBC.eol());
    1295 
    1296             if (toDownload.size() > 1) {
    1297                 int ret = JOptionPane.showConfirmDialog(
    1298                         Main.parent,
    1299                         msg,
    1300                         tr("Download from OSM along this track"),
    1301                         JOptionPane.OK_CANCEL_OPTION,
    1302                         JOptionPane.PLAIN_MESSAGE
    1303                         );
    1304                 switch(ret) {
    1305                 case JOptionPane.CANCEL_OPTION:
    1306                 case JOptionPane.CLOSED_OPTION:
    1307                     return;
    1308                 default:
    1309                     // continue
    1310                 }
    1311             }
    1312             final PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Download data"));
    1313             final Future<?> future = new DownloadOsmTaskList().download(false, toDownload, monitor);
    1314             Main.worker.submit(
    1315                     new Runnable() {
    1316                         @Override
    1317                         public void run() {
    1318                             try {
    1319                                 future.get();
    1320                             } catch(Exception e) {
    1321                                 e.printStackTrace();
    1322                                 return;
    1323                             }
    1324                             monitor.close();
    1325                         }
    1326                     }
    1327                     );
    1328         }
    1329     }
    1330 
    1331 
    1332     public class DownloadWmsAlongTrackAction extends AbstractAction {
    1333         public DownloadWmsAlongTrackAction() {
    1334             super(tr("Precache imagery tiles along this track"), ImageProvider.get("downloadalongtrack"));
    1335         }
    1336 
    1337         public void actionPerformed(ActionEvent e) {
    1338 
    1339             final List<LatLon> points = new ArrayList<LatLon>();
    1340 
    1341             for (GpxTrack trk : data.tracks) {
    1342                 for (GpxTrackSegment segment : trk.getSegments()) {
    1343                     for (WayPoint p : segment.getWayPoints()) {
    1344                         points.add(p.getCoor());
    1345                     }
    1346                 }
    1347             }
    1348             for (WayPoint p : data.waypoints) {
    1349                 points.add(p.getCoor());
    1350             }
    1351 
    1352 
    1353             final WMSLayer layer = askWMSLayer();
    1354             if (layer != null) {
    1355                 PleaseWaitRunnable task = new PleaseWaitRunnable(tr("Precaching WMS")) {
    1356 
    1357                     private PrecacheTask precacheTask;
    1358 
    1359                     @Override
    1360                     protected void realRun() throws SAXException, IOException, OsmTransferException {
    1361                         precacheTask = new PrecacheTask(progressMonitor);
    1362                         layer.downloadAreaToCache(precacheTask, points, 0, 0);
    1363                         while (!precacheTask.isFinished() && !progressMonitor.isCanceled()) {
    1364                             synchronized (this) {
    1365                                 try {
    1366                                     wait(200);
    1367                                 } catch (InterruptedException e) {
    1368                                     e.printStackTrace();
    1369                                 }
    1370                             }
    1371                         }
    1372                     }
    1373 
    1374                     @Override
    1375                     protected void finish() {
    1376                     }
    1377 
    1378                     @Override
    1379                     protected void cancel() {
    1380                         precacheTask.cancel();
    1381                     }
    1382 
    1383                     @Override
    1384                     public ProgressTaskId canRunInBackground() {
    1385                         return ProgressTaskIds.PRECACHE_WMS;
    1386                     }
    1387                 };
    1388                 Main.worker.execute(task);
    1389             }
    1390 
    1391 
    1392         }
    1393 
    1394         protected WMSLayer askWMSLayer() {
    1395             List<WMSLayer> targetLayers = Main.map.mapView.getLayersOfType(WMSLayer.class);
    1396 
    1397             if (targetLayers.isEmpty()) {
    1398                 warnNoImageryLayers();
    1399                 return null;
    1400             }
    1401 
    1402             JosmComboBox layerList = new JosmComboBox(targetLayers.toArray());
    1403             layerList.setRenderer(new LayerListCellRenderer());
    1404             layerList.setSelectedIndex(0);
    1405 
    1406             JPanel pnl = new JPanel(new GridBagLayout());
    1407             pnl.add(new JLabel(tr("Please select the imagery layer.")), GBC.eol());
    1408             pnl.add(layerList, GBC.eol());
    1409 
    1410             ExtendedDialog ed = new ExtendedDialog(Main.parent,
    1411                     tr("Select imagery layer"),
    1412                     new String[] { tr("Download"), tr("Cancel") });
    1413             ed.setButtonIcons(new String[] { "dialogs/down", "cancel" });
    1414             ed.setContent(pnl);
    1415             ed.showDialog();
    1416             if (ed.getValue() != 1)
    1417                 return null;
    1418 
    1419             return (WMSLayer) layerList.getSelectedItem();
    1420         }
    1421 
    1422         protected void warnNoImageryLayers() {
    1423             JOptionPane.showMessageDialog(Main.parent,
    1424                     tr("There are no imagery layers."),
    1425                     tr("No imagery layers"), JOptionPane.WARNING_MESSAGE);
    1426         }
    1427     }
    1428 
    1429     private static void addToDownload(Area a, Rectangle2D r, Collection<Rectangle2D> results, double max_area) {
    1430         Area tmp = new Area(r);
    1431         // intersect with sought-after area
    1432         tmp.intersect(a);
    1433         if (tmp.isEmpty())
    1434             return;
    1435         Rectangle2D bounds = tmp.getBounds2D();
    1436         if (bounds.getWidth() * bounds.getHeight() > max_area) {
    1437             // the rectangle gets too large; split it and make recursive call.
    1438             Rectangle2D r1;
    1439             Rectangle2D r2;
    1440             if (bounds.getWidth() > bounds.getHeight()) {
    1441                 // rectangles that are wider than high are split into a left and right half,
    1442                 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth() / 2, bounds.getHeight());
    1443                 r2 = new Rectangle2D.Double(bounds.getX() + bounds.getWidth() / 2, bounds.getY(),
    1444                         bounds.getWidth() / 2, bounds.getHeight());
    1445             } else {
    1446                 // others into a top and bottom half.
    1447                 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() / 2);
    1448                 r2 = new Rectangle2D.Double(bounds.getX(), bounds.getY() + bounds.getHeight() / 2, bounds.getWidth(),
    1449                         bounds.getHeight() / 2);
    1450             }
    1451             addToDownload(a, r1, results, max_area);
    1452             addToDownload(a, r2, results, max_area);
    1453         } else {
    1454             results.add(bounds);
    1455         }
    1456     }
    1457 
    1458     /**
    1459      * Makes a new marker layer derived from this GpxLayer containing at least one audio marker
    1460      * which the given audio file is associated with. Markers are derived from the following (a)
    1461      * explict waypoints in the GPX layer, or (b) named trackpoints in the GPX layer, or (d)
    1462      * timestamp on the wav file (e) (in future) voice recognised markers in the sound recording (f)
    1463      * a single marker at the beginning of the track
    1464      * @param wavFile : the file to be associated with the markers in the new marker layer
    1465      * @param markers : keeps track of warning messages to avoid repeated warnings
    1466      */
    1467     private void importAudio(File wavFile, MarkerLayer ml, double firstStartTime, Markers markers) {
    1468         URL url = null;
    1469         try {
    1470             url = wavFile.toURI().toURL();
    1471         } catch (MalformedURLException e) {
    1472             System.err.println("Unable to convert filename " + wavFile.getAbsolutePath() + " to URL");
    1473         }
    1474         Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
    1475         boolean timedMarkersOmitted = false;
    1476         boolean untimedMarkersOmitted = false;
    1477         double snapDistance = Main.pref.getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3); /*
    1478          * about
    1479          * 25
    1480          * m
    1481          */
    1482         WayPoint wayPointFromTimeStamp = null;
    1483 
    1484         // determine time of first point in track
    1485         double firstTime = -1.0;
    1486         if (data.tracks != null && !data.tracks.isEmpty()) {
    1487             for (GpxTrack track : data.tracks) {
    1488                 for (GpxTrackSegment seg : track.getSegments()) {
    1489                     for (WayPoint w : seg.getWayPoints()) {
    1490                         firstTime = w.time;
    1491                         break;
    1492                     }
    1493                     if (firstTime >= 0.0) {
    1494                         break;
    1495                     }
    1496                 }
    1497                 if (firstTime >= 0.0) {
    1498                     break;
    1499                 }
    1500             }
    1501         }
    1502         if (firstTime < 0.0) {
    1503             JOptionPane.showMessageDialog(
    1504                     Main.parent,
    1505                     tr("No GPX track available in layer to associate audio with."),
    1506                     tr("Error"),
    1507                     JOptionPane.ERROR_MESSAGE
    1508                     );
    1509             return;
    1510         }
    1511 
    1512         // (a) try explicit timestamped waypoints - unless suppressed
    1513         if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) && data.waypoints != null
    1514                 && !data.waypoints.isEmpty()) {
    1515             for (WayPoint w : data.waypoints) {
    1516                 if (w.time > firstTime) {
    1517                     waypoints.add(w);
    1518                 } else if (w.time > 0.0) {
    1519                     timedMarkersOmitted = true;
    1520                 }
    1521             }
    1522         }
    1523 
    1524         // (b) try explicit waypoints without timestamps - unless suppressed
    1525         if (Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true) && data.waypoints != null
    1526                 && !data.waypoints.isEmpty()) {
    1527             for (WayPoint w : data.waypoints) {
    1528                 if (waypoints.contains(w)) {
    1529                     continue;
    1530                 }
    1531                 WayPoint wNear = nearestPointOnTrack(w.getEastNorth(), snapDistance);
    1532                 if (wNear != null) {
    1533                     WayPoint wc = new WayPoint(w.getCoor());
    1534                     wc.time = wNear.time;
    1535                     if (w.attr.containsKey("name")) {
    1536                         wc.attr.put("name", w.getString("name"));
    1537                     }
    1538                     waypoints.add(wc);
    1539                 } else {
    1540                     untimedMarkersOmitted = true;
    1541                 }
    1542             }
    1543         }
    1544 
    1545         // (c) use explicitly named track points, again unless suppressed
    1546         if ((Main.pref.getBoolean("marker.audiofromnamedtrackpoints", false)) && data.tracks != null
    1547                 && !data.tracks.isEmpty()) {
    1548             for (GpxTrack track : data.tracks) {
    1549                 for (GpxTrackSegment seg : track.getSegments()) {
    1550                     for (WayPoint w : seg.getWayPoints()) {
    1551                         if (w.attr.containsKey("name") || w.attr.containsKey("desc")) {
    1552                             waypoints.add(w);
    1553                         }
    1554                     }
    1555                 }
    1556             }
    1557         }
    1558 
    1559         // (d) use timestamp of file as location on track
    1560         if ((Main.pref.getBoolean("marker.audiofromwavtimestamps", false)) && data.tracks != null
    1561                 && !data.tracks.isEmpty()) {
    1562             double lastModified = wavFile.lastModified() / 1000.0; // lastModified is in
    1563             // milliseconds
    1564             double duration = AudioUtil.getCalibratedDuration(wavFile);
    1565             double startTime = lastModified - duration;
    1566             startTime = firstStartTime + (startTime - firstStartTime)
    1567                     / Main.pref.getDouble("audio.calibration", "1.0" /* default, ratio */);
    1568             WayPoint w1 = null;
    1569             WayPoint w2 = null;
    1570 
    1571             for (GpxTrack track : data.tracks) {
    1572                 for (GpxTrackSegment seg : track.getSegments()) {
    1573                     for (WayPoint w : seg.getWayPoints()) {
    1574                         if (startTime < w.time) {
    1575                             w2 = w;
    1576                             break;
    1577                         }
    1578                         w1 = w;
    1579                     }
    1580                     if (w2 != null) {
    1581                         break;
    1582                     }
    1583                 }
    1584             }
    1585 
    1586             if (w1 == null || w2 == null) {
    1587                 timedMarkersOmitted = true;
    1588             } else {
    1589                 wayPointFromTimeStamp = new WayPoint(w1.getCoor().interpolate(w2.getCoor(),
    1590                         (startTime - w1.time) / (w2.time - w1.time)));
    1591                 wayPointFromTimeStamp.time = startTime;
    1592                 String name = wavFile.getName();
    1593                 int dot = name.lastIndexOf(".");
    1594                 if (dot > 0) {
    1595                     name = name.substring(0, dot);
    1596                 }
    1597                 wayPointFromTimeStamp.attr.put("name", name);
    1598                 waypoints.add(wayPointFromTimeStamp);
    1599             }
    1600         }
    1601 
    1602         // (e) analyse audio for spoken markers here, in due course
    1603 
    1604         // (f) simply add a single marker at the start of the track
    1605         if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) && data.tracks != null
    1606                 && !data.tracks.isEmpty()) {
    1607             boolean gotOne = false;
    1608             for (GpxTrack track : data.tracks) {
    1609                 for (GpxTrackSegment seg : track.getSegments()) {
    1610                     for (WayPoint w : seg.getWayPoints()) {
    1611                         WayPoint wStart = new WayPoint(w.getCoor());
    1612                         wStart.attr.put("name", "start");
    1613                         wStart.time = w.time;
    1614                         waypoints.add(wStart);
    1615                         gotOne = true;
    1616                         break;
    1617                     }
    1618                     if (gotOne) {
    1619                         break;
    1620                     }
    1621                 }
    1622                 if (gotOne) {
    1623                     break;
    1624                 }
    1625             }
    1626         }
    1627 
    1628         /* we must have got at least one waypoint now */
    1629 
    1630         Collections.sort((ArrayList<WayPoint>) waypoints, new Comparator<WayPoint>() {
    1631             @Override
    1632             public int compare(WayPoint a, WayPoint b) {
    1633                 return a.time <= b.time ? -1 : 1;
    1634             }
    1635         });
    1636 
    1637         firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
    1638         for (WayPoint w : waypoints) {
    1639             if (firstTime < 0.0) {
    1640                 firstTime = w.time;
    1641             }
    1642             double offset = w.time - firstTime;
    1643             AudioMarker am = new AudioMarker(w.getCoor(), w, url, ml, w.time, offset);
    1644             /*
    1645              * timeFromAudio intended for future use to shift markers of this type on
    1646              * synchronization
    1647              */
    1648             if (w == wayPointFromTimeStamp) {
    1649                 am.timeFromAudio = true;
    1650             }
    1651             ml.data.add(am);
    1652         }
    1653 
    1654         if (timedMarkersOmitted && !markers.timedMarkersOmitted) {
    1655             JOptionPane
    1656             .showMessageDialog(
    1657                     Main.parent,
    1658                     tr("Some waypoints with timestamps from before the start of the track or after the end were omitted or moved to the start."));
    1659             markers.timedMarkersOmitted = timedMarkersOmitted;
    1660         }
    1661         if (untimedMarkersOmitted && !markers.untimedMarkersOmitted) {
    1662             JOptionPane
    1663             .showMessageDialog(
    1664                     Main.parent,
    1665                     tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
    1666             markers.untimedMarkersOmitted = untimedMarkersOmitted;
    1667         }
    1668     }
    1669 
    1670     /**
    1671      * Makes a WayPoint at the projection of point P onto the track providing P is less than
    1672      * tolerance away from the track
    1673      *
    1674      * @param P : the point to determine the projection for
    1675      * @param tolerance : must be no further than this from the track
    1676      * @return the closest point on the track to P, which may be the first or last point if off the
    1677      * end of a segment, or may be null if nothing close enough
    1678      */
    1679     public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
    1680         /*
    1681          * assume the coordinates of P are xp,yp, and those of a section of track between two
    1682          * trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
    1683          *
    1684          * The equation of RS is Ax + By + C = 0 where A = ys - yr B = xr - xs C = - Axr - Byr
    1685          *
    1686          * Also, note that the distance RS^2 is A^2 + B^2
    1687          *
    1688          * If RS^2 == 0.0 ignore the degenerate section of track
    1689          *
    1690          * PN^2 = (Axp + Byp + C)^2 / RS^2 that is the distance from P to the line
    1691          *
    1692          * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject the line;
    1693          * otherwise... determine if the projected poijnt lies within the bounds of the line: PR^2 -
    1694          * PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
    1695          *
    1696          * where PR^2 = (xp - xr)^2 + (yp-yr)^2 and PS^2 = (xp - xs)^2 + (yp-ys)^2
    1697          *
    1698          * If so, calculate N as xn = xr + (RN/RS) B yn = y1 + (RN/RS) A
    1699          *
    1700          * where RN = sqrt(PR^2 - PN^2)
    1701          */
    1702 
    1703         double PNminsq = tolerance * tolerance;
    1704         EastNorth bestEN = null;
    1705         double bestTime = 0.0;
    1706         double px = P.east();
    1707         double py = P.north();
    1708         double rx = 0.0, ry = 0.0, sx, sy, x, y;
    1709         if (data.tracks == null)
    1710             return null;
    1711         for (GpxTrack track : data.tracks) {
    1712             for (GpxTrackSegment seg : track.getSegments()) {
    1713                 WayPoint R = null;
    1714                 for (WayPoint S : seg.getWayPoints()) {
    1715                     EastNorth c = S.getEastNorth();
    1716                     if (R == null) {
    1717                         R = S;
    1718                         rx = c.east();
    1719                         ry = c.north();
    1720                         x = px - rx;
    1721                         y = py - ry;
    1722                         double PRsq = x * x + y * y;
    1723                         if (PRsq < PNminsq) {
    1724                             PNminsq = PRsq;
    1725                             bestEN = c;
    1726                             bestTime = R.time;
    1727                         }
    1728                     } else {
    1729                         sx = c.east();
    1730                         sy = c.north();
    1731                         double A = sy - ry;
    1732                         double B = rx - sx;
    1733                         double C = -A * rx - B * ry;
    1734                         double RSsq = A * A + B * B;
    1735                         if (RSsq == 0.0) {
    1736                             continue;
    1737                         }
    1738                         double PNsq = A * px + B * py + C;
    1739                         PNsq = PNsq * PNsq / RSsq;
    1740                         if (PNsq < PNminsq) {
    1741                             x = px - rx;
    1742                             y = py - ry;
    1743                             double PRsq = x * x + y * y;
    1744                             x = px - sx;
    1745                             y = py - sy;
    1746                             double PSsq = x * x + y * y;
    1747                             if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
    1748                                 double RNoverRS = Math.sqrt((PRsq - PNsq) / RSsq);
    1749                                 double nx = rx - RNoverRS * B;
    1750                                 double ny = ry + RNoverRS * A;
    1751                                 bestEN = new EastNorth(nx, ny);
    1752                                 bestTime = R.time + RNoverRS * (S.time - R.time);
    1753                                 PNminsq = PNsq;
    1754                             }
    1755                         }
    1756                         R = S;
    1757                         rx = sx;
    1758                         ry = sy;
    1759                     }
    1760                 }
    1761                 if (R != null) {
    1762                     EastNorth c = R.getEastNorth();
    1763                     /* if there is only one point in the seg, it will do this twice, but no matter */
    1764                     rx = c.east();
    1765                     ry = c.north();
    1766                     x = px - rx;
    1767                     y = py - ry;
    1768                     double PRsq = x * x + y * y;
    1769                     if (PRsq < PNminsq) {
    1770                         PNminsq = PRsq;
    1771                         bestEN = c;
    1772                         bestTime = R.time;
    1773                     }
    1774                 }
    1775             }
    1776         }
    1777         if (bestEN == null)
    1778             return null;
    1779         WayPoint best = new WayPoint(Main.getProjection().eastNorth2latlon(bestEN));
    1780         best.time = bestTime;
    1781         return best;
    1782     }
    1783 
    1784     private class CustomizeDrawing extends AbstractAction implements LayerAction, MultiLayerAction {
    1785         List<Layer> layers;
    1786 
    1787         public CustomizeDrawing(List<Layer> l) {
    1788             this();
    1789             layers = l;
    1790         }
    1791 
    1792         public CustomizeDrawing(Layer l) {
    1793             this();
    1794             layers = new LinkedList<Layer>();
    1795             layers.add(l);
    1796         }
    1797 
    1798         private CustomizeDrawing() {
    1799             super(tr("Customize track drawing"), ImageProvider.get("mapmode/addsegment"));
    1800             putValue("help", ht("/Action/GPXLayerCustomizeLineDrawing"));
    1801         }
    1802 
    1803         @Override
    1804         public boolean supportLayers(List<Layer> layers) {
    1805             for(Layer layer: layers) {
    1806                 if(!(layer instanceof GpxLayer))
    1807                     return false;
    1808             }
    1809             return true;
    1810         }
    1811 
    1812         @Override
    1813         public Component createMenuComponent() {
    1814             return new JMenuItem(this);
    1815         }
    1816 
    1817         @Override
    1818         public Action getMultiLayerAction(List<Layer> layers) {
    1819             return new CustomizeDrawing(layers);
    1820         }
    1821 
    1822         @Override
    1823         public void actionPerformed(ActionEvent e) {
    1824             boolean hasLocal = false, hasNonlocal = false;
    1825             for (Layer layer : layers) {
    1826                 if (layer instanceof GpxLayer) {
    1827                     if (((GpxLayer) layer).isLocalFile) {
    1828                         hasLocal = true;
    1829                     } else {
    1830                         hasNonlocal = true;
    1831                     }
    1832                 }
    1833             }
    1834             GPXSettingsPanel panel=new GPXSettingsPanel(getName(), hasLocal, hasNonlocal);
    1835             JScrollPane scrollpane = new JScrollPane(panel,
    1836                     JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
    1837             scrollpane.setBorder(BorderFactory.createEmptyBorder( 0, 0, 0, 0 ));
    1838             int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
    1839             if (screenHeight < 700) { // to fit on screen 800x600
    1840                 scrollpane.setPreferredSize(new Dimension(panel.getPreferredSize().width, Math.min(panel.getPreferredSize().height,450)));
    1841             }
    1842             int answer = JOptionPane.showConfirmDialog(Main.parent, scrollpane,
    1843                     tr("Customize track drawing"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
    1844             if (answer == JOptionPane.CANCEL_OPTION || answer == JOptionPane.CLOSED_OPTION) return;
    1845             for(Layer layer : layers) {
    1846                 // save preferences for all layers
    1847                 boolean f=false;
    1848                 if (layer instanceof GpxLayer) {
    1849                     f=((GpxLayer)layer).isLocalFile;
    1850                 }
    1851                 panel.savePreferences(layer.getName(),f);
    1852             }
    1853             Main.map.repaint();
    1854         }
    1855     }
    1856 
    1857     private class MarkersFromNamedPoins extends AbstractAction {
    1858 
    1859         public MarkersFromNamedPoins() {
    1860             super(tr("Markers From Named Points"), ImageProvider.get("addmarkers"));
    1861             putValue("help", ht("/Action/MarkersFromNamedPoints"));
    1862         }
    1863 
    1864         @Override
    1865         public void actionPerformed(ActionEvent e) {
    1866             GpxData namedTrackPoints = new GpxData();
    1867             for (GpxTrack track : data.tracks) {
    1868                 for (GpxTrackSegment seg : track.getSegments()) {
    1869                     for (WayPoint point : seg.getWayPoints())
    1870                         if (point.attr.containsKey("name") || point.attr.containsKey("desc")) {
    1871                             namedTrackPoints.waypoints.add(point);
    1872                         }
    1873                 }
    1874             }
    1875 
    1876             MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", getName()),
    1877                     getAssociatedFile(), GpxLayer.this);
    1878             if (ml.data.size() > 0) {
    1879                 Main.main.addLayer(ml);
    1880             }
    1881 
    1882         }
    1883     }
    1884 
    1885     private class ImportAudio extends AbstractAction {
    1886 
    1887         public ImportAudio() {
    1888             super(tr("Import Audio"), ImageProvider.get("importaudio"));
    1889             putValue("help", ht("/Action/ImportAudio"));
    1890         }
    1891 
    1892         private void warnCantImportIntoServerLayer(GpxLayer layer) {
    1893             String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>"
    1894                     + "Because its way points do not include a timestamp we cannot correlate them with audio data.</html>",
    1895                     layer.getName()
    1896                     );
    1897             HelpAwareOptionPane.showOptionDialog(
    1898                     Main.parent,
    1899                     msg,
    1900                     tr("Import not possible"),
    1901                     JOptionPane.WARNING_MESSAGE,
    1902                     ht("/Action/ImportAudio#CantImportIntoGpxLayerFromServer")
    1903                     );
    1904         }
    1905 
    1906         @Override
    1907         public void actionPerformed(ActionEvent e) {
    1908             if (GpxLayer.this.data.fromServer) {
    1909                 warnCantImportIntoServerLayer(GpxLayer.this);
    1910                 return;
    1911             }
    1912             FileFilter filter = new FileFilter() {
    1913                 @Override
    1914                 public boolean accept(File f) {
    1915                     return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
    1916                 }
    1917 
    1918                 @Override
    1919                 public String getDescription() {
    1920                     return tr("Wave Audio files (*.wav)");
    1921                 }
    1922             };
    1923             JFileChooser fc = DiskAccessAction.createAndOpenFileChooser(true, true, null, filter, JFileChooser.FILES_ONLY, "markers.lastaudiodirectory");
    1924             if (fc != null) {
    1925                 File sel[] = fc.getSelectedFiles();
    1926                 // sort files in increasing order of timestamp (this is the end time, but so
    1927                 // long as they don't overlap, that's fine)
    1928                 if (sel.length > 1) {
    1929                     Arrays.sort(sel, new Comparator<File>() {
    1930                         @Override
    1931                         public int compare(File a, File b) {
    1932                             return a.lastModified() <= b.lastModified() ? -1 : 1;
    1933                         }
    1934                     });
    1935                 }
    1936 
    1937                 String names = null;
    1938                 for (int i = 0; i < sel.length; i++) {
    1939                     if (names == null) {
    1940                         names = " (";
    1941                     } else {
    1942                         names += ", ";
    1943                     }
    1944                     names += sel[i].getName();
    1945                 }
    1946                 if (names != null) {
    1947                     names += ")";
    1948                 } else {
    1949                     names = "";
    1950                 }
    1951                 MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", getName()) + names,
    1952                         getAssociatedFile(), GpxLayer.this);
    1953                 double firstStartTime = sel[0].lastModified() / 1000.0 /* ms -> seconds */
    1954                         - AudioUtil.getCalibratedDuration(sel[0]);
    1955 
    1956                 Markers m = new Markers();
    1957                 for (int i = 0; i < sel.length; i++) {
    1958                     importAudio(sel[i], ml, firstStartTime, m);
    1959                 }
    1960                 Main.main.addLayer(ml);
    1961                 Main.map.repaint();
    1962             }
    1963         }
    1964     }
    1965 
    1966     private class ImportImages extends AbstractAction {
    1967 
    1968         public ImportImages() {
    1969             super(tr("Import images"), ImageProvider.get("dialogs/geoimage"));
    1970             putValue("help", ht("/Action/ImportImages"));
    1971         }
    1972 
    1973         private void warnCantImportIntoServerLayer(GpxLayer layer) {
    1974             String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>"
    1975                     + "Because its way points do not include a timestamp we cannot correlate them with images.</html>",
    1976                     layer.getName()
    1977                     );
    1978             HelpAwareOptionPane.showOptionDialog(
    1979                     Main.parent,
    1980                     msg,
    1981                     tr("Import not possible"),
    1982                     JOptionPane.WARNING_MESSAGE,
    1983                     ht("/Action/ImportImages#CantImportIntoGpxLayerFromServer")
    1984                     );
    1985         }
    1986 
    1987         private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
    1988             for (File f : sel) {
    1989                 if (f.isDirectory()) {
    1990                     addRecursiveFiles(files, f.listFiles());
    1991                 } else if (f.getName().toLowerCase().endsWith(".jpg")) {
    1992                     files.add(f);
    1993                 }
    1994             }
    1995         }
    1996 
    1997         @Override
    1998         public void actionPerformed(ActionEvent e) {
    1999 
    2000             if (GpxLayer.this.data.fromServer) {
    2001                 warnCantImportIntoServerLayer(GpxLayer.this);
    2002                 return;
    2003             }
    2004            
    2005             JpgImporter importer = new JpgImporter(GpxLayer.this);
    2006             JFileChooser fc = new JFileChooserManager(true, "geoimage.lastdirectory", Main.pref.get("lastDirectory")).
    2007                     createFileChooser(true, null, importer.filter, JFileChooser.FILES_AND_DIRECTORIES).openFileChooser();
    2008             if (fc != null) {
    2009                 File[] sel = fc.getSelectedFiles();
    2010                 if (sel != null && sel.length > 0) {
    2011                     LinkedList<File> files = new LinkedList<File>();
    2012                     addRecursiveFiles(files, sel);
    2013                     importer.importDataHandleExceptions(files, NullProgressMonitor.INSTANCE);
    2014                 }
    2015             }
    2016         }
    2017     }
    2018 
    2019798    @Override
    2020799    public void projectionChanged(Projection oldValue, Projection newValue) {
  • trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java

    r5557 r5715  
    8484        }
    8585
    86         boolean isFinished() {
     86        public boolean isFinished() {
    8787            return totalCount == processedCount;
    8888        }
  • trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java

    r4284 r5715  
    143143            Point p = Main.map.mapView.getPoint(en);
    144144            EastNorth enPlus25px = Main.map.mapView.getEastNorth(p.x+dropTolerance, p.y);
    145             cw = recent.parentLayer.fromLayer.nearestPointOnTrack(en, enPlus25px.east() - en.east());
     145            cw = recent.parentLayer.fromLayer.data.nearestPointOnTrack(en, enPlus25px.east() - en.east());
    146146        }
    147147
     
    219219            Point p = Main.map.mapView.getPoint(en);
    220220            EastNorth enPlus25px = Main.map.mapView.getEastNorth(p.x+dropTolerance, p.y);
    221             WayPoint cw = recent.parentLayer.fromLayer.nearestPointOnTrack(en, enPlus25px.east() - en.east());
     221            WayPoint cw = recent.parentLayer.fromLayer.data.nearestPointOnTrack(en, enPlus25px.east() - en.east());
    222222            if (cw == null) {
    223223                JOptionPane.showMessageDialog(
Note: See TracChangeset for help on using the changeset viewer.