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

Last change on this file since 6083 was 6083, checked in by bastiK, 11 years ago

see #8902 - Small performance enhancements (patch by shinigami)

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