source: josm/trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java@ 11452

Last change on this file since 11452 was 11452, checked in by Don-vip, 7 years ago

sonar - fb-contrib:SEO_SUBOPTIMAL_EXPRESSION_ORDER - Performance - Method orders expressions in a conditional in a sub optimal way

  • Property svn:eol-style set to native
File size: 12.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer.gpx;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.event.ActionEvent;
8import java.io.File;
9import java.net.URL;
10import java.util.ArrayList;
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.Comparator;
14
15import javax.swing.AbstractAction;
16import javax.swing.JFileChooser;
17import javax.swing.JOptionPane;
18import javax.swing.filechooser.FileFilter;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.actions.DiskAccessAction;
22import org.openstreetmap.josm.data.gpx.GpxConstants;
23import org.openstreetmap.josm.data.gpx.GpxData;
24import org.openstreetmap.josm.data.gpx.GpxTrack;
25import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
26import org.openstreetmap.josm.data.gpx.WayPoint;
27import org.openstreetmap.josm.gui.HelpAwareOptionPane;
28import org.openstreetmap.josm.gui.layer.GpxLayer;
29import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
30import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
31import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
32import org.openstreetmap.josm.tools.AudioUtil;
33import org.openstreetmap.josm.tools.ImageProvider;
34import org.openstreetmap.josm.tools.Utils;
35
36/**
37 * Import audio files into a GPX layer to enable audio playback functions.
38 * @since 5715
39 */
40public class ImportAudioAction extends AbstractAction {
41 private final transient GpxLayer layer;
42
43 static final class AudioFileFilter extends FileFilter {
44 @Override
45 public boolean accept(File f) {
46 return f.isDirectory() || Utils.hasExtension(f, "wav");
47 }
48
49 @Override
50 public String getDescription() {
51 return tr("Wave Audio files (*.wav)");
52 }
53 }
54
55 private static class Markers {
56 public boolean timedMarkersOmitted;
57 public boolean untimedMarkersOmitted;
58 }
59
60 /**
61 * Constructs a new {@code ImportAudioAction}.
62 * @param layer The associated GPX layer
63 */
64 public ImportAudioAction(final GpxLayer layer) {
65 super(tr("Import Audio"), ImageProvider.get("importaudio"));
66 this.layer = layer;
67 putValue("help", ht("/Action/ImportAudio"));
68 }
69
70 private static void warnCantImportIntoServerLayer(GpxLayer layer) {
71 String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>" +
72 "Because its way points do not include a timestamp we cannot correlate them with audio data.</html>",
73 layer.getName());
74 HelpAwareOptionPane.showOptionDialog(Main.parent, msg, tr("Import not possible"),
75 JOptionPane.WARNING_MESSAGE, ht("/Action/ImportAudio#CantImportIntoGpxLayerFromServer"));
76 }
77
78 @Override
79 public void actionPerformed(ActionEvent e) {
80 if (layer.data.fromServer) {
81 warnCantImportIntoServerLayer(layer);
82 return;
83 }
84 AbstractFileChooser fc = DiskAccessAction.createAndOpenFileChooser(true, true, null, new AudioFileFilter(),
85 JFileChooser.FILES_ONLY, "markers.lastaudiodirectory");
86 if (fc != null) {
87 File[] sel = fc.getSelectedFiles();
88 // sort files in increasing order of timestamp (this is the end time, but so
89 // long as they don't overlap, that's fine)
90 if (sel.length > 1) {
91 Arrays.sort(sel, Comparator.comparingLong(File::lastModified));
92 }
93 StringBuilder names = new StringBuilder();
94 for (File file : sel) {
95 if (names.length() == 0) {
96 names.append(" (");
97 } else {
98 names.append(", ");
99 }
100 names.append(file.getName());
101 }
102 if (names.length() > 0) {
103 names.append(')');
104 }
105 MarkerLayer ml = new MarkerLayer(new GpxData(),
106 tr("Audio markers from {0}", layer.getName()) + names, layer.getAssociatedFile(), layer);
107 double firstStartTime = sel[0].lastModified() / 1000.0 - AudioUtil.getCalibratedDuration(sel[0]);
108 Markers m = new Markers();
109 for (File file : sel) {
110 importAudio(file, ml, firstStartTime, m);
111 }
112 Main.getLayerManager().addLayer(ml);
113 Main.map.repaint();
114 }
115 }
116
117 /**
118 * Makes a new marker layer derived from this GpxLayer containing at least one audio marker
119 * which the given audio file is associated with. Markers are derived from the following (a)
120 * explict waypoints in the GPX layer, or (b) named trackpoints in the GPX layer, or (d)
121 * timestamp on the wav file (e) (in future) voice recognised markers in the sound recording (f)
122 * a single marker at the beginning of the track
123 * @param wavFile the file to be associated with the markers in the new marker layer
124 * @param ml marker layer
125 * @param firstStartTime first start time in milliseconds, used for (d)
126 * @param markers keeps track of warning messages to avoid repeated warnings
127 */
128 private void importAudio(File wavFile, MarkerLayer ml, double firstStartTime, Markers markers) {
129 URL url = Utils.fileToURL(wavFile);
130 boolean hasTracks = layer.data.tracks != null && !layer.data.tracks.isEmpty();
131 boolean hasWaypoints = layer.data.waypoints != null && !layer.data.waypoints.isEmpty();
132 Collection<WayPoint> waypoints = new ArrayList<>();
133 boolean timedMarkersOmitted = false;
134 boolean untimedMarkersOmitted = false;
135 double snapDistance = Main.pref.getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3);
136 // about 25 m
137 WayPoint wayPointFromTimeStamp = null;
138
139 // determine time of first point in track
140 double firstTime = -1.0;
141 if (hasTracks) {
142 for (GpxTrack track : layer.data.tracks) {
143 for (GpxTrackSegment seg : track.getSegments()) {
144 for (WayPoint w : seg.getWayPoints()) {
145 firstTime = w.time;
146 break;
147 }
148 if (firstTime >= 0.0) {
149 break;
150 }
151 }
152 if (firstTime >= 0.0) {
153 break;
154 }
155 }
156 }
157 if (firstTime < 0.0) {
158 JOptionPane.showMessageDialog(
159 Main.parent,
160 tr("No GPX track available in layer to associate audio with."),
161 tr("Error"),
162 JOptionPane.ERROR_MESSAGE
163 );
164 return;
165 }
166
167 // (a) try explicit timestamped waypoints - unless suppressed
168 if (hasWaypoints && Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true)) {
169 for (WayPoint w : layer.data.waypoints) {
170 if (w.time > firstTime) {
171 waypoints.add(w);
172 } else if (w.time > 0.0) {
173 timedMarkersOmitted = true;
174 }
175 }
176 }
177
178 // (b) try explicit waypoints without timestamps - unless suppressed
179 if (hasWaypoints && Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true)) {
180 for (WayPoint w : layer.data.waypoints) {
181 if (waypoints.contains(w)) {
182 continue;
183 }
184 WayPoint wNear = layer.data.nearestPointOnTrack(w.getEastNorth(), snapDistance);
185 if (wNear != null) {
186 WayPoint wc = new WayPoint(w.getCoor());
187 wc.time = wNear.time;
188 if (w.attr.containsKey(GpxConstants.GPX_NAME)) {
189 wc.put(GpxConstants.GPX_NAME, w.getString(GpxConstants.GPX_NAME));
190 }
191 waypoints.add(wc);
192 } else {
193 untimedMarkersOmitted = true;
194 }
195 }
196 }
197
198 // (c) use explicitly named track points, again unless suppressed
199 if (layer.data.tracks != null && Main.pref.getBoolean("marker.audiofromnamedtrackpoints", false)
200 && !layer.data.tracks.isEmpty()) {
201 for (GpxTrack track : layer.data.tracks) {
202 for (GpxTrackSegment seg : track.getSegments()) {
203 for (WayPoint w : seg.getWayPoints()) {
204 if (w.attr.containsKey(GpxConstants.GPX_NAME) || w.attr.containsKey(GpxConstants.GPX_DESC)) {
205 waypoints.add(w);
206 }
207 }
208 }
209 }
210 }
211
212 // (d) use timestamp of file as location on track
213 if (hasTracks && Main.pref.getBoolean("marker.audiofromwavtimestamps", false)) {
214 double lastModified = wavFile.lastModified() / 1000.0; // lastModified is in
215 // milliseconds
216 double duration = AudioUtil.getCalibratedDuration(wavFile);
217 double startTime = lastModified - duration;
218 startTime = firstStartTime + (startTime - firstStartTime)
219 / Main.pref.getDouble("audio.calibration", 1.0 /* default, ratio */);
220 WayPoint w1 = null;
221 WayPoint w2 = null;
222
223 for (GpxTrack track : layer.data.tracks) {
224 for (GpxTrackSegment seg : track.getSegments()) {
225 for (WayPoint w : seg.getWayPoints()) {
226 if (startTime < w.time) {
227 w2 = w;
228 break;
229 }
230 w1 = w;
231 }
232 if (w2 != null) {
233 break;
234 }
235 }
236 }
237
238 if (w1 == null || w2 == null) {
239 timedMarkersOmitted = true;
240 } else {
241 wayPointFromTimeStamp = new WayPoint(w1.getCoor().interpolate(w2.getCoor(),
242 (startTime - w1.time) / (w2.time - w1.time)));
243 wayPointFromTimeStamp.time = startTime;
244 String name = wavFile.getName();
245 int dot = name.lastIndexOf('.');
246 if (dot > 0) {
247 name = name.substring(0, dot);
248 }
249 wayPointFromTimeStamp.put(GpxConstants.GPX_NAME, name);
250 waypoints.add(wayPointFromTimeStamp);
251 }
252 }
253
254 // (e) analyse audio for spoken markers here, in due course
255
256 // (f) simply add a single marker at the start of the track
257 if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) && hasTracks) {
258 boolean gotOne = false;
259 for (GpxTrack track : layer.data.tracks) {
260 for (GpxTrackSegment seg : track.getSegments()) {
261 for (WayPoint w : seg.getWayPoints()) {
262 WayPoint wStart = new WayPoint(w.getCoor());
263 wStart.put(GpxConstants.GPX_NAME, "start");
264 wStart.time = w.time;
265 waypoints.add(wStart);
266 gotOne = true;
267 break;
268 }
269 if (gotOne) {
270 break;
271 }
272 }
273 if (gotOne) {
274 break;
275 }
276 }
277 }
278
279 /* we must have got at least one waypoint now */
280
281 ((ArrayList<WayPoint>) waypoints).sort(Comparator.comparingDouble(o -> o.time));
282
283 firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
284 for (WayPoint w : waypoints) {
285 if (firstTime < 0.0) {
286 firstTime = w.time;
287 }
288 double offset = w.time - firstTime;
289 AudioMarker am = new AudioMarker(w.getCoor(), w, url, ml, w.time, offset);
290 /*
291 * timeFromAudio intended for future use to shift markers of this type on synchronization
292 */
293 if (w == wayPointFromTimeStamp) {
294 am.timeFromAudio = true;
295 }
296 ml.data.add(am);
297 }
298
299 if (timedMarkersOmitted && !markers.timedMarkersOmitted) {
300 JOptionPane
301 .showMessageDialog(
302 Main.parent,
303 // CHECKSTYLE.OFF: LineLength
304 tr("Some waypoints with timestamps from before the start of the track or after the end were omitted or moved to the start."));
305 // CHECKSTYLE.ON: LineLength
306 markers.timedMarkersOmitted = timedMarkersOmitted;
307 }
308 if (untimedMarkersOmitted && !markers.untimedMarkersOmitted) {
309 JOptionPane
310 .showMessageDialog(
311 Main.parent,
312 tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
313 markers.untimedMarkersOmitted = untimedMarkersOmitted;
314 }
315 }
316}
Note: See TracBrowser for help on using the repository browser.