Ignore:
Timestamp:
2008-12-23T15:07:05+01:00 (17 years ago)
Author:
stoecker
Message:

removed usage of tab stops

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java

    r1165 r1169  
    8080
    8181public class GpxLayer extends Layer {
    82         public GpxData data;
    83         private final GpxLayer me;
    84         protected static final double PHI = Math.toRadians(15);
    85         private boolean computeCacheInSync;
    86         private int computeCacheMaxLineLengthUsed;
    87         private Color computeCacheColorUsed;
    88         private boolean computeCacheColored;
    89 
    90         public GpxLayer(GpxData d) {
    91                 super((String) d.attr.get("name"));
    92                 data = d;
    93                 me = this;
    94                 computeCacheInSync = false;
    95         }
    96 
    97         public GpxLayer(GpxData d, String name) {
    98                 this(d);
    99                 this.name = name;
    100         }
    101 
    102         @Override public Icon getIcon() {
    103                 return ImageProvider.get("layer", "gpx_small");
    104         }
    105 
    106         @Override public Object getInfoComponent() {
    107                 return getToolTipText();
    108         }
    109 
    110         @Override public Component[] getMenuEntries() {
    111                 JMenuItem line = new JMenuItem(tr("Customize line drawing"), ImageProvider.get("mapmode/addsegment"));
    112                 line.addActionListener(new ActionListener() {
    113                         public void actionPerformed(ActionEvent e) {
    114                                 JRadioButton[] r = new JRadioButton[3];
    115                                 r[0] = new JRadioButton(tr("Use global settings."));
    116                                 r[1] = new JRadioButton(tr("Draw lines between points for this layer."));
    117                                 r[2] = new JRadioButton(tr("Do not draw lines between points for this layer."));
    118                                 ButtonGroup group = new ButtonGroup();
    119                                 Box panel = Box.createVerticalBox();
    120                                 for (JRadioButton b : r) {
    121                                         group.add(b);
    122                                         panel.add(b);
    123                                 }
    124                                 String propName = "draw.rawgps.lines.layer "+name;
    125                                 if (Main.pref.hasKey(propName))
    126                                         group.setSelected(r[Main.pref.getBoolean(propName) ? 1:2].getModel(), true);
    127                                 else
    128                                         group.setSelected(r[0].getModel(), true);
    129                                 int answer = JOptionPane.showConfirmDialog(Main.parent, panel, tr("Select line drawing options"), JOptionPane.OK_CANCEL_OPTION);
    130                                 if (answer == JOptionPane.CANCEL_OPTION)
    131                                         return;
    132                                 if (group.getSelection() == r[0].getModel())
    133                                         Main.pref.put(propName, null);
    134                                 else
    135                                         Main.pref.put(propName, group.getSelection() == r[1].getModel());
    136                                 Main.map.repaint();
    137                         }
    138                 });
    139 
    140                 JMenuItem color = new JMenuItem(tr("Customize Color"), ImageProvider.get("colorchooser"));
    141                 color.putClientProperty("help", "Action/LayerCustomizeColor");
    142                 color.addActionListener(new ActionListener() {
    143                         public void actionPerformed(ActionEvent e) {
    144                                 JColorChooser c = new JColorChooser(Main.pref.getColor(marktr("gps point"), "layer "+name, Color.gray));
    145                                 Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
    146                                 int answer = JOptionPane.showOptionDialog(Main.parent, c, tr("Choose a color"), JOptionPane.OK_CANCEL_OPTION,
    147                                 JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
    148                                 switch (answer) {
    149                                 case 0:
    150                                         Main.pref.putColor("layer "+name, c.getColor());
    151                                         break;
    152                                 case 1:
    153                                         return;
    154                                 case 2:
    155                                         Main.pref.putColor("layer "+name, null);
    156                                         break;
    157                                 }
    158                                 Main.map.repaint();
    159                         }
    160                 });
    161 
    162                 JMenuItem markersFromNamedTrackpoints = new JMenuItem(tr("Markers From Named Points"), ImageProvider.get("addmarkers"));
    163                 markersFromNamedTrackpoints.putClientProperty("help", "Action/MarkersFromNamedPoints");
    164                 markersFromNamedTrackpoints.addActionListener(new ActionListener() {
    165                         public void actionPerformed(ActionEvent e) {
    166                                 GpxData namedTrackPoints = new GpxData();
    167                                 for (GpxTrack track : data.tracks)
    168                                         for (Collection<WayPoint> seg : track.trackSegs)
    169                                                 for (WayPoint point : seg)
    170                                                         if (point.attr.containsKey("name") || point.attr.containsKey("desc"))
    171                                                                 namedTrackPoints.waypoints.add(point);
    172 
    173                                 MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", name), associatedFile, me);
    174                                 if (ml.data.size() > 0) {
    175                                         Main.main.addLayer(ml);
    176                                 }
    177                         }
    178                 });
    179 
    180                 JMenuItem importAudio = new JMenuItem(tr("Import Audio"), ImageProvider.get("importaudio"));
    181                 importAudio.putClientProperty("help", "ImportAudio");
    182                 importAudio.addActionListener(new ActionListener() {
    183                         public void actionPerformed(ActionEvent e) {
    184                                 String dir = Main.pref.get("markers.lastaudiodirectory");
    185                                 JFileChooser fc = new JFileChooser(dir);
    186                                 fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
    187                                 fc.setAcceptAllFileFilterUsed(false);
    188                                 fc.setFileFilter(new FileFilter(){
    189                                         @Override public boolean accept(File f) {
    190                                                 return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
    191                                         }
    192                                         @Override public String getDescription() {
    193                                                 return tr("Wave Audio files (*.wav)");
    194                                         }
    195                                 });
    196                                 fc.setMultiSelectionEnabled(true);
    197                                 if(fc.showOpenDialog(Main.parent) == JFileChooser.APPROVE_OPTION) {
    198                                         if (!fc.getCurrentDirectory().getAbsolutePath().equals(dir))
    199                                                 Main.pref.put("markers.lastaudiodirectory", fc.getCurrentDirectory().getAbsolutePath());
    200 
    201                                         // FIXME: properly support multi-selection here.
    202                                         // Calling importAudio several times just creates N maker layers, which
    203                                         // is sub-optimal.
    204                                         File sel[] = fc.getSelectedFiles();
    205                                         if(sel != null)
    206                                                 for (int i = 0; i < sel.length; i++)
    207                                                         importAudio(sel[i]);
    208 
    209                                         Main.map.repaint();
    210                                 }
    211                         }
    212                 });
    213 
    214                 JMenuItem tagimage = new JMenuItem(tr("Import images"), ImageProvider.get("tagimages"));
    215                 tagimage.putClientProperty("help", "Action/ImportImages");
    216                 tagimage.addActionListener(new ActionListener() {
    217                         public void actionPerformed(ActionEvent e) {
    218                                 JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
    219                                 fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
    220                                 fc.setMultiSelectionEnabled(true);
    221                                 fc.setAcceptAllFileFilterUsed(false);
    222                                 fc.setFileFilter(new FileFilter() {
    223                                         @Override public boolean accept(File f) {
    224                                                 return f.isDirectory() || f.getName().toLowerCase().endsWith(".jpg");
    225                                         }
    226                                         @Override public String getDescription() {
    227                                                 return tr("JPEG images (*.jpg)");
    228                                         }
    229                                 });
    230                                 fc.showOpenDialog(Main.parent);
    231                                 File[] sel = fc.getSelectedFiles();
    232                                 if (sel == null || sel.length == 0)
    233                                         return;
    234                                 LinkedList<File> files = new LinkedList<File>();
    235                                 addRecursiveFiles(files, sel);
    236                                 Main.pref.put("tagimages.lastdirectory", fc.getCurrentDirectory().getPath());
    237                                 GeoImageLayer.create(files, GpxLayer.this);
    238                         }
    239 
    240                         private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
    241                                 for (File f : sel) {
    242                                         if (f.isDirectory())
    243                                                 addRecursiveFiles(files, f.listFiles());
    244                                         else if (f.getName().toLowerCase().endsWith(".jpg"))
    245                                                 files.add(f);
    246                                 }
    247                         }
    248                 });
    249 
    250                 if (Main.applet)
    251                         return new Component[] {
    252                                 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
    253                                 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
    254                                 new JSeparator(),
    255                                 color,
    256                                 line,
    257                                 new JMenuItem(new ConvertToDataLayerAction()),
    258                                 new JSeparator(),
    259                                 new JMenuItem(new RenameLayerAction(associatedFile, this)),
    260                                 new JSeparator(),
    261                                 new JMenuItem(new LayerListPopup.InfoAction(this))};
    262                 return new Component[] {
    263                         new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
    264                         new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
    265                         new JSeparator(),
    266                         new JMenuItem(new SaveAction(this)),
    267                         new JMenuItem(new SaveAsAction(this)),
    268                         // new JMenuItem(new UploadTraceAction()),
    269                         color,
    270                         line,
    271                         tagimage,
    272                         importAudio,
    273                         markersFromNamedTrackpoints,
    274                         new JMenuItem(new ConvertToDataLayerAction()),
     82    public GpxData data;
     83    private final GpxLayer me;
     84    protected static final double PHI = Math.toRadians(15);
     85    private boolean computeCacheInSync;
     86    private int computeCacheMaxLineLengthUsed;
     87    private Color computeCacheColorUsed;
     88    private boolean computeCacheColored;
     89
     90    public GpxLayer(GpxData d) {
     91        super((String) d.attr.get("name"));
     92        data = d;
     93        me = this;
     94        computeCacheInSync = false;
     95    }
     96
     97    public GpxLayer(GpxData d, String name) {
     98        this(d);
     99        this.name = name;
     100    }
     101
     102    @Override public Icon getIcon() {
     103        return ImageProvider.get("layer", "gpx_small");
     104    }
     105
     106    @Override public Object getInfoComponent() {
     107        return getToolTipText();
     108    }
     109
     110    @Override public Component[] getMenuEntries() {
     111        JMenuItem line = new JMenuItem(tr("Customize line drawing"), ImageProvider.get("mapmode/addsegment"));
     112        line.addActionListener(new ActionListener() {
     113            public void actionPerformed(ActionEvent e) {
     114                JRadioButton[] r = new JRadioButton[3];
     115                r[0] = new JRadioButton(tr("Use global settings."));
     116                r[1] = new JRadioButton(tr("Draw lines between points for this layer."));
     117                r[2] = new JRadioButton(tr("Do not draw lines between points for this layer."));
     118                ButtonGroup group = new ButtonGroup();
     119                Box panel = Box.createVerticalBox();
     120                for (JRadioButton b : r) {
     121                    group.add(b);
     122                    panel.add(b);
     123                }
     124                String propName = "draw.rawgps.lines.layer "+name;
     125                if (Main.pref.hasKey(propName))
     126                    group.setSelected(r[Main.pref.getBoolean(propName) ? 1:2].getModel(), true);
     127                else
     128                    group.setSelected(r[0].getModel(), true);
     129                int answer = JOptionPane.showConfirmDialog(Main.parent, panel, tr("Select line drawing options"), JOptionPane.OK_CANCEL_OPTION);
     130                if (answer == JOptionPane.CANCEL_OPTION)
     131                    return;
     132                if (group.getSelection() == r[0].getModel())
     133                    Main.pref.put(propName, null);
     134                else
     135                    Main.pref.put(propName, group.getSelection() == r[1].getModel());
     136                Main.map.repaint();
     137            }
     138        });
     139
     140        JMenuItem color = new JMenuItem(tr("Customize Color"), ImageProvider.get("colorchooser"));
     141        color.putClientProperty("help", "Action/LayerCustomizeColor");
     142        color.addActionListener(new ActionListener() {
     143            public void actionPerformed(ActionEvent e) {
     144                JColorChooser c = new JColorChooser(Main.pref.getColor(marktr("gps point"), "layer "+name, Color.gray));
     145                Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
     146                int answer = JOptionPane.showOptionDialog(Main.parent, c, tr("Choose a color"), JOptionPane.OK_CANCEL_OPTION,
     147                JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
     148                switch (answer) {
     149                case 0:
     150                    Main.pref.putColor("layer "+name, c.getColor());
     151                    break;
     152                case 1:
     153                    return;
     154                case 2:
     155                    Main.pref.putColor("layer "+name, null);
     156                    break;
     157                }
     158                Main.map.repaint();
     159            }
     160        });
     161
     162        JMenuItem markersFromNamedTrackpoints = new JMenuItem(tr("Markers From Named Points"), ImageProvider.get("addmarkers"));
     163        markersFromNamedTrackpoints.putClientProperty("help", "Action/MarkersFromNamedPoints");
     164        markersFromNamedTrackpoints.addActionListener(new ActionListener() {
     165            public void actionPerformed(ActionEvent e) {
     166                GpxData namedTrackPoints = new GpxData();
     167                for (GpxTrack track : data.tracks)
     168                    for (Collection<WayPoint> seg : track.trackSegs)
     169                        for (WayPoint point : seg)
     170                            if (point.attr.containsKey("name") || point.attr.containsKey("desc"))
     171                                namedTrackPoints.waypoints.add(point);
     172
     173                MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", name), associatedFile, me);
     174                if (ml.data.size() > 0) {
     175                    Main.main.addLayer(ml);
     176                }
     177            }
     178        });
     179
     180        JMenuItem importAudio = new JMenuItem(tr("Import Audio"), ImageProvider.get("importaudio"));
     181        importAudio.putClientProperty("help", "ImportAudio");
     182        importAudio.addActionListener(new ActionListener() {
     183            public void actionPerformed(ActionEvent e) {
     184                String dir = Main.pref.get("markers.lastaudiodirectory");
     185                JFileChooser fc = new JFileChooser(dir);
     186                fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
     187                fc.setAcceptAllFileFilterUsed(false);
     188                fc.setFileFilter(new FileFilter(){
     189                    @Override public boolean accept(File f) {
     190                        return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
     191                    }
     192                    @Override public String getDescription() {
     193                        return tr("Wave Audio files (*.wav)");
     194                    }
     195                });
     196                fc.setMultiSelectionEnabled(true);
     197                if(fc.showOpenDialog(Main.parent) == JFileChooser.APPROVE_OPTION) {
     198                    if (!fc.getCurrentDirectory().getAbsolutePath().equals(dir))
     199                        Main.pref.put("markers.lastaudiodirectory", fc.getCurrentDirectory().getAbsolutePath());
     200
     201                    // FIXME: properly support multi-selection here.
     202                    // Calling importAudio several times just creates N maker layers, which
     203                    // is sub-optimal.
     204                    File sel[] = fc.getSelectedFiles();
     205                    if(sel != null)
     206                        for (int i = 0; i < sel.length; i++)
     207                            importAudio(sel[i]);
     208
     209                    Main.map.repaint();
     210                }
     211            }
     212        });
     213
     214        JMenuItem tagimage = new JMenuItem(tr("Import images"), ImageProvider.get("tagimages"));
     215        tagimage.putClientProperty("help", "Action/ImportImages");
     216        tagimage.addActionListener(new ActionListener() {
     217            public void actionPerformed(ActionEvent e) {
     218                JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
     219                fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
     220                fc.setMultiSelectionEnabled(true);
     221                fc.setAcceptAllFileFilterUsed(false);
     222                fc.setFileFilter(new FileFilter() {
     223                    @Override public boolean accept(File f) {
     224                        return f.isDirectory() || f.getName().toLowerCase().endsWith(".jpg");
     225                    }
     226                    @Override public String getDescription() {
     227                        return tr("JPEG images (*.jpg)");
     228                    }
     229                });
     230                fc.showOpenDialog(Main.parent);
     231                File[] sel = fc.getSelectedFiles();
     232                if (sel == null || sel.length == 0)
     233                    return;
     234                LinkedList<File> files = new LinkedList<File>();
     235                addRecursiveFiles(files, sel);
     236                Main.pref.put("tagimages.lastdirectory", fc.getCurrentDirectory().getPath());
     237                GeoImageLayer.create(files, GpxLayer.this);
     238            }
     239
     240            private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
     241                for (File f : sel) {
     242                    if (f.isDirectory())
     243                        addRecursiveFiles(files, f.listFiles());
     244                    else if (f.getName().toLowerCase().endsWith(".jpg"))
     245                        files.add(f);
     246                }
     247            }
     248        });
     249
     250        if (Main.applet)
     251            return new Component[] {
     252                new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
     253                new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
     254                new JSeparator(),
     255                color,
     256                line,
     257                new JMenuItem(new ConvertToDataLayerAction()),
     258                new JSeparator(),
     259                new JMenuItem(new RenameLayerAction(associatedFile, this)),
     260                new JSeparator(),
     261                new JMenuItem(new LayerListPopup.InfoAction(this))};
     262        return new Component[] {
     263            new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
     264            new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
     265            new JSeparator(),
     266            new JMenuItem(new SaveAction(this)),
     267            new JMenuItem(new SaveAsAction(this)),
     268            // new JMenuItem(new UploadTraceAction()),
     269            color,
     270            line,
     271            tagimage,
     272            importAudio,
     273            markersFromNamedTrackpoints,
     274            new JMenuItem(new ConvertToDataLayerAction()),
    275275            new JMenuItem(new DownloadAlongTrackAction()),
    276                         new JSeparator(),
    277                         new JMenuItem(new RenameLayerAction(associatedFile, this)),
    278                         new JSeparator(),
    279                         new JMenuItem(new LayerListPopup.InfoAction(this))};
    280         }
    281 
    282         @Override public String getToolTipText() {
    283                 StringBuilder info = new StringBuilder().append("<html>");
    284 
    285                 info.append(trn("{0} track, ", "{0} tracks, ",
    286                 data.tracks.size(), data.tracks.size())).append(trn("{0} route, ", "{0} routes, ",
    287                 data.routes.size(), data.routes.size())).append(trn("{0} waypoint", "{0} waypoints",
    288                 data.waypoints.size(), data.waypoints.size())).append("<br>");
    289 
    290                 if (data.attr.containsKey("name"))
    291                         info.append(tr("Name: {0}", data.attr.get("name"))).append("<br>");
    292 
    293                 if (data.attr.containsKey("desc"))
    294                         info.append(tr("Description: {0}", data.attr.get("desc"))).append("<br>");
    295 
    296                 if(data.tracks.size() > 0){
    297                         boolean first = true;
    298                         WayPoint earliest = null, latest = null;
    299 
    300                         for(GpxTrack trk: data.tracks){
    301                                 for(Collection<WayPoint> seg:trk.trackSegs){
    302                                         for(WayPoint pnt:seg){
    303                                                 if(first){
    304                                                         latest = earliest = pnt;
    305                                                         first = false;
    306                                                 }else{
    307                                                         if(pnt.compareTo(earliest) < 0){
    308                                                                 earliest = pnt;
    309                                                         }else{
    310                                                                 latest = pnt;
    311                                                         }
    312                                                 }
    313                                         }
    314                                 }
    315                         }
    316                         if (earliest != null && latest != null) {
    317                                 DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
    318                                 info.append(tr("Timespan: ") + df.format(new Date((long)(earliest.time * 1000))) + " - "
    319                                 + df.format(new Date((long)(latest.time * 1000))));
    320                                 int diff = (int)(latest.time - earliest.time);
    321                                 info.append(" (" + (diff / 3600) + ":" + ((diff % 3600)/60) + ")");
    322                                 info.append("<br>");
    323                         }
    324                 }
    325                 info.append(tr("Length: ") + new DecimalFormat("#0.00").format(data.length() / 1000) + "km");
    326                 info.append("<br>");
    327 
    328                 return info.append("</html>").toString();
    329         }
    330 
    331         @Override public boolean isMergable(Layer other) {
    332                 return other instanceof GpxLayer;
    333         }
    334 
    335         @Override public void mergeFrom(Layer from) {
    336                 data.mergeFrom(((GpxLayer)from).data);
    337                 computeCacheInSync = false;
    338         }
    339 
    340         private static Color[] colors = new Color[256];
    341         static {
    342                 for (int i = 0; i < colors.length; i++) {
    343                         colors[i] = Color.getHSBColor(i/300.0f, 1, 1);
    344                 }
    345         }
    346 
    347         // lookup array to draw arrows without doing any math
    348         private static int ll0 = 9;
    349         private static int sl4 = 5;
    350         private static int sl9 = 3;
    351         private static int[][] dir = {
    352                 {+sl4,+ll0,+ll0,+sl4},
    353                 {-sl9,+ll0,+sl9,+ll0},
    354                 {-ll0,+sl4,-sl4,+ll0},
    355                 {-ll0,-sl9,-ll0,+sl9},
    356                 {-sl4,-ll0,-ll0,-sl4},
    357                 {+sl9,-ll0,-sl9,-ll0},
    358                 {+ll0,-sl4,+sl4,-ll0},
    359                 {+ll0,+sl9,+ll0,-sl9},
    360                 {+sl4,+ll0,+ll0,+sl4},
    361                 {-sl9,+ll0,+sl9,+ll0},
    362                 {-ll0,+sl4,-sl4,+ll0},
    363                 {-ll0,-sl9,-ll0,+sl9}
    364         };
    365 
    366         @Override public void paint(Graphics g, MapView mv) {
    367 
    368                 /****************************************************************
    369                 ********** STEP 1 - GET CONFIG VALUES **************************
    370                 ****************************************************************/
    371                 // Long startTime = System.currentTimeMillis();
    372                 Color neutralColor = Main.pref.getColor(marktr("gps point"), "layer "+name, Color.GRAY);
    373                 boolean forceLines = Main.pref.getBoolean("draw.rawgps.lines.force");                     // also draw lines between points belonging to different segments
    374                 boolean direction = Main.pref.getBoolean("draw.rawgps.direction");                        // draw direction arrows on the lines
    375                 int maxLineLength = -1;
    376                 try {
    377                         maxLineLength = Integer.parseInt(Main.pref.get("draw.rawgps.max-line-length", "-1"));   // don't draw lines if longer than x meters
    378                 } catch (java.lang.NumberFormatException e) {
    379                         Main.pref.put("draw.rawgps.max-line-length", "-1");
    380                 }
    381                 boolean lines = Main.pref.getBoolean("draw.rawgps.lines");                                // draw line between points, global setting
    382                 String linesKey = "draw.rawgps.lines.layer "+name;
    383                 if (Main.pref.hasKey(linesKey))
    384                         lines = Main.pref.getBoolean(linesKey);                                                 // draw lines, per-layer setting
    385                 boolean large = Main.pref.getBoolean("draw.rawgps.large");                                // paint large dots for points
    386                 boolean colored = Main.pref.getBoolean("draw.rawgps.colors");                             // color the lines
    387                 boolean alternatedirection = Main.pref.getBoolean("draw.rawgps.alternatedirection");      // paint direction arrow with alternate math. may be faster
    388                 int delta = 0;
    389                 try {
    390                         delta = Integer.parseInt(Main.pref.get("draw.rawgps.min-arrow-distance", "0"));         // don't draw arrows nearer to each other than this
    391                 } catch (java.lang.NumberFormatException e) {
    392                         Main.pref.put("draw.rawgps.min-arrow-distance", "0");
    393                 }
    394 
    395                 /****************************************************************
    396                 ********** STEP 2a - CHECK CACHE VALIDITY **********************
    397                 ****************************************************************/
    398                 if (computeCacheInSync && ((computeCacheMaxLineLengthUsed != maxLineLength) ||
    399                                                                    (!neutralColor.equals(computeCacheColorUsed)) ||
    400                                                                    (computeCacheColored != colored))) {
     276            new JSeparator(),
     277            new JMenuItem(new RenameLayerAction(associatedFile, this)),
     278            new JSeparator(),
     279            new JMenuItem(new LayerListPopup.InfoAction(this))};
     280    }
     281
     282    @Override public String getToolTipText() {
     283        StringBuilder info = new StringBuilder().append("<html>");
     284
     285        info.append(trn("{0} track, ", "{0} tracks, ",
     286        data.tracks.size(), data.tracks.size())).append(trn("{0} route, ", "{0} routes, ",
     287        data.routes.size(), data.routes.size())).append(trn("{0} waypoint", "{0} waypoints",
     288        data.waypoints.size(), data.waypoints.size())).append("<br>");
     289
     290        if (data.attr.containsKey("name"))
     291            info.append(tr("Name: {0}", data.attr.get("name"))).append("<br>");
     292
     293        if (data.attr.containsKey("desc"))
     294            info.append(tr("Description: {0}", data.attr.get("desc"))).append("<br>");
     295
     296        if(data.tracks.size() > 0){
     297            boolean first = true;
     298            WayPoint earliest = null, latest = null;
     299
     300            for(GpxTrack trk: data.tracks){
     301                for(Collection<WayPoint> seg:trk.trackSegs){
     302                    for(WayPoint pnt:seg){
     303                        if(first){
     304                            latest = earliest = pnt;
     305                            first = false;
     306                        }else{
     307                            if(pnt.compareTo(earliest) < 0){
     308                                earliest = pnt;
     309                            }else{
     310                                latest = pnt;
     311                            }
     312                        }
     313                    }
     314                }
     315            }
     316            if (earliest != null && latest != null) {
     317                DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
     318                info.append(tr("Timespan: ") + df.format(new Date((long)(earliest.time * 1000))) + " - "
     319                + df.format(new Date((long)(latest.time * 1000))));
     320                int diff = (int)(latest.time - earliest.time);
     321                info.append(" (" + (diff / 3600) + ":" + ((diff % 3600)/60) + ")");
     322                info.append("<br>");
     323            }
     324        }
     325        info.append(tr("Length: ") + new DecimalFormat("#0.00").format(data.length() / 1000) + "km");
     326        info.append("<br>");
     327
     328        return info.append("</html>").toString();
     329    }
     330
     331    @Override public boolean isMergable(Layer other) {
     332        return other instanceof GpxLayer;
     333    }
     334
     335    @Override public void mergeFrom(Layer from) {
     336        data.mergeFrom(((GpxLayer)from).data);
     337        computeCacheInSync = false;
     338    }
     339
     340    private static Color[] colors = new Color[256];
     341    static {
     342        for (int i = 0; i < colors.length; i++) {
     343            colors[i] = Color.getHSBColor(i/300.0f, 1, 1);
     344        }
     345    }
     346
     347    // lookup array to draw arrows without doing any math
     348    private static int ll0 = 9;
     349    private static int sl4 = 5;
     350    private static int sl9 = 3;
     351    private static int[][] dir = {
     352        {+sl4,+ll0,+ll0,+sl4},
     353        {-sl9,+ll0,+sl9,+ll0},
     354        {-ll0,+sl4,-sl4,+ll0},
     355        {-ll0,-sl9,-ll0,+sl9},
     356        {-sl4,-ll0,-ll0,-sl4},
     357        {+sl9,-ll0,-sl9,-ll0},
     358        {+ll0,-sl4,+sl4,-ll0},
     359        {+ll0,+sl9,+ll0,-sl9},
     360        {+sl4,+ll0,+ll0,+sl4},
     361        {-sl9,+ll0,+sl9,+ll0},
     362        {-ll0,+sl4,-sl4,+ll0},
     363        {-ll0,-sl9,-ll0,+sl9}
     364    };
     365
     366    @Override public void paint(Graphics g, MapView mv) {
     367
     368        /****************************************************************
     369        ********** STEP 1 - GET CONFIG VALUES **************************
     370        ****************************************************************/
     371        // Long startTime = System.currentTimeMillis();
     372        Color neutralColor = Main.pref.getColor(marktr("gps point"), "layer "+name, Color.GRAY);
     373        boolean forceLines = Main.pref.getBoolean("draw.rawgps.lines.force");                     // also draw lines between points belonging to different segments
     374        boolean direction = Main.pref.getBoolean("draw.rawgps.direction");                        // draw direction arrows on the lines
     375        int maxLineLength = -1;
     376        try {
     377            maxLineLength = Integer.parseInt(Main.pref.get("draw.rawgps.max-line-length", "-1"));   // don't draw lines if longer than x meters
     378        } catch (java.lang.NumberFormatException e) {
     379            Main.pref.put("draw.rawgps.max-line-length", "-1");
     380        }
     381        boolean lines = Main.pref.getBoolean("draw.rawgps.lines");                                // draw line between points, global setting
     382        String linesKey = "draw.rawgps.lines.layer "+name;
     383        if (Main.pref.hasKey(linesKey))
     384            lines = Main.pref.getBoolean(linesKey);                                                 // draw lines, per-layer setting
     385        boolean large = Main.pref.getBoolean("draw.rawgps.large");                                // paint large dots for points
     386        boolean colored = Main.pref.getBoolean("draw.rawgps.colors");                             // color the lines
     387        boolean alternatedirection = Main.pref.getBoolean("draw.rawgps.alternatedirection");      // paint direction arrow with alternate math. may be faster
     388        int delta = 0;
     389        try {
     390            delta = Integer.parseInt(Main.pref.get("draw.rawgps.min-arrow-distance", "0"));         // don't draw arrows nearer to each other than this
     391        } catch (java.lang.NumberFormatException e) {
     392            Main.pref.put("draw.rawgps.min-arrow-distance", "0");
     393        }
     394
     395        /****************************************************************
     396        ********** STEP 2a - CHECK CACHE VALIDITY **********************
     397        ****************************************************************/
     398        if (computeCacheInSync && ((computeCacheMaxLineLengthUsed != maxLineLength) ||
     399                                   (!neutralColor.equals(computeCacheColorUsed)) ||
     400                                   (computeCacheColored != colored))) {
    401401//          System.out.println("(re-)computing gpx line styles, reason: CCIS=" + computeCacheInSync + " CCMLLU=" + (computeCacheMaxLineLengthUsed != maxLineLength) + " CCCU=" +  (!neutralColor.equals(computeCacheColorUsed)) + " CCC=" + (computeCacheColored != colored));
    402                         computeCacheMaxLineLengthUsed = maxLineLength;
    403                         computeCacheInSync = false;
    404                         computeCacheColorUsed = neutralColor;
    405                         computeCacheColored = colored;
    406                 }
    407 
    408                 /****************************************************************
    409                 ********** STEP 2b - RE-COMPUTE CACHE DATA *********************
    410                 ****************************************************************/
    411                 if (!computeCacheInSync) { // don't compute if the cache is good
    412                         WayPoint oldWp = null;
    413                         for (GpxTrack trk : data.tracks) {
    414                                 if (!forceLines) { // don't draw lines between segments, unless forced to
    415                                         oldWp = null;
    416                                 }
    417                                 for (Collection<WayPoint> segment : trk.trackSegs) {
    418                                         for (WayPoint trkPnt : segment) {
    419                                                 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon())) {
    420                                                         continue;
    421                                                 }
    422                                                 if (oldWp != null) {
    423                                                         double dist = trkPnt.latlon.greatCircleDistance(oldWp.latlon);
    424                                                         double dtime = trkPnt.time - oldWp.time;
    425                                                         double vel = dist/dtime;
    426 
    427                                                         if (!colored) {
    428                                                                 trkPnt.speedLineColor = neutralColor;
    429                                                         } else if (dtime <= 0 || vel < 0 || vel > 36) { // attn: bad case first
    430                                                                 trkPnt.speedLineColor = colors[255];
    431                                                         } else {
    432                                                                 trkPnt.speedLineColor = colors[(int) (7*vel)];
    433                                                         }
    434                                                         if (maxLineLength == -1 || dist <= maxLineLength) {
    435                                                                 trkPnt.drawLine = true;
    436                                                                 trkPnt.dir = (int)(Math.atan2(-trkPnt.eastNorth.north()+oldWp.eastNorth.north(), trkPnt.eastNorth.east()-oldWp.eastNorth.east()) / Math.PI * 4 + 3.5); // crude but works
    437                                                         } else {
    438                                                                 trkPnt.drawLine = false;
    439                                                         }
    440                                                 } else { // make sure we reset outdated data
    441                                                         trkPnt.speedLineColor = colors[255];
    442                                                         trkPnt.drawLine = false;
    443                                                 }
    444                                                 oldWp = trkPnt;
    445                                         }
    446                                 }
    447                         }
    448                         computeCacheInSync = true;
    449                 }
    450 
    451                 /****************************************************************
    452                 ********** STEP 3a - DRAW LINES ********************************
    453                 ****************************************************************/
    454                 if (lines) {
    455                 Point old = null;
    456                 for (GpxTrack trk : data.tracks) {
    457                         for (Collection<WayPoint> segment : trk.trackSegs) {
    458                                 for (WayPoint trkPnt : segment) {
    459                                         if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
    460                                                 continue;
    461                                         Point screen = mv.getPoint(trkPnt.eastNorth);
    462                                                 if (trkPnt.drawLine) {
    463                                                         // skip points that are on the same screenposition
    464                                                         if (old != null && ((old.x != screen.x) || (old.y != screen.y))) {
    465                                                                 g.setColor(trkPnt.speedLineColor);
    466                                                                 g.drawLine(old.x, old.y, screen.x, screen.y);
    467                                                         }
    468                                                 }
    469                                                 old = screen;
    470                                         } // end for trkpnt
    471                                 } // end for segment
    472                         } // end for trk
    473                 } // end if lines
    474 
    475                 /****************************************************************
    476                 ********** STEP 3b - DRAW NICE ARROWS **************************
    477                 ****************************************************************/
    478                 if (lines && direction && !alternatedirection) {
    479                         Point old = null;
    480                         Point oldA = null; // last arrow painted
    481                         for (GpxTrack trk : data.tracks) {
    482                                 for (Collection<WayPoint> segment : trk.trackSegs) {
    483                                         for (WayPoint trkPnt : segment) {
    484                                                 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
    485                                                         continue;
    486                                                 if (trkPnt.drawLine) {
    487                                                         Point screen = mv.getPoint(trkPnt.eastNorth);
    488                                                         // skip points that are on the same screenposition
    489                                                         if (old != null && (oldA == null || screen.x < oldA.x-delta || screen.x > oldA.x+delta || screen.y < oldA.y-delta || screen.y > oldA.y+delta)) {
    490                                                                 g.setColor(trkPnt.speedLineColor);
    491                                                                 double t = Math.atan2(screen.y-old.y, screen.x-old.x) + Math.PI;
    492                                                                 g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t-PHI)), (int)(screen.y
    493                                                                 + 10*Math.sin(t-PHI)));
    494                                                                 g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t+PHI)), (int)(screen.y
    495                                                                 + 10*Math.sin(t+PHI)));
    496                                                                 oldA = screen;
    497                                                         }
    498                                                         old = screen;
    499                                                 }
    500                                         } // end for trkpnt
    501                                 } // end for segment
    502                         } // end for trk
    503                 } // end if lines
    504 
    505                 /****************************************************************
    506                 ********** STEP 3c - DRAW FAST ARROWS **************************
    507                 ****************************************************************/
    508                 if (lines && direction && alternatedirection) {
    509                         Point old = null;
    510                         Point oldA = null; // last arrow painted
    511                         for (GpxTrack trk : data.tracks) {
    512                                 for (Collection<WayPoint> segment : trk.trackSegs) {
    513                                         for (WayPoint trkPnt : segment) {
    514                                                 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
    515                                                         continue;
    516                                                 if (trkPnt.drawLine) {
    517                                                         Point screen = mv.getPoint(trkPnt.eastNorth);
    518                                                         // skip points that are on the same screenposition
    519                                                         if (old != null && (oldA == null || screen.x < oldA.x-delta || screen.x > oldA.x+delta || screen.y < oldA.y-delta || screen.y > oldA.y+delta)) {
    520                                                                 g.setColor(trkPnt.speedLineColor);
    521                                                                 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][0], screen.y + dir[trkPnt.dir][1]);
    522                                                                 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][2], screen.y + dir[trkPnt.dir][3]);
    523                                                                 oldA = screen;
    524                                                         }
    525                                                         old = screen;
    526                                                 }
    527                                         } // end for trkpnt
    528                                 } // end for segment
    529                         } // end for trk
    530                 } // end if lines
    531 
    532                 /****************************************************************
    533                 ********** STEP 3d - DRAW LARGE POINTS *************************
    534                 ****************************************************************/
    535                 if (large) {
    536                         g.setColor(neutralColor);
    537                         for (GpxTrack trk : data.tracks) {
    538                                 for (Collection<WayPoint> segment : trk.trackSegs) {
    539                                         for (WayPoint trkPnt : segment) {
    540                                                 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
    541                                                         continue;
    542                                                 Point screen = mv.getPoint(trkPnt.eastNorth);
    543                                                         g.fillRect(screen.x-1, screen.y-1, 3, 3);
    544                                         } // end for trkpnt
    545                                 } // end for segment
    546                         } // end for trk
    547                 } // end if large
    548 
    549                 /****************************************************************
    550                 ********** STEP 3e - DRAW SMALL POINTS FOR LINES ***************
    551                 ****************************************************************/
    552                 if (!large && lines){
    553                         g.setColor(neutralColor);
    554                         for (GpxTrack trk : data.tracks) {
    555                                 for (Collection<WayPoint> segment : trk.trackSegs) {
    556                                         for (WayPoint trkPnt : segment) {
    557                                                 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
    558                                                         continue;
    559                                                 if (!trkPnt.drawLine) {
    560                                                         Point screen = mv.getPoint(trkPnt.eastNorth);
    561                                                 g.drawRect(screen.x, screen.y, 0, 0);
    562                                         }
    563                                         } // end for trkpnt
    564                                 } // end for segment
    565                         } // end for trk
    566                 } // end if large
    567 
    568                 /****************************************************************
    569                 ********** STEP 3f - DRAW SMALL POINTS INSTEAD OF LINES ********
    570                 ****************************************************************/
    571                 if (!large && !lines){
    572                         g.setColor(neutralColor);
    573                         for (GpxTrack trk : data.tracks) {
    574                                 for (Collection<WayPoint> segment : trk.trackSegs) {
    575                                         for (WayPoint trkPnt : segment) {
    576                                                 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
    577                                                         continue;
    578                                                 Point screen = mv.getPoint(trkPnt.eastNorth);
    579                                                 g.drawRect(screen.x, screen.y, 0, 0);
    580                                         } // end for trkpnt
    581                                 } // end for segment
    582                         } // end for trk
    583                 } // end if large
    584 
    585                 //Long duration = System.currentTimeMillis() - startTime;
    586                 //System.out.println(duration);
    587         } // end paint
    588 
    589         @Override public void visitBoundingBox(BoundingXYVisitor v) {
    590                 for (WayPoint p : data.waypoints)
    591                         v.visit(p.eastNorth);
    592 
    593                 for (GpxRoute rte : data.routes) {
    594                         Collection<WayPoint> r = rte.routePoints;
    595                         for (WayPoint p : r) {
    596                                 v.visit(p.eastNorth);
    597                         }
    598                 }
    599 
    600                 for (GpxTrack trk : data.tracks) {
    601                         for (Collection<WayPoint> seg : trk.trackSegs) {
    602                                 for (WayPoint p : seg) {
    603                                         v.visit(p.eastNorth);
    604                                 }
    605                         }
    606                 }
    607         }
    608 
    609         public class UploadTraceAction extends AbstractAction {
    610                 public UploadTraceAction() {
    611                         super(tr("Upload this trace..."), ImageProvider.get("uploadtrace"));
    612                 }
    613                 public void actionPerformed(ActionEvent e) {
    614                         JPanel msg = new JPanel(new GridBagLayout());
    615                         msg.add(new JLabel(tr("<html>This functionality has been added only recently. Please<br>"+
    616                         "use with care and check if it works as expected.</html>")), GBC.eop());
    617                         ButtonGroup bg = new ButtonGroup();
    618                         JRadioButton c1 = null;
    619                         JRadioButton c2 = null;
    620 
    621                         //TODO
    622                         //check whether data comes from server
    623                         //check whether data changed sind last save/open
    624 
    625                         c1 = new JRadioButton(tr("Upload track filtered by JOSM"), true);
    626                         c2 = new JRadioButton(tr("Upload raw file: "), false);
    627                         c2.setEnabled(false);
    628                         c1.setEnabled(false);
    629                         bg.add(c1);
    630                         bg.add(c2);
    631 
    632                         msg.add(c1, GBC.eol());
    633                         msg.add(c2, GBC.eop());
    634 
    635 
    636                         JLabel description = new JLabel((String) data.attr.get("desc"));
    637                         JTextField tags = new JTextField();
    638                         tags.setText((String) data.attr.get("keywords"));
    639                         msg.add(new JLabel(tr("Description:")), GBC.std());
    640                         msg.add(description, GBC.eol().fill(GBC.HORIZONTAL));
    641                         msg.add(new JLabel(tr("Tags (keywords in GPX):")), GBC.std());
    642                         msg.add(tags, GBC.eol().fill(GBC.HORIZONTAL));
    643                         JCheckBox c3 = new JCheckBox("public");
    644                         msg.add(c3, GBC.eop());
    645                         msg.add(new JLabel("Please ensure that you don't upload your traces twice."), GBC.eop());
    646 
    647                         int answer = JOptionPane.showConfirmDialog(Main.parent, msg, tr("GPX-Upload"), JOptionPane.OK_CANCEL_OPTION);
    648                         if (answer == JOptionPane.OK_OPTION)
    649                         {
    650                                 try {
    651                                         String version = Main.pref.get("osm-server.version", "0.5");
    652                                         URL url = new URL(Main.pref.get("osm-server.url") + "/" + version + "/gpx/create");
    653 
    654                                         // create a boundary string
    655                                         String boundary = MultiPartFormOutputStream.createBoundary();
    656                                         URLConnection urlConn = MultiPartFormOutputStream.createConnection(url);
    657                                         urlConn.setRequestProperty("Accept", "*/*");
    658                                         urlConn.setRequestProperty("Content-Type", MultiPartFormOutputStream.getContentType(boundary));
    659                                         // set some other request headers...
    660                                         urlConn.setRequestProperty("Connection", "Keep-Alive");
    661                                         urlConn.setRequestProperty("Cache-Control", "no-cache");
    662                                         // no need to connect cuz getOutputStream() does it
    663                                         MultiPartFormOutputStream out = new MultiPartFormOutputStream(urlConn.getOutputStream(), boundary);
    664                                         out.writeField("description", description.getText());
    665                                         out.writeField("tags", tags.getText());
    666                                         out.writeField("public", (c3.getSelectedObjects() != null) ? "1" : "0");
    667                                         // upload a file
    668                                         // out.writeFile("gpx_file", "text/xml", associatedFile);
    669                                         // can also write bytes directly
    670                                         // out.writeFile("myFile", "text/plain", "C:\\test.txt",
    671                                         // "This is some file text.".getBytes("ASCII"));
    672                                         File tmp = File.createTempFile("josm", "tmp.gpx");
    673                                         FileOutputStream outs = new FileOutputStream(tmp);
    674                                         new GpxWriter(outs).write(data);
    675                                         outs.close();
    676                                         FileInputStream ins = new FileInputStream(tmp);
    677                                         new GpxWriter(System.out).write(data);
    678                                         out.writeFile("gpx_file", "text/xml", data.storageFile.getName(), ins);
    679                                         out.close();
    680                                         tmp.delete();
    681                                         // read response from server
    682                                         BufferedReader in = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
    683                                         String line = "";
    684                                         while((line = in.readLine()) != null) {
    685                                                 System.out.println(line);
    686                                         }
    687                                         in.close();
    688 
    689                                         //TODO check response
    690                                         /*                  int retCode = urlConn.getResponseCode();
    691                                         System.out.println("got return: " + retCode);
    692                                         String retMsg = urlConn.getResponseMessage();
    693                                         urlConn.disconnect();
    694                                         if (retCode != 200) {
    695                                                 // Look for a detailed error message from the server
    696                                                 if (urlConn.getHeaderField("Error") != null)
    697                                                         retMsg += "\n" + urlConn.getHeaderField("Error");
    698 
    699                                                 // Report our error
    700                                                 ByteArrayOutputStream o = new ByteArrayOutputStream();
    701                                                 System.out.println(new String(o.toByteArray(), "UTF-8").toString());
    702                                                 throw new RuntimeException(retCode+" "+retMsg);
    703                                         }
    704                                         */
    705                                 } catch (UnknownHostException ex) {
    706                                         throw new RuntimeException(tr("Unknown host")+": "+ex.getMessage(), ex);
    707                                 } catch (Exception ex) {
    708                                         //if (cancel)
    709                                         //  return; // assume cancel
    710                                         if (ex instanceof RuntimeException)
    711                                                 throw (RuntimeException)ex;
    712                                         throw new RuntimeException(ex.getMessage(), ex);
    713                                 }
    714                         }
    715                 }
    716         }
    717 
    718         public class ConvertToDataLayerAction extends AbstractAction {
    719                 public ConvertToDataLayerAction() {
    720                         super(tr("Convert to data layer"), ImageProvider.get("converttoosm"));
    721                 }
    722                 public void actionPerformed(ActionEvent e) {
    723                         JPanel msg = new JPanel(new GridBagLayout());
    724                         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:")), GBC.eol());
    725                         msg.add(new UrlLabel(tr("http://www.openstreetmap.org/traces")), GBC.eop());
    726                         if (!DontShowAgainInfo.show("convert_to_data", msg))
    727                                 return;
    728                         DataSet ds = new DataSet();
    729                         for (GpxTrack trk : data.tracks) {
    730                                 for (Collection<WayPoint> segment : trk.trackSegs) {
    731                                         Way w = new Way();
    732                                         for (WayPoint p : segment) {
    733                                                 Node n = new Node(p.latlon);
    734                                                 String timestr = p.getString("time");
    735                                                 if(timestr != null)
    736                                                 {
    737                                                         timestr = timestr.replace("Z","+00:00");
    738                                                         n.timestamp = timestr;
    739                                                 }
    740                                                 ds.nodes.add(n);
    741                                                 w.nodes.add(n);
    742                                         }
    743                                         ds.ways.add(w);
    744                                 }
    745                         }
    746                         Main.main.addLayer(new OsmDataLayer(ds, tr("Converted from: {0}", GpxLayer.this.name), null));
    747                         Main.main.removeLayer(GpxLayer.this);
    748                 }
    749         }
     402            computeCacheMaxLineLengthUsed = maxLineLength;
     403            computeCacheInSync = false;
     404            computeCacheColorUsed = neutralColor;
     405            computeCacheColored = colored;
     406        }
     407
     408        /****************************************************************
     409        ********** STEP 2b - RE-COMPUTE CACHE DATA *********************
     410        ****************************************************************/
     411        if (!computeCacheInSync) { // don't compute if the cache is good
     412            WayPoint oldWp = null;
     413            for (GpxTrack trk : data.tracks) {
     414                if (!forceLines) { // don't draw lines between segments, unless forced to
     415                    oldWp = null;
     416                }
     417                for (Collection<WayPoint> segment : trk.trackSegs) {
     418                    for (WayPoint trkPnt : segment) {
     419                        if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon())) {
     420                            continue;
     421                        }
     422                        if (oldWp != null) {
     423                            double dist = trkPnt.latlon.greatCircleDistance(oldWp.latlon);
     424                            double dtime = trkPnt.time - oldWp.time;
     425                            double vel = dist/dtime;
     426
     427                            if (!colored) {
     428                                trkPnt.speedLineColor = neutralColor;
     429                            } else if (dtime <= 0 || vel < 0 || vel > 36) { // attn: bad case first
     430                                trkPnt.speedLineColor = colors[255];
     431                            } else {
     432                                trkPnt.speedLineColor = colors[(int) (7*vel)];
     433                            }
     434                            if (maxLineLength == -1 || dist <= maxLineLength) {
     435                                trkPnt.drawLine = true;
     436                                trkPnt.dir = (int)(Math.atan2(-trkPnt.eastNorth.north()+oldWp.eastNorth.north(), trkPnt.eastNorth.east()-oldWp.eastNorth.east()) / Math.PI * 4 + 3.5); // crude but works
     437                            } else {
     438                                trkPnt.drawLine = false;
     439                            }
     440                        } else { // make sure we reset outdated data
     441                            trkPnt.speedLineColor = colors[255];
     442                            trkPnt.drawLine = false;
     443                        }
     444                        oldWp = trkPnt;
     445                    }
     446                }
     447            }
     448            computeCacheInSync = true;
     449        }
     450
     451        /****************************************************************
     452        ********** STEP 3a - DRAW LINES ********************************
     453        ****************************************************************/
     454        if (lines) {
     455        Point old = null;
     456        for (GpxTrack trk : data.tracks) {
     457            for (Collection<WayPoint> segment : trk.trackSegs) {
     458                for (WayPoint trkPnt : segment) {
     459                    if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
     460                        continue;
     461                    Point screen = mv.getPoint(trkPnt.eastNorth);
     462                        if (trkPnt.drawLine) {
     463                            // skip points that are on the same screenposition
     464                            if (old != null && ((old.x != screen.x) || (old.y != screen.y))) {
     465                                g.setColor(trkPnt.speedLineColor);
     466                                g.drawLine(old.x, old.y, screen.x, screen.y);
     467                            }
     468                        }
     469                        old = screen;
     470                    } // end for trkpnt
     471                } // end for segment
     472            } // end for trk
     473        } // end if lines
     474
     475        /****************************************************************
     476        ********** STEP 3b - DRAW NICE ARROWS **************************
     477        ****************************************************************/
     478        if (lines && direction && !alternatedirection) {
     479            Point old = null;
     480            Point oldA = null; // last arrow painted
     481            for (GpxTrack trk : data.tracks) {
     482                for (Collection<WayPoint> segment : trk.trackSegs) {
     483                    for (WayPoint trkPnt : segment) {
     484                        if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
     485                            continue;
     486                        if (trkPnt.drawLine) {
     487                            Point screen = mv.getPoint(trkPnt.eastNorth);
     488                            // skip points that are on the same screenposition
     489                            if (old != null && (oldA == null || screen.x < oldA.x-delta || screen.x > oldA.x+delta || screen.y < oldA.y-delta || screen.y > oldA.y+delta)) {
     490                                g.setColor(trkPnt.speedLineColor);
     491                                double t = Math.atan2(screen.y-old.y, screen.x-old.x) + Math.PI;
     492                                g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t-PHI)), (int)(screen.y
     493                                + 10*Math.sin(t-PHI)));
     494                                g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t+PHI)), (int)(screen.y
     495                                + 10*Math.sin(t+PHI)));
     496                                oldA = screen;
     497                            }
     498                            old = screen;
     499                        }
     500                    } // end for trkpnt
     501                } // end for segment
     502            } // end for trk
     503        } // end if lines
     504
     505        /****************************************************************
     506        ********** STEP 3c - DRAW FAST ARROWS **************************
     507        ****************************************************************/
     508        if (lines && direction && alternatedirection) {
     509            Point old = null;
     510            Point oldA = null; // last arrow painted
     511            for (GpxTrack trk : data.tracks) {
     512                for (Collection<WayPoint> segment : trk.trackSegs) {
     513                    for (WayPoint trkPnt : segment) {
     514                        if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
     515                            continue;
     516                        if (trkPnt.drawLine) {
     517                            Point screen = mv.getPoint(trkPnt.eastNorth);
     518                            // skip points that are on the same screenposition
     519                            if (old != null && (oldA == null || screen.x < oldA.x-delta || screen.x > oldA.x+delta || screen.y < oldA.y-delta || screen.y > oldA.y+delta)) {
     520                                g.setColor(trkPnt.speedLineColor);
     521                                g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][0], screen.y + dir[trkPnt.dir][1]);
     522                                g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][2], screen.y + dir[trkPnt.dir][3]);
     523                                oldA = screen;
     524                            }
     525                            old = screen;
     526                        }
     527                    } // end for trkpnt
     528                } // end for segment
     529            } // end for trk
     530        } // end if lines
     531
     532        /****************************************************************
     533        ********** STEP 3d - DRAW LARGE POINTS *************************
     534        ****************************************************************/
     535        if (large) {
     536            g.setColor(neutralColor);
     537            for (GpxTrack trk : data.tracks) {
     538                for (Collection<WayPoint> segment : trk.trackSegs) {
     539                    for (WayPoint trkPnt : segment) {
     540                        if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
     541                            continue;
     542                        Point screen = mv.getPoint(trkPnt.eastNorth);
     543                            g.fillRect(screen.x-1, screen.y-1, 3, 3);
     544                    } // end for trkpnt
     545                } // end for segment
     546            } // end for trk
     547        } // end if large
     548
     549        /****************************************************************
     550        ********** STEP 3e - DRAW SMALL POINTS FOR LINES ***************
     551        ****************************************************************/
     552        if (!large && lines){
     553            g.setColor(neutralColor);
     554            for (GpxTrack trk : data.tracks) {
     555                for (Collection<WayPoint> segment : trk.trackSegs) {
     556                    for (WayPoint trkPnt : segment) {
     557                        if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
     558                            continue;
     559                        if (!trkPnt.drawLine) {
     560                            Point screen = mv.getPoint(trkPnt.eastNorth);
     561                        g.drawRect(screen.x, screen.y, 0, 0);
     562                    }
     563                    } // end for trkpnt
     564                } // end for segment
     565            } // end for trk
     566        } // end if large
     567
     568        /****************************************************************
     569        ********** STEP 3f - DRAW SMALL POINTS INSTEAD OF LINES ********
     570        ****************************************************************/
     571        if (!large && !lines){
     572            g.setColor(neutralColor);
     573            for (GpxTrack trk : data.tracks) {
     574                for (Collection<WayPoint> segment : trk.trackSegs) {
     575                    for (WayPoint trkPnt : segment) {
     576                        if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
     577                            continue;
     578                        Point screen = mv.getPoint(trkPnt.eastNorth);
     579                        g.drawRect(screen.x, screen.y, 0, 0);
     580                    } // end for trkpnt
     581                } // end for segment
     582            } // end for trk
     583        } // end if large
     584
     585        //Long duration = System.currentTimeMillis() - startTime;
     586        //System.out.println(duration);
     587    } // end paint
     588
     589    @Override public void visitBoundingBox(BoundingXYVisitor v) {
     590        for (WayPoint p : data.waypoints)
     591            v.visit(p.eastNorth);
     592
     593        for (GpxRoute rte : data.routes) {
     594            Collection<WayPoint> r = rte.routePoints;
     595            for (WayPoint p : r) {
     596                v.visit(p.eastNorth);
     597            }
     598        }
     599
     600        for (GpxTrack trk : data.tracks) {
     601            for (Collection<WayPoint> seg : trk.trackSegs) {
     602                for (WayPoint p : seg) {
     603                    v.visit(p.eastNorth);
     604                }
     605            }
     606        }
     607    }
     608
     609    public class UploadTraceAction extends AbstractAction {
     610        public UploadTraceAction() {
     611            super(tr("Upload this trace..."), ImageProvider.get("uploadtrace"));
     612        }
     613        public void actionPerformed(ActionEvent e) {
     614            JPanel msg = new JPanel(new GridBagLayout());
     615            msg.add(new JLabel(tr("<html>This functionality has been added only recently. Please<br>"+
     616            "use with care and check if it works as expected.</html>")), GBC.eop());
     617            ButtonGroup bg = new ButtonGroup();
     618            JRadioButton c1 = null;
     619            JRadioButton c2 = null;
     620
     621            //TODO
     622            //check whether data comes from server
     623            //check whether data changed sind last save/open
     624
     625            c1 = new JRadioButton(tr("Upload track filtered by JOSM"), true);
     626            c2 = new JRadioButton(tr("Upload raw file: "), false);
     627            c2.setEnabled(false);
     628            c1.setEnabled(false);
     629            bg.add(c1);
     630            bg.add(c2);
     631
     632            msg.add(c1, GBC.eol());
     633            msg.add(c2, GBC.eop());
     634
     635
     636            JLabel description = new JLabel((String) data.attr.get("desc"));
     637            JTextField tags = new JTextField();
     638            tags.setText((String) data.attr.get("keywords"));
     639            msg.add(new JLabel(tr("Description:")), GBC.std());
     640            msg.add(description, GBC.eol().fill(GBC.HORIZONTAL));
     641            msg.add(new JLabel(tr("Tags (keywords in GPX):")), GBC.std());
     642            msg.add(tags, GBC.eol().fill(GBC.HORIZONTAL));
     643            JCheckBox c3 = new JCheckBox("public");
     644            msg.add(c3, GBC.eop());
     645            msg.add(new JLabel("Please ensure that you don't upload your traces twice."), GBC.eop());
     646
     647            int answer = JOptionPane.showConfirmDialog(Main.parent, msg, tr("GPX-Upload"), JOptionPane.OK_CANCEL_OPTION);
     648            if (answer == JOptionPane.OK_OPTION)
     649            {
     650                try {
     651                    String version = Main.pref.get("osm-server.version", "0.5");
     652                    URL url = new URL(Main.pref.get("osm-server.url") + "/" + version + "/gpx/create");
     653
     654                    // create a boundary string
     655                    String boundary = MultiPartFormOutputStream.createBoundary();
     656                    URLConnection urlConn = MultiPartFormOutputStream.createConnection(url);
     657                    urlConn.setRequestProperty("Accept", "*/*");
     658                    urlConn.setRequestProperty("Content-Type", MultiPartFormOutputStream.getContentType(boundary));
     659                    // set some other request headers...
     660                    urlConn.setRequestProperty("Connection", "Keep-Alive");
     661                    urlConn.setRequestProperty("Cache-Control", "no-cache");
     662                    // no need to connect cuz getOutputStream() does it
     663                    MultiPartFormOutputStream out = new MultiPartFormOutputStream(urlConn.getOutputStream(), boundary);
     664                    out.writeField("description", description.getText());
     665                    out.writeField("tags", tags.getText());
     666                    out.writeField("public", (c3.getSelectedObjects() != null) ? "1" : "0");
     667                    // upload a file
     668                    // out.writeFile("gpx_file", "text/xml", associatedFile);
     669                    // can also write bytes directly
     670                    // out.writeFile("myFile", "text/plain", "C:\\test.txt",
     671                    // "This is some file text.".getBytes("ASCII"));
     672                    File tmp = File.createTempFile("josm", "tmp.gpx");
     673                    FileOutputStream outs = new FileOutputStream(tmp);
     674                    new GpxWriter(outs).write(data);
     675                    outs.close();
     676                    FileInputStream ins = new FileInputStream(tmp);
     677                    new GpxWriter(System.out).write(data);
     678                    out.writeFile("gpx_file", "text/xml", data.storageFile.getName(), ins);
     679                    out.close();
     680                    tmp.delete();
     681                    // read response from server
     682                    BufferedReader in = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
     683                    String line = "";
     684                    while((line = in.readLine()) != null) {
     685                        System.out.println(line);
     686                    }
     687                    in.close();
     688
     689                    //TODO check response
     690                    /*                  int retCode = urlConn.getResponseCode();
     691                    System.out.println("got return: " + retCode);
     692                    String retMsg = urlConn.getResponseMessage();
     693                    urlConn.disconnect();
     694                    if (retCode != 200) {
     695                        // Look for a detailed error message from the server
     696                        if (urlConn.getHeaderField("Error") != null)
     697                            retMsg += "\n" + urlConn.getHeaderField("Error");
     698
     699                        // Report our error
     700                        ByteArrayOutputStream o = new ByteArrayOutputStream();
     701                        System.out.println(new String(o.toByteArray(), "UTF-8").toString());
     702                        throw new RuntimeException(retCode+" "+retMsg);
     703                    }
     704                    */
     705                } catch (UnknownHostException ex) {
     706                    throw new RuntimeException(tr("Unknown host")+": "+ex.getMessage(), ex);
     707                } catch (Exception ex) {
     708                    //if (cancel)
     709                    //  return; // assume cancel
     710                    if (ex instanceof RuntimeException)
     711                        throw (RuntimeException)ex;
     712                    throw new RuntimeException(ex.getMessage(), ex);
     713                }
     714            }
     715        }
     716    }
     717
     718    public class ConvertToDataLayerAction extends AbstractAction {
     719        public ConvertToDataLayerAction() {
     720            super(tr("Convert to data layer"), ImageProvider.get("converttoosm"));
     721        }
     722        public void actionPerformed(ActionEvent e) {
     723            JPanel msg = new JPanel(new GridBagLayout());
     724            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:")), GBC.eol());
     725            msg.add(new UrlLabel(tr("http://www.openstreetmap.org/traces")), GBC.eop());
     726            if (!DontShowAgainInfo.show("convert_to_data", msg))
     727                return;
     728            DataSet ds = new DataSet();
     729            for (GpxTrack trk : data.tracks) {
     730                for (Collection<WayPoint> segment : trk.trackSegs) {
     731                    Way w = new Way();
     732                    for (WayPoint p : segment) {
     733                        Node n = new Node(p.latlon);
     734                        String timestr = p.getString("time");
     735                        if(timestr != null)
     736                        {
     737                            timestr = timestr.replace("Z","+00:00");
     738                            n.timestamp = timestr;
     739                        }
     740                        ds.nodes.add(n);
     741                        w.nodes.add(n);
     742                    }
     743                    ds.ways.add(w);
     744                }
     745            }
     746            Main.main.addLayer(new OsmDataLayer(ds, tr("Converted from: {0}", GpxLayer.this.name), null));
     747            Main.main.removeLayer(GpxLayer.this);
     748        }
     749    }
    750750
    751751    /**
    752752     * Action that issues a series of download requests to the API, following the GPX track.
    753      * 
     753     *
    754754     * @author fred
    755755     */
     
    762762            JList buffer = new JList(new String[] { "50 metres", "500 metres", "5000 metres" });
    763763            JList maxRect = new JList(new String[] { "1 sq km", "5 sq km", "10 sq km", "20 sq km" });
    764            
     764
    765765            msg.add(new JLabel(tr("Download everything within:")), GBC.eol());
    766766            msg.add(buffer, GBC.eol());
    767767            msg.add(new JLabel(tr("Maximum area per request:")), GBC.eol());
    768768            msg.add(maxRect, GBC.eol());
    769            
    770             if (JOptionPane.showConfirmDialog(Main.parent, msg, 
    771                 tr("Download from OSM along this track"), 
     769
     770            if (JOptionPane.showConfirmDialog(Main.parent, msg,
     771                tr("Download from OSM along this track"),
    772772                JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
    773773                return;
    774            
     774
    775775            }
    776776
     
    781781            double latsum = 0;
    782782            int latcnt = 0;
    783            
     783
    784784            for (GpxTrack trk : data.tracks) {
    785785                for (Collection<WayPoint> segment : trk.trackSegs) {
     
    790790                }
    791791            }
    792            
     792
    793793            double avglat = latsum / latcnt;
    794794            double scale = Math.cos(Math.toRadians(avglat));
     
    797797             * Compute buffer zone extents and maximum bounding box size. Note how the
    798798             * maximum we ever offer is a bbox area of 0.002, while the API theoretically
    799              * supports 0.25, but as soon as you touch any built-up area, that kind of 
    800              * bounding box will download forever and then stop because it has more than 
     799             * supports 0.25, but as soon as you touch any built-up area, that kind of
     800             * bounding box will download forever and then stop because it has more than
    801801             * 50k nodes.
    802802             */
     
    809809            }
    810810            buffer_x = buffer_y / scale;
    811            
     811
    812812            double max_area;
    813813            switch(maxRect.getSelectedIndex()) {
     
    820820            Area a = new Area();
    821821            Rectangle2D r = new Rectangle2D.Double();
    822            
     822
    823823            /*
    824824             * Collect the combined area of all gpx points plus buffer zones around them.
     
    836836                }
    837837            }
    838            
     838
    839839            /*
    840840             * Area "a" now contains the hull that we would like to download data for.
     
    858858             * actually has something in it.
    859859             */
    860            
     860
    861861            List<Rectangle2D> toDownload = new ArrayList<Rectangle2D>();
    862            
     862
    863863            addToDownload(a, a.getBounds(), toDownload, max_area);
    864            
     864
    865865            msg = new JPanel(new GridBagLayout());
    866                        
     866
    867867            msg.add(new JLabel(tr("<html>This action will require {0} individual<br>download requests. Do you wish<br>to continue?</html>",
    868868                toDownload.size())), GBC.eol());
    869            
    870             if (JOptionPane.showConfirmDialog(Main.parent, msg, 
    871                 tr("Download from OSM along this track"), 
     869
     870            if (JOptionPane.showConfirmDialog(Main.parent, msg,
     871                tr("Download from OSM along this track"),
    872872                JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
    873873                return;
    874874            }
    875            
     875
    876876            // FIXME: DownloadTask's "please wait" dialog should display the number of
    877877            // downloads left, and "cancel" needs to be honoured. An error along the way
     
    883883        }
    884884    }
    885    
     885
    886886    private static void addToDownload(Area a, Rectangle2D r, Collection<Rectangle2D> results, double max_area) {
    887887        Area tmp = new Area(r);
     
    909909        }
    910910    }
    911    
    912         /**
    913         * Makes a new marker layer derived from this GpxLayer containing at least one
    914         * audio marker which the given audio file is associated with.
    915         * Markers are derived from the following
    916         * (a) explict waypoints in the GPX layer, or
    917         * (b) named trackpoints in the GPX layer, or
    918         * (c) (in future) voice recognised markers in the sound recording
    919         * (d) a single marker at the beginning of the track
    920         * @param wavFile : the file to be associated with the markers in the new marker layer
    921         */
    922         private void importAudio(File wavFile) {
    923                 String uri = "file:".concat(wavFile.getAbsolutePath());
    924                 MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", name), associatedFile, me);
    925 
    926                 Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
    927                 boolean timedMarkersOmitted = false;
    928                 boolean untimedMarkersOmitted = false;
    929                 double snapDistance = Main.pref.getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3); /* about 25m */
    930 
    931                 // determine time of first point in track
    932                 double firstTime = -1.0;
    933                 if (data.tracks != null && ! data.tracks.isEmpty()) {
    934                         for (GpxTrack track : data.tracks) {
    935                                 if (track.trackSegs == null) continue;
    936                                 for (Collection<WayPoint> seg : track.trackSegs) {
    937                                         for (WayPoint w : seg) {
    938                                                 firstTime = w.time;
    939                                                 break;
    940                                         }
    941                                         if (firstTime >= 0.0) break;
    942                                 }
    943                                 if (firstTime >= 0.0) break;
    944                         }
    945                 }
    946                 if (firstTime < 0.0) {
    947                         JOptionPane.showMessageDialog(Main.parent, tr("No GPX track available in layer to associate audio with."));
    948                         return;
    949                 }
    950 
    951                 // (a) try explicit timestamped waypoints - unless suppressed
    952                 if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) &&
    953                         data.waypoints != null && ! data.waypoints.isEmpty())
    954                 {
    955                         for (WayPoint w : data.waypoints) {
    956                                 if (w.time > firstTime) {
    957                                         waypoints.add(w);
    958                                 } else if (w.time > 0.0) {
    959                                         timedMarkersOmitted = true;
    960                                 }
    961                         }
    962                 }
    963 
    964                 // (b) try explicit waypoints without timestamps - unless suppressed
    965                 if (Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true) &&
    966                         data.waypoints != null && ! data.waypoints.isEmpty())
    967                 {
    968                         for (WayPoint w : data.waypoints) {
    969                                 if (waypoints.contains(w)) { continue; }
    970                                 WayPoint wNear = nearestPointOnTrack(w.eastNorth, snapDistance);
    971                                 if (wNear != null) {
    972                                         WayPoint wc = new WayPoint(w.latlon);
    973                                         wc.time = wNear.time;
    974                                         if (w.attr.containsKey("name")) wc.attr.put("name", w.getString("name"));
    975                                         waypoints.add(wc);
    976                                 } else {
    977                                         untimedMarkersOmitted = true;
    978                                 }
    979                         }
    980                 }
    981 
    982                 // (c) use explicitly named track points, again unless suppressed
    983                 if ((Main.pref.getBoolean("marker.audiofromnamedtrackpoints", false)) &&
    984                         data.tracks != null && ! data.tracks.isEmpty())
    985                 {
    986                         for (GpxTrack track : data.tracks) {
    987                                 if (track.trackSegs == null) continue;
    988                                 for (Collection<WayPoint> seg : track.trackSegs) {
    989                                         for (WayPoint w : seg) {
    990                                                 if (w.attr.containsKey("name") || w.attr.containsKey("desc")) {
    991                                                         waypoints.add(w);
    992                                                 }
    993                                         }
    994                                 }
    995                         }
    996                 }
    997 
    998                 // (d) analyse audio for spoken markers here, in due course
    999 
    1000                 // (e) simply add a single marker at the start of the track
    1001                 if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) &&
    1002                         data.tracks != null && ! data.tracks.isEmpty())
    1003                 {
    1004                         boolean gotOne = false;
    1005                         for (GpxTrack track : data.tracks) {
    1006                                 if (track.trackSegs == null) continue;
    1007                                 for (Collection<WayPoint> seg : track.trackSegs) {
    1008                                         for (WayPoint w : seg) {
    1009                                                 WayPoint wStart = new WayPoint(w.latlon);
    1010                                                 wStart.attr.put("name", "start");
    1011                                                 wStart.time = w.time;
    1012                                                 waypoints.add(wStart);
    1013                                                 gotOne = true;
    1014                                                 break;
    1015                                         }
    1016                                         if (gotOne) break;
    1017                                 }
    1018                                 if (gotOne) break;
    1019                         }
    1020                 }
    1021 
    1022                 /* we must have got at least one waypoint now */
    1023 
    1024                 Collections.sort((ArrayList<WayPoint>) waypoints, new Comparator<WayPoint>() {
    1025                         public int compare(WayPoint a, WayPoint b) {
    1026                                 return a.time <= b.time ? -1 : 1;
    1027                         }
    1028                 });
    1029 
    1030                 firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
    1031                 for (WayPoint w : waypoints) {
    1032                         if (firstTime < 0.0) firstTime = w.time;
    1033                         double offset = w.time - firstTime;
    1034                         String name;
    1035                         if (w.attr.containsKey("name"))
    1036                                 name = w.getString("name");
    1037                         else if (w.attr.containsKey("desc"))
    1038                                 name = w.getString("desc");
    1039                         else
    1040                                 name = AudioMarker.inventName(offset);
    1041                         AudioMarker am = AudioMarker.create(w.latlon,
    1042                                         name, uri, ml, w.time, offset);
    1043                         ml.data.add(am);
    1044                 }
    1045                 Main.main.addLayer(ml);
    1046 
    1047                 if (timedMarkersOmitted) {
    1048                         JOptionPane.showMessageDialog(Main.parent,
    1049                         tr("Some waypoints with timestamps from before the start of the track were omitted."));
    1050                 }
    1051                 if (untimedMarkersOmitted) {
    1052                         JOptionPane.showMessageDialog(Main.parent,
    1053                         tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
    1054                 }
    1055         }
    1056 
    1057         /**
    1058         * Makes a WayPoint at the projection of point P onto the track providing P is
    1059         * less than tolerance away from the track
    1060 
    1061         * @param P : the point to determine the projection for
    1062         * @param tolerance : must be no further than this from the track
    1063         * @return the closest point on the track to P, which may be the
    1064         * first or last point if off the end of a segment, or may be null if
    1065         * nothing close enough
    1066         */
    1067         public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
    1068                 /*
    1069                 * assume the coordinates of P are xp,yp, and those of a section of track
    1070                 * between two trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
    1071                 *
    1072                 * The equation of RS is Ax + By + C = 0 where
    1073                 * A = ys - yr
    1074                 * B = xr - xs
    1075                 * C = - Axr - Byr
    1076                 *
    1077                 * Also, note that the distance RS^2 is A^2 + B^2
    1078                 *
    1079                 * If RS^2 == 0.0 ignore the degenerate section of track
    1080                 *
    1081                 * PN^2 = (Axp + Byp + C)^2 / RS^2
    1082                 * that is the distance from P to the line
    1083                 *
    1084                 * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject
    1085                 * the line; otherwise...
    1086                 * determine if the projected poijnt lies within the bounds of the line:
    1087                 * PR^2 - PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
    1088                 *
    1089                 * where PR^2 = (xp - xr)^2 + (yp-yr)^2
    1090                 * and   PS^2 = (xp - xs)^2 + (yp-ys)^2
    1091                 *
    1092                 * If so, calculate N as
    1093                 * xn = xr + (RN/RS) B
    1094                 * yn = y1 + (RN/RS) A
    1095                 *
    1096                 * where RN = sqrt(PR^2 - PN^2)
    1097                 */
    1098 
    1099                 double PNminsq = tolerance * tolerance;
    1100                 EastNorth bestEN = null;
    1101                 double bestTime = 0.0;
    1102                 double px = P.east();
    1103                 double py = P.north();
    1104                 double rx = 0.0, ry = 0.0, sx, sy, x, y;
    1105                 if (data.tracks == null) return null;
    1106                 for (GpxTrack track : data.tracks) {
    1107                         if (track.trackSegs == null) continue;
    1108                         for (Collection<WayPoint> seg : track.trackSegs) {
    1109                                 WayPoint R = null;
    1110                                 for (WayPoint S : seg) {
    1111                                         if (R == null) {
    1112                                                 R = S;
    1113                                                 rx = R.eastNorth.east();
    1114                                                 ry = R.eastNorth.north();
    1115                                                 x = px - rx;
    1116                                                 y = py - ry;
    1117                                                 double PRsq = x * x + y * y;
    1118                                                 if (PRsq < PNminsq) {
    1119                                                         PNminsq = PRsq;
    1120                                                         bestEN = R.eastNorth;
    1121                                                         bestTime = R.time;
    1122                                                 }
    1123                                         } else {
    1124                                                 sx = S.eastNorth.east();
    1125                                                 sy = S.eastNorth.north();
    1126                                                 double A = sy - ry;
    1127                                                 double B = rx - sx;
    1128                                                 double C = - A * rx - B * ry;
    1129                                                 double RSsq = A * A + B * B;
    1130                                                 if (RSsq == 0.0) continue;
    1131                                                 double PNsq = A * px + B * py + C;
    1132                                                 PNsq = PNsq * PNsq / RSsq;
    1133                                                 if (PNsq < PNminsq) {
    1134                                                         x = px - rx;
    1135                                                         y = py - ry;
    1136                                                         double PRsq = x * x + y * y;
    1137                                                         x = px - sx;
    1138                                                         y = py - sy;
    1139                                                         double PSsq = x * x + y * y;
    1140                                                         if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
    1141                                                                 double RNoverRS = Math.sqrt((PRsq - PNsq)/RSsq);
    1142                                                                 double nx = rx - RNoverRS * B;
    1143                                                                 double ny = ry + RNoverRS * A;
    1144                                                                 bestEN = new EastNorth(nx, ny);
    1145                                                                 bestTime = R.time + RNoverRS * (S.time - R.time);
    1146                                                                 PNminsq = PNsq;
    1147                                                         }
    1148                                                 }
    1149                                                 R = S;
    1150                                                 rx = sx;
    1151                                                 ry = sy;
    1152                                         }
    1153                                 }
    1154                                 if (R != null) {
    1155                                         /* if there is only one point in the seg, it will do this twice, but no matter */
    1156                                         rx = R.eastNorth.east();
    1157                                         ry = R.eastNorth.north();
    1158                                         x = px - rx;
    1159                                         y = py - ry;
    1160                                         double PRsq = x * x + y * y;
    1161                                         if (PRsq < PNminsq) {
    1162                                                 PNminsq = PRsq;
    1163                                                 bestEN = R.eastNorth;
    1164                                                 bestTime = R.time;
    1165                                         }
    1166                                 }
    1167                         }
    1168                 }
    1169                 if (bestEN == null) return null;
    1170                 WayPoint best = new WayPoint(Main.proj.eastNorth2latlon(bestEN));
    1171                 best.time = bestTime;
    1172                 return best;
    1173         }
     911
     912    /**
     913    * Makes a new marker layer derived from this GpxLayer containing at least one
     914    * audio marker which the given audio file is associated with.
     915    * Markers are derived from the following
     916    * (a) explict waypoints in the GPX layer, or
     917    * (b) named trackpoints in the GPX layer, or
     918    * (c) (in future) voice recognised markers in the sound recording
     919    * (d) a single marker at the beginning of the track
     920    * @param wavFile : the file to be associated with the markers in the new marker layer
     921    */
     922    private void importAudio(File wavFile) {
     923        String uri = "file:".concat(wavFile.getAbsolutePath());
     924        MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", name), associatedFile, me);
     925
     926        Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
     927        boolean timedMarkersOmitted = false;
     928        boolean untimedMarkersOmitted = false;
     929        double snapDistance = Main.pref.getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3); /* about 25m */
     930
     931        // determine time of first point in track
     932        double firstTime = -1.0;
     933        if (data.tracks != null && ! data.tracks.isEmpty()) {
     934            for (GpxTrack track : data.tracks) {
     935                if (track.trackSegs == null) continue;
     936                for (Collection<WayPoint> seg : track.trackSegs) {
     937                    for (WayPoint w : seg) {
     938                        firstTime = w.time;
     939                        break;
     940                    }
     941                    if (firstTime >= 0.0) break;
     942                }
     943                if (firstTime >= 0.0) break;
     944            }
     945        }
     946        if (firstTime < 0.0) {
     947            JOptionPane.showMessageDialog(Main.parent, tr("No GPX track available in layer to associate audio with."));
     948            return;
     949        }
     950
     951        // (a) try explicit timestamped waypoints - unless suppressed
     952        if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) &&
     953            data.waypoints != null && ! data.waypoints.isEmpty())
     954        {
     955            for (WayPoint w : data.waypoints) {
     956                if (w.time > firstTime) {
     957                    waypoints.add(w);
     958                } else if (w.time > 0.0) {
     959                    timedMarkersOmitted = true;
     960                }
     961            }
     962        }
     963
     964        // (b) try explicit waypoints without timestamps - unless suppressed
     965        if (Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true) &&
     966            data.waypoints != null && ! data.waypoints.isEmpty())
     967        {
     968            for (WayPoint w : data.waypoints) {
     969                if (waypoints.contains(w)) { continue; }
     970                WayPoint wNear = nearestPointOnTrack(w.eastNorth, snapDistance);
     971                if (wNear != null) {
     972                    WayPoint wc = new WayPoint(w.latlon);
     973                    wc.time = wNear.time;
     974                    if (w.attr.containsKey("name")) wc.attr.put("name", w.getString("name"));
     975                    waypoints.add(wc);
     976                } else {
     977                    untimedMarkersOmitted = true;
     978                }
     979            }
     980        }
     981
     982        // (c) use explicitly named track points, again unless suppressed
     983        if ((Main.pref.getBoolean("marker.audiofromnamedtrackpoints", false)) &&
     984            data.tracks != null && ! data.tracks.isEmpty())
     985        {
     986            for (GpxTrack track : data.tracks) {
     987                if (track.trackSegs == null) continue;
     988                for (Collection<WayPoint> seg : track.trackSegs) {
     989                    for (WayPoint w : seg) {
     990                        if (w.attr.containsKey("name") || w.attr.containsKey("desc")) {
     991                            waypoints.add(w);
     992                        }
     993                    }
     994                }
     995            }
     996        }
     997
     998        // (d) analyse audio for spoken markers here, in due course
     999
     1000        // (e) simply add a single marker at the start of the track
     1001        if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) &&
     1002            data.tracks != null && ! data.tracks.isEmpty())
     1003        {
     1004            boolean gotOne = false;
     1005            for (GpxTrack track : data.tracks) {
     1006                if (track.trackSegs == null) continue;
     1007                for (Collection<WayPoint> seg : track.trackSegs) {
     1008                    for (WayPoint w : seg) {
     1009                        WayPoint wStart = new WayPoint(w.latlon);
     1010                        wStart.attr.put("name", "start");
     1011                        wStart.time = w.time;
     1012                        waypoints.add(wStart);
     1013                        gotOne = true;
     1014                        break;
     1015                    }
     1016                    if (gotOne) break;
     1017                }
     1018                if (gotOne) break;
     1019            }
     1020        }
     1021
     1022        /* we must have got at least one waypoint now */
     1023
     1024        Collections.sort((ArrayList<WayPoint>) waypoints, new Comparator<WayPoint>() {
     1025            public int compare(WayPoint a, WayPoint b) {
     1026                return a.time <= b.time ? -1 : 1;
     1027            }
     1028        });
     1029
     1030        firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
     1031        for (WayPoint w : waypoints) {
     1032            if (firstTime < 0.0) firstTime = w.time;
     1033            double offset = w.time - firstTime;
     1034            String name;
     1035            if (w.attr.containsKey("name"))
     1036                name = w.getString("name");
     1037            else if (w.attr.containsKey("desc"))
     1038                name = w.getString("desc");
     1039            else
     1040                name = AudioMarker.inventName(offset);
     1041            AudioMarker am = AudioMarker.create(w.latlon,
     1042                    name, uri, ml, w.time, offset);
     1043            ml.data.add(am);
     1044        }
     1045        Main.main.addLayer(ml);
     1046
     1047        if (timedMarkersOmitted) {
     1048            JOptionPane.showMessageDialog(Main.parent,
     1049            tr("Some waypoints with timestamps from before the start of the track were omitted."));
     1050        }
     1051        if (untimedMarkersOmitted) {
     1052            JOptionPane.showMessageDialog(Main.parent,
     1053            tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
     1054        }
     1055    }
     1056
     1057    /**
     1058    * Makes a WayPoint at the projection of point P onto the track providing P is
     1059    * less than tolerance away from the track
     1060
     1061    * @param P : the point to determine the projection for
     1062    * @param tolerance : must be no further than this from the track
     1063    * @return the closest point on the track to P, which may be the
     1064    * first or last point if off the end of a segment, or may be null if
     1065    * nothing close enough
     1066    */
     1067    public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
     1068        /*
     1069        * assume the coordinates of P are xp,yp, and those of a section of track
     1070        * between two trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
     1071        *
     1072        * The equation of RS is Ax + By + C = 0 where
     1073        * A = ys - yr
     1074        * B = xr - xs
     1075        * C = - Axr - Byr
     1076        *
     1077        * Also, note that the distance RS^2 is A^2 + B^2
     1078        *
     1079        * If RS^2 == 0.0 ignore the degenerate section of track
     1080        *
     1081        * PN^2 = (Axp + Byp + C)^2 / RS^2
     1082        * that is the distance from P to the line
     1083        *
     1084        * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject
     1085        * the line; otherwise...
     1086        * determine if the projected poijnt lies within the bounds of the line:
     1087        * PR^2 - PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
     1088        *
     1089        * where PR^2 = (xp - xr)^2 + (yp-yr)^2
     1090        * and   PS^2 = (xp - xs)^2 + (yp-ys)^2
     1091        *
     1092        * If so, calculate N as
     1093        * xn = xr + (RN/RS) B
     1094        * yn = y1 + (RN/RS) A
     1095        *
     1096        * where RN = sqrt(PR^2 - PN^2)
     1097        */
     1098
     1099        double PNminsq = tolerance * tolerance;
     1100        EastNorth bestEN = null;
     1101        double bestTime = 0.0;
     1102        double px = P.east();
     1103        double py = P.north();
     1104        double rx = 0.0, ry = 0.0, sx, sy, x, y;
     1105        if (data.tracks == null) return null;
     1106        for (GpxTrack track : data.tracks) {
     1107            if (track.trackSegs == null) continue;
     1108            for (Collection<WayPoint> seg : track.trackSegs) {
     1109                WayPoint R = null;
     1110                for (WayPoint S : seg) {
     1111                    if (R == null) {
     1112                        R = S;
     1113                        rx = R.eastNorth.east();
     1114                        ry = R.eastNorth.north();
     1115                        x = px - rx;
     1116                        y = py - ry;
     1117                        double PRsq = x * x + y * y;
     1118                        if (PRsq < PNminsq) {
     1119                            PNminsq = PRsq;
     1120                            bestEN = R.eastNorth;
     1121                            bestTime = R.time;
     1122                        }
     1123                    } else {
     1124                        sx = S.eastNorth.east();
     1125                        sy = S.eastNorth.north();
     1126                        double A = sy - ry;
     1127                        double B = rx - sx;
     1128                        double C = - A * rx - B * ry;
     1129                        double RSsq = A * A + B * B;
     1130                        if (RSsq == 0.0) continue;
     1131                        double PNsq = A * px + B * py + C;
     1132                        PNsq = PNsq * PNsq / RSsq;
     1133                        if (PNsq < PNminsq) {
     1134                            x = px - rx;
     1135                            y = py - ry;
     1136                            double PRsq = x * x + y * y;
     1137                            x = px - sx;
     1138                            y = py - sy;
     1139                            double PSsq = x * x + y * y;
     1140                            if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
     1141                                double RNoverRS = Math.sqrt((PRsq - PNsq)/RSsq);
     1142                                double nx = rx - RNoverRS * B;
     1143                                double ny = ry + RNoverRS * A;
     1144                                bestEN = new EastNorth(nx, ny);
     1145                                bestTime = R.time + RNoverRS * (S.time - R.time);
     1146                                PNminsq = PNsq;
     1147                            }
     1148                        }
     1149                        R = S;
     1150                        rx = sx;
     1151                        ry = sy;
     1152                    }
     1153                }
     1154                if (R != null) {
     1155                    /* if there is only one point in the seg, it will do this twice, but no matter */
     1156                    rx = R.eastNorth.east();
     1157                    ry = R.eastNorth.north();
     1158                    x = px - rx;
     1159                    y = py - ry;
     1160                    double PRsq = x * x + y * y;
     1161                    if (PRsq < PNminsq) {
     1162                        PNminsq = PRsq;
     1163                        bestEN = R.eastNorth;
     1164                        bestTime = R.time;
     1165                    }
     1166                }
     1167            }
     1168        }
     1169        if (bestEN == null) return null;
     1170        WayPoint best = new WayPoint(Main.proj.eastNorth2latlon(bestEN));
     1171        best.time = bestTime;
     1172        return best;
     1173    }
    11741174}
Note: See TracChangeset for help on using the changeset viewer.