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