source: josm/trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java@ 13652

Last change on this file since 13652 was 13281, checked in by stoecker, 6 years ago

see #15734 - drop some old checks

  • Property svn:eol-style set to native
File size: 17.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.Dimension;
8import java.awt.Graphics2D;
9import java.awt.event.ActionEvent;
10import java.io.File;
11import java.text.DateFormat;
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.Date;
15import java.util.List;
16
17import javax.swing.AbstractAction;
18import javax.swing.Action;
19import javax.swing.Icon;
20import javax.swing.JScrollPane;
21import javax.swing.SwingUtilities;
22
23import org.openstreetmap.josm.actions.ExpertToggleAction;
24import org.openstreetmap.josm.actions.ExpertToggleAction.ExpertModeChangeListener;
25import org.openstreetmap.josm.actions.RenameLayerAction;
26import org.openstreetmap.josm.actions.SaveActionBase;
27import org.openstreetmap.josm.data.Bounds;
28import org.openstreetmap.josm.data.SystemOfMeasurement;
29import org.openstreetmap.josm.data.gpx.GpxConstants;
30import org.openstreetmap.josm.data.gpx.GpxData;
31import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener;
32import org.openstreetmap.josm.data.gpx.GpxTrack;
33import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
34import org.openstreetmap.josm.data.preferences.NamedColorProperty;
35import org.openstreetmap.josm.data.projection.Projection;
36import org.openstreetmap.josm.gui.MapView;
37import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
38import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
39import org.openstreetmap.josm.gui.io.importexport.GpxImporter;
40import org.openstreetmap.josm.gui.layer.gpx.ChooseTrackVisibilityAction;
41import org.openstreetmap.josm.gui.layer.gpx.ConvertToDataLayerAction;
42import org.openstreetmap.josm.gui.layer.gpx.CustomizeDrawingAction;
43import org.openstreetmap.josm.gui.layer.gpx.DownloadAlongTrackAction;
44import org.openstreetmap.josm.gui.layer.gpx.DownloadWmsAlongTrackAction;
45import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper;
46import org.openstreetmap.josm.gui.layer.gpx.ImportAudioAction;
47import org.openstreetmap.josm.gui.layer.gpx.ImportImagesAction;
48import org.openstreetmap.josm.gui.layer.gpx.MarkersFromNamedPointsAction;
49import org.openstreetmap.josm.gui.widgets.HtmlPanel;
50import org.openstreetmap.josm.tools.ImageProvider;
51import org.openstreetmap.josm.tools.date.DateUtils;
52
53/**
54 * A layer that displays data from a Gpx file / the OSM gpx downloads.
55 */
56public class GpxLayer extends Layer implements ExpertModeChangeListener {
57
58 /** GPX data */
59 public GpxData data;
60 private final boolean isLocalFile;
61 private boolean isExpertMode;
62 /**
63 * used by {@link ChooseTrackVisibilityAction} to determine which tracks to show/hide
64 *
65 * Call {@link #invalidate()} after each change!
66 *
67 * TODO: Make it private, make it respond to track changes.
68 */
69 public boolean[] trackVisibility = new boolean[0];
70 /**
71 * Added as field to be kept as reference.
72 */
73 private final GpxDataChangeListener dataChangeListener = e -> this.invalidate();
74
75 /**
76 * Constructs a new {@code GpxLayer} without name.
77 * @param d GPX data
78 */
79 public GpxLayer(GpxData d) {
80 this(d, null, false);
81 }
82
83 /**
84 * Constructs a new {@code GpxLayer} with a given name.
85 * @param d GPX data
86 * @param name layer name
87 */
88 public GpxLayer(GpxData d, String name) {
89 this(d, name, false);
90 }
91
92 /**
93 * Constructs a new {@code GpxLayer} with a given name, that can be attached to a local file.
94 * @param d GPX data
95 * @param name layer name
96 * @param isLocal whether data is attached to a local file
97 */
98 public GpxLayer(GpxData d, String name, boolean isLocal) {
99 super(d.getString(GpxConstants.META_NAME));
100 data = d;
101 data.addWeakChangeListener(dataChangeListener);
102 trackVisibility = new boolean[data.getTracks().size()];
103 Arrays.fill(trackVisibility, true);
104 setName(name);
105 isLocalFile = isLocal;
106 ExpertToggleAction.addExpertModeChangeListener(this, true);
107 }
108
109 @Override
110 protected NamedColorProperty getBaseColorProperty() {
111 return GpxDrawHelper.DEFAULT_COLOR;
112 }
113
114 /**
115 * Returns a human readable string that shows the timespan of the given track
116 * @param trk The GPX track for which timespan is displayed
117 * @return The timespan as a string
118 */
119 public static String getTimespanForTrack(GpxTrack trk) {
120 Date[] bounds = GpxData.getMinMaxTimeForTrack(trk);
121 String ts = "";
122 if (bounds != null) {
123 DateFormat df = DateUtils.getDateFormat(DateFormat.SHORT);
124 String earliestDate = df.format(bounds[0]);
125 String latestDate = df.format(bounds[1]);
126
127 if (earliestDate.equals(latestDate)) {
128 DateFormat tf = DateUtils.getTimeFormat(DateFormat.SHORT);
129 ts += earliestDate + ' ';
130 ts += tf.format(bounds[0]) + " - " + tf.format(bounds[1]);
131 } else {
132 DateFormat dtf = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM);
133 ts += dtf.format(bounds[0]) + " - " + dtf.format(bounds[1]);
134 }
135
136 int diff = (int) (bounds[1].getTime() - bounds[0].getTime()) / 1000;
137 ts += String.format(" (%d:%02d)", diff / 3600, (diff % 3600) / 60);
138 }
139 return ts;
140 }
141
142 @Override
143 public Icon getIcon() {
144 return ImageProvider.get("layer", "gpx_small");
145 }
146
147 @Override
148 public Object getInfoComponent() {
149 StringBuilder info = new StringBuilder(128)
150 .append("<html><head><style>")
151 .append("td { padding: 4px 16px; }")
152 .append("</style></head><body>");
153
154 if (data.attr.containsKey("name")) {
155 info.append(tr("Name: {0}", data.get(GpxConstants.META_NAME))).append("<br>");
156 }
157
158 if (data.attr.containsKey("desc")) {
159 info.append(tr("Description: {0}", data.get(GpxConstants.META_DESC))).append("<br>");
160 }
161
162 if (!data.getTracks().isEmpty()) {
163 info.append("<table><thead align='center'><tr><td colspan='5'>")
164 .append(trn("{0} track, {1} track segments", "{0} tracks, {1} track segments",
165 data.getTrackCount(), data.getTrackCount(),
166 data.getTrackSegsCount(), data.getTrackSegsCount()))
167 .append("</td></tr><tr align='center'><td>").append(tr("Name"))
168 .append("</td><td>").append(tr("Description"))
169 .append("</td><td>").append(tr("Timespan"))
170 .append("</td><td>").append(tr("Length"))
171 .append("</td><td>").append(tr("Number of<br/>Segments"))
172 .append("</td><td>").append(tr("URL"))
173 .append("</td></tr></thead>");
174
175 for (GpxTrack trk : data.getTracks()) {
176 info.append("<tr><td>");
177 if (trk.getAttributes().containsKey(GpxConstants.GPX_NAME)) {
178 info.append(trk.get(GpxConstants.GPX_NAME));
179 }
180 info.append("</td><td>");
181 if (trk.getAttributes().containsKey(GpxConstants.GPX_DESC)) {
182 info.append(' ').append(trk.get(GpxConstants.GPX_DESC));
183 }
184 info.append("</td><td>");
185 info.append(getTimespanForTrack(trk));
186 info.append("</td><td>");
187 info.append(SystemOfMeasurement.getSystemOfMeasurement().getDistText(trk.length()));
188 info.append("</td><td>");
189 info.append(trk.getSegments().size());
190 info.append("</td><td>");
191 if (trk.getAttributes().containsKey("url")) {
192 info.append(trk.get("url"));
193 }
194 info.append("</td></tr>");
195 }
196 info.append("</table><br><br>");
197 }
198
199 info.append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length()))).append("<br>")
200 .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size()))
201 .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size()))
202 .append("<br></body></html>");
203
204 final JScrollPane sp = new JScrollPane(new HtmlPanel(info.toString()));
205 sp.setPreferredSize(new Dimension(sp.getPreferredSize().width+20, 370));
206 SwingUtilities.invokeLater(() -> sp.getVerticalScrollBar().setValue(0));
207 return sp;
208 }
209
210 @Override
211 public boolean isInfoResizable() {
212 return true;
213 }
214
215 @Override
216 public Action[] getMenuEntries() {
217 List<Action> entries = new ArrayList<>(Arrays.asList(
218 LayerListDialog.getInstance().createShowHideLayerAction(),
219 LayerListDialog.getInstance().createDeleteLayerAction(),
220 LayerListDialog.getInstance().createMergeLayerAction(this),
221 SeparatorLayerAction.INSTANCE,
222 new LayerSaveAction(this),
223 new LayerSaveAsAction(this),
224 new CustomizeColor(this),
225 new CustomizeDrawingAction(this),
226 new ImportImagesAction(this),
227 new ImportAudioAction(this),
228 new MarkersFromNamedPointsAction(this),
229 new ConvertToDataLayerAction.FromGpxLayer(this),
230 new DownloadAlongTrackAction(data),
231 new DownloadWmsAlongTrackAction(data),
232 SeparatorLayerAction.INSTANCE,
233 new ChooseTrackVisibilityAction(this),
234 new RenameLayerAction(getAssociatedFile(), this)));
235
236 List<Action> expert = Arrays.asList(
237 new CombineTracksToSegmentedTrackAction(this),
238 new SplitTrackSegementsToTracksAction(this),
239 new SplitTracksToLayersAction(this));
240
241 if (isExpertMode && expert.stream().anyMatch(Action::isEnabled)) {
242 entries.add(SeparatorLayerAction.INSTANCE);
243 expert.stream().filter(Action::isEnabled).forEach(entries::add);
244 }
245
246 entries.add(SeparatorLayerAction.INSTANCE);
247 entries.add(new LayerListPopup.InfoAction(this));
248 return entries.toArray(new Action[0]);
249 }
250
251 /**
252 * Determines if data is attached to a local file.
253 * @return {@code true} if data is attached to a local file, {@code false} otherwise
254 */
255 public boolean isLocalFile() {
256 return isLocalFile;
257 }
258
259 @Override
260 public String getToolTipText() {
261 StringBuilder info = new StringBuilder(48).append("<html>");
262
263 if (data.attr.containsKey(GpxConstants.META_NAME)) {
264 info.append(tr("Name: {0}", data.get(GpxConstants.META_NAME))).append("<br>");
265 }
266
267 if (data.attr.containsKey(GpxConstants.META_DESC)) {
268 info.append(tr("Description: {0}", data.get(GpxConstants.META_DESC))).append("<br>");
269 }
270
271 info.append(trn("{0} track", "{0} tracks", data.getTrackCount(), data.getTrackCount()))
272 .append(trn(" ({0} segment)", " ({0} segments)", data.getTrackSegsCount(), data.getTrackSegsCount()))
273 .append(", ")
274 .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size()))
275 .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size())).append("<br>")
276 .append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length())))
277 .append("<br></html>");
278 return info.toString();
279 }
280
281 @Override
282 public boolean isMergable(Layer other) {
283 return other instanceof GpxLayer;
284 }
285
286 /**
287 * Shows/hides all tracks of a given date range by setting them to visible/invisible.
288 * @param fromDate The min date
289 * @param toDate The max date
290 * @param showWithoutDate Include tracks that don't have any date set..
291 */
292 public void filterTracksByDate(Date fromDate, Date toDate, boolean showWithoutDate) {
293 int i = 0;
294 long from = fromDate.getTime();
295 long to = toDate.getTime();
296 for (GpxTrack trk : data.getTracks()) {
297 Date[] t = GpxData.getMinMaxTimeForTrack(trk);
298
299 if (t == null) continue;
300 long tm = t[1].getTime();
301 trackVisibility[i] = (tm == 0 && showWithoutDate) || (from <= tm && tm <= to);
302 i++;
303 }
304 invalidate();
305 }
306
307 @Override
308 public void mergeFrom(Layer from) {
309 if (!(from instanceof GpxLayer))
310 throw new IllegalArgumentException("not a GpxLayer: " + from);
311 data.mergeFrom(((GpxLayer) from).data);
312 invalidate();
313 }
314
315 @Override
316 public void visitBoundingBox(BoundingXYVisitor v) {
317 v.visit(data.recalculateBounds());
318 }
319
320 @Override
321 public File getAssociatedFile() {
322 return data.storageFile;
323 }
324
325 @Override
326 public void setAssociatedFile(File file) {
327 data.storageFile = file;
328 }
329
330 @Override
331 public void projectionChanged(Projection oldValue, Projection newValue) {
332 if (newValue == null) return;
333 data.resetEastNorthCache();
334 }
335
336 @Override
337 public boolean isSavable() {
338 return true; // With GpxExporter
339 }
340
341 @Override
342 public boolean checkSaveConditions() {
343 return data != null;
344 }
345
346 @Override
347 public File createAndOpenSaveFileChooser() {
348 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save GPX file"), GpxImporter.getFileFilter());
349 }
350
351 @Override
352 public LayerPositionStrategy getDefaultLayerPosition() {
353 return LayerPositionStrategy.AFTER_LAST_DATA_LAYER;
354 }
355
356 @Override
357 public void paint(Graphics2D g, MapView mv, Bounds bbox) {
358 // unused - we use a painter so this is not called.
359 }
360
361 @Override
362 protected LayerPainter createMapViewPainter(MapViewEvent event) {
363 return new GpxDrawHelper(this);
364 }
365
366 /**
367 * Action to merge tracks into a single segmented track
368 *
369 * @since 13210
370 */
371 public static class CombineTracksToSegmentedTrackAction extends AbstractAction {
372 private final transient GpxLayer layer;
373
374 /**
375 * Create a new CombineTracksToSegmentedTrackAction
376 * @param layer The layer with the data to work on.
377 */
378 public CombineTracksToSegmentedTrackAction(GpxLayer layer) {
379 // FIXME: icon missing, create a new icon for this action
380 //new ImageProvider(..."gpx_tracks_to_segmented_track").getResource().attachImageIcon(this, true);
381 putValue(SHORT_DESCRIPTION, tr("Collect segments of all tracks and combine in a single track."));
382 putValue(NAME, tr("Combine tracks of this layer"));
383 this.layer = layer;
384 }
385
386 @Override
387 public void actionPerformed(ActionEvent e) {
388 layer.data.combineTracksToSegmentedTrack();
389 layer.invalidate();
390 }
391
392 @Override
393 public boolean isEnabled() {
394 return layer.data.getTrackCount() > 1;
395 }
396 }
397
398 /**
399 * Action to split track segments into a multiple tracks with one segment each
400 *
401 * @since 13210
402 */
403 public static class SplitTrackSegementsToTracksAction extends AbstractAction {
404 private final transient GpxLayer layer;
405
406 /**
407 * Create a new SplitTrackSegementsToTracksAction
408 * @param layer The layer with the data to work on.
409 */
410 public SplitTrackSegementsToTracksAction(GpxLayer layer) {
411 // FIXME: icon missing, create a new icon for this action
412 //new ImageProvider(..."gpx_segmented_track_to_tracks").getResource().attachImageIcon(this, true);
413 putValue(SHORT_DESCRIPTION, tr("Split multiple track segments of one track into multiple tracks."));
414 putValue(NAME, tr("Split track segments to tracks"));
415 this.layer = layer;
416 }
417
418 @Override
419 public void actionPerformed(ActionEvent e) {
420 layer.data.splitTrackSegmentsToTracks();
421 layer.invalidate();
422 }
423
424 @Override
425 public boolean isEnabled() {
426 return layer.data.getTrackSegsCount() > layer.data.getTrackCount();
427 }
428 }
429
430 /**
431 * Action to split tracks of one gpx layer into multiple gpx layers,
432 * the result is one GPX track per gpx layer.
433 *
434 * @since 13210
435 */
436 public static class SplitTracksToLayersAction extends AbstractAction {
437 private final transient GpxLayer layer;
438
439 /**
440 * Create a new SplitTrackSegementsToTracksAction
441 * @param layer The layer with the data to work on.
442 */
443 public SplitTracksToLayersAction(GpxLayer layer) {
444 // FIXME: icon missing, create a new icon for this action
445 //new ImageProvider(..."gpx_split_tracks_to_layers").getResource().attachImageIcon(this, true);
446 putValue(SHORT_DESCRIPTION, tr("Split the tracks of this layer to one new layer each."));
447 putValue(NAME, tr("Split tracks to new layers"));
448 this.layer = layer;
449 }
450
451 @Override
452 public void actionPerformed(ActionEvent e) {
453 layer.data.splitTracksToLayers();
454 // layer is not modified by this action
455 }
456
457 @Override
458 public boolean isEnabled() {
459 return layer.data.getTrackCount() > 1;
460 }
461 }
462
463 @Override
464 public void expertChanged(boolean isExpert) {
465 this.isExpertMode = isExpert;
466 }
467}
Note: See TracBrowser for help on using the repository browser.