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

Last change on this file since 5717 was 5715, checked in by akks, 11 years ago

see #8416. GpxLayer refactoring: inner classes goes to org.openstreetmap.josm.gui.layer.gpx
Any change of behavior is a bug!

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