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

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

sonar - squid:S2131 - Primitives should not be boxed just for "String" conversion

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