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

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

SonarQube - fix minor code issues

  • 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>td { padding: 4px 16px; }</style></head><body>");
151
152 if (data.attr.containsKey("name")) {
153 info.append(tr("Name: {0}", data.get(GpxConstants.META_NAME))).append("<br>");
154 }
155
156 if (data.attr.containsKey("desc")) {
157 info.append(tr("Description: {0}", data.get(GpxConstants.META_DESC))).append("<br>");
158 }
159
160 if (!data.getTracks().isEmpty()) {
161 info.append("<table><thead align='center'><tr><td colspan='5'>")
162 .append(trn("{0} track, {1} track segments", "{0} tracks, {1} track segments",
163 data.getTrackCount(), data.getTrackCount(),
164 data.getTrackSegsCount(), data.getTrackSegsCount()))
165 .append("</td></tr><tr align='center'><td>").append(tr("Name"))
166 .append("</td><td>").append(tr("Description"))
167 .append("</td><td>").append(tr("Timespan"))
168 .append("</td><td>").append(tr("Length"))
169 .append("</td><td>").append(tr("Number of<br/>Segments"))
170 .append("</td><td>").append(tr("URL"))
171 .append("</td></tr></thead>");
172
173 for (GpxTrack trk : data.getTracks()) {
174 info.append("<tr><td>");
175 if (trk.getAttributes().containsKey(GpxConstants.GPX_NAME)) {
176 info.append(trk.get(GpxConstants.GPX_NAME));
177 }
178 info.append("</td><td>");
179 if (trk.getAttributes().containsKey(GpxConstants.GPX_DESC)) {
180 info.append(' ').append(trk.get(GpxConstants.GPX_DESC));
181 }
182 info.append("</td><td>");
183 info.append(getTimespanForTrack(trk));
184 info.append("</td><td>");
185 info.append(SystemOfMeasurement.getSystemOfMeasurement().getDistText(trk.length()));
186 info.append("</td><td>");
187 info.append(trk.getSegments().size());
188 info.append("</td><td>");
189 if (trk.getAttributes().containsKey("url")) {
190 info.append(trk.get("url"));
191 }
192 info.append("</td></tr>");
193 }
194 info.append("</table><br><br>");
195 }
196
197 info.append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length()))).append("<br>")
198 .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size()))
199 .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size()))
200 .append("<br></body></html>");
201
202 final JScrollPane sp = new JScrollPane(new HtmlPanel(info.toString()));
203 sp.setPreferredSize(new Dimension(sp.getPreferredSize().width+20, 370));
204 SwingUtilities.invokeLater(() -> sp.getVerticalScrollBar().setValue(0));
205 return sp;
206 }
207
208 @Override
209 public boolean isInfoResizable() {
210 return true;
211 }
212
213 @Override
214 public Action[] getMenuEntries() {
215 List<Action> entries = new ArrayList<>(Arrays.asList(
216 LayerListDialog.getInstance().createShowHideLayerAction(),
217 LayerListDialog.getInstance().createDeleteLayerAction(),
218 LayerListDialog.getInstance().createMergeLayerAction(this),
219 SeparatorLayerAction.INSTANCE,
220 new LayerSaveAction(this),
221 new LayerSaveAsAction(this),
222 new CustomizeColor(this),
223 new CustomizeDrawingAction(this),
224 new ImportImagesAction(this),
225 new ImportAudioAction(this),
226 new MarkersFromNamedPointsAction(this),
227 new ConvertToDataLayerAction.FromGpxLayer(this),
228 new DownloadAlongTrackAction(data),
229 new DownloadWmsAlongTrackAction(data),
230 SeparatorLayerAction.INSTANCE,
231 new ChooseTrackVisibilityAction(this),
232 new RenameLayerAction(getAssociatedFile(), this)));
233
234 List<Action> expert = Arrays.asList(
235 new CombineTracksToSegmentedTrackAction(this),
236 new SplitTrackSegementsToTracksAction(this),
237 new SplitTracksToLayersAction(this));
238
239 if (isExpertMode && expert.stream().anyMatch(Action::isEnabled)) {
240 entries.add(SeparatorLayerAction.INSTANCE);
241 expert.stream().filter(Action::isEnabled).forEach(entries::add);
242 }
243
244 entries.add(SeparatorLayerAction.INSTANCE);
245 entries.add(new LayerListPopup.InfoAction(this));
246 return entries.toArray(new Action[0]);
247 }
248
249 /**
250 * Determines if data is attached to a local file.
251 * @return {@code true} if data is attached to a local file, {@code false} otherwise
252 */
253 public boolean isLocalFile() {
254 return isLocalFile;
255 }
256
257 @Override
258 public String getToolTipText() {
259 StringBuilder info = new StringBuilder(48).append("<html>");
260
261 if (data.attr.containsKey(GpxConstants.META_NAME)) {
262 info.append(tr("Name: {0}", data.get(GpxConstants.META_NAME))).append("<br>");
263 }
264
265 if (data.attr.containsKey(GpxConstants.META_DESC)) {
266 info.append(tr("Description: {0}", data.get(GpxConstants.META_DESC))).append("<br>");
267 }
268
269 info.append(trn("{0} track", "{0} tracks", data.getTrackCount(), data.getTrackCount()))
270 .append(trn(" ({0} segment)", " ({0} segments)", data.getTrackSegsCount(), data.getTrackSegsCount()))
271 .append(", ")
272 .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size()))
273 .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size())).append("<br>")
274 .append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length())))
275 .append("<br></html>");
276 return info.toString();
277 }
278
279 @Override
280 public boolean isMergable(Layer other) {
281 return other instanceof GpxLayer;
282 }
283
284 /**
285 * Shows/hides all tracks of a given date range by setting them to visible/invisible.
286 * @param fromDate The min date
287 * @param toDate The max date
288 * @param showWithoutDate Include tracks that don't have any date set..
289 */
290 public void filterTracksByDate(Date fromDate, Date toDate, boolean showWithoutDate) {
291 int i = 0;
292 long from = fromDate.getTime();
293 long to = toDate.getTime();
294 for (GpxTrack trk : data.getTracks()) {
295 Date[] t = GpxData.getMinMaxTimeForTrack(trk);
296
297 if (t == null) continue;
298 long tm = t[1].getTime();
299 trackVisibility[i] = (tm == 0 && showWithoutDate) || (from <= tm && tm <= to);
300 i++;
301 }
302 invalidate();
303 }
304
305 @Override
306 public void mergeFrom(Layer from) {
307 if (!(from instanceof GpxLayer))
308 throw new IllegalArgumentException("not a GpxLayer: " + from);
309 data.mergeFrom(((GpxLayer) from).data);
310 invalidate();
311 }
312
313 @Override
314 public void visitBoundingBox(BoundingXYVisitor v) {
315 v.visit(data.recalculateBounds());
316 }
317
318 @Override
319 public File getAssociatedFile() {
320 return data.storageFile;
321 }
322
323 @Override
324 public void setAssociatedFile(File file) {
325 data.storageFile = file;
326 }
327
328 @Override
329 public void projectionChanged(Projection oldValue, Projection newValue) {
330 if (newValue == null) return;
331 data.resetEastNorthCache();
332 }
333
334 @Override
335 public boolean isSavable() {
336 return true; // With GpxExporter
337 }
338
339 @Override
340 public boolean checkSaveConditions() {
341 return data != null;
342 }
343
344 @Override
345 public File createAndOpenSaveFileChooser() {
346 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save GPX file"), GpxImporter.getFileFilter());
347 }
348
349 @Override
350 public LayerPositionStrategy getDefaultLayerPosition() {
351 return LayerPositionStrategy.AFTER_LAST_DATA_LAYER;
352 }
353
354 @Override
355 public void paint(Graphics2D g, MapView mv, Bounds bbox) {
356 // unused - we use a painter so this is not called.
357 }
358
359 @Override
360 protected LayerPainter createMapViewPainter(MapViewEvent event) {
361 return new GpxDrawHelper(this);
362 }
363
364 /**
365 * Action to merge tracks into a single segmented track
366 *
367 * @since 13210
368 */
369 public static class CombineTracksToSegmentedTrackAction extends AbstractAction {
370 private final transient GpxLayer layer;
371
372 /**
373 * Create a new CombineTracksToSegmentedTrackAction
374 * @param layer The layer with the data to work on.
375 */
376 public CombineTracksToSegmentedTrackAction(GpxLayer layer) {
377 // FIXME: icon missing, create a new icon for this action
378 //new ImageProvider(..."gpx_tracks_to_segmented_track").getResource().attachImageIcon(this, true);
379 putValue(SHORT_DESCRIPTION, tr("Collect segments of all tracks and combine in a single track."));
380 putValue(NAME, tr("Combine tracks of this layer"));
381 this.layer = layer;
382 }
383
384 @Override
385 public void actionPerformed(ActionEvent e) {
386 layer.data.combineTracksToSegmentedTrack();
387 layer.invalidate();
388 }
389
390 @Override
391 public boolean isEnabled() {
392 return layer.data.getTrackCount() > 1;
393 }
394 }
395
396 /**
397 * Action to split track segments into a multiple tracks with one segment each
398 *
399 * @since 13210
400 */
401 public static class SplitTrackSegementsToTracksAction extends AbstractAction {
402 private final transient GpxLayer layer;
403
404 /**
405 * Create a new SplitTrackSegementsToTracksAction
406 * @param layer The layer with the data to work on.
407 */
408 public SplitTrackSegementsToTracksAction(GpxLayer layer) {
409 // FIXME: icon missing, create a new icon for this action
410 //new ImageProvider(..."gpx_segmented_track_to_tracks").getResource().attachImageIcon(this, true);
411 putValue(SHORT_DESCRIPTION, tr("Split multiple track segments of one track into multiple tracks."));
412 putValue(NAME, tr("Split track segments to tracks"));
413 this.layer = layer;
414 }
415
416 @Override
417 public void actionPerformed(ActionEvent e) {
418 layer.data.splitTrackSegmentsToTracks();
419 layer.invalidate();
420 }
421
422 @Override
423 public boolean isEnabled() {
424 return layer.data.getTrackSegsCount() > layer.data.getTrackCount();
425 }
426 }
427
428 /**
429 * Action to split tracks of one gpx layer into multiple gpx layers,
430 * the result is one GPX track per gpx layer.
431 *
432 * @since 13210
433 */
434 public static class SplitTracksToLayersAction extends AbstractAction {
435 private final transient GpxLayer layer;
436
437 /**
438 * Create a new SplitTrackSegementsToTracksAction
439 * @param layer The layer with the data to work on.
440 */
441 public SplitTracksToLayersAction(GpxLayer layer) {
442 // FIXME: icon missing, create a new icon for this action
443 //new ImageProvider(..."gpx_split_tracks_to_layers").getResource().attachImageIcon(this, true);
444 putValue(SHORT_DESCRIPTION, tr("Split the tracks of this layer to one new layer each."));
445 putValue(NAME, tr("Split tracks to new layers"));
446 this.layer = layer;
447 }
448
449 @Override
450 public void actionPerformed(ActionEvent e) {
451 layer.data.splitTracksToLayers();
452 // layer is not modified by this action
453 }
454
455 @Override
456 public boolean isEnabled() {
457 return layer.data.getTrackCount() > 1;
458 }
459 }
460
461 @Override
462 public void expertChanged(boolean isExpert) {
463 this.isExpertMode = isExpert;
464 }
465}
Note: See TracBrowser for help on using the repository browser.