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

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

see #15182 - deprecate Main.getLayerManager(). Replacement: gui.MainApplication.getLayerManager()

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