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

Last change on this file was 18287, checked in by Don-vip, 2 years ago

fix #20913 - fix handling of GPX files in sessions (patch by Bjoeni)

  • revert r17659 (except for unit tests) - see #20233
    • don't save GPX track and marker colors in session file anymore, but in the GPX files as extensions (like before)
    • don't ask to save unmodified GPX file
    • don't override global color settings when individual track doesn't have a color
  • ask user to save changes to GPX file when any drawing settings or marker colors have changed (currently only happens for track colors)
  • save marker color values to session even when corresponding GPX layer has already been deleted
  • save alpha values for GPX marker colors
  • added explanation to the "overwrite GPX file" dialog
  • inform user if not all files referenced by the session file are saved yet
  • allow user to save all files that are not included in the *.jos/*.joz but are only referenced in the session file
  • display * next to GPX layers that need to be saved (move isDirty() logic from OsmDataLayer to AbstractModifiableLayer)
  • Property svn:eol-style set to native
File size: 23.1 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.Color;
8import java.awt.Dimension;
9import java.awt.Graphics2D;
10import java.awt.event.ActionEvent;
11import java.io.File;
12import java.time.Instant;
13import java.util.ArrayList;
14import java.util.Arrays;
15import java.util.Collections;
16import java.util.List;
17import java.util.NoSuchElementException;
18import java.util.stream.Collectors;
19
20import javax.swing.AbstractAction;
21import javax.swing.Action;
22import javax.swing.Icon;
23import javax.swing.JScrollPane;
24import javax.swing.SwingUtilities;
25
26import org.openstreetmap.josm.actions.AutoScaleAction;
27import org.openstreetmap.josm.actions.ExpertToggleAction;
28import org.openstreetmap.josm.actions.ExpertToggleAction.ExpertModeChangeListener;
29import org.openstreetmap.josm.actions.RenameLayerAction;
30import org.openstreetmap.josm.actions.SaveActionBase;
31import org.openstreetmap.josm.data.Bounds;
32import org.openstreetmap.josm.data.Data;
33import org.openstreetmap.josm.data.SystemOfMeasurement;
34import org.openstreetmap.josm.data.gpx.GpxConstants;
35import org.openstreetmap.josm.data.gpx.GpxData;
36import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeEvent;
37import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener;
38import org.openstreetmap.josm.data.gpx.GpxDataContainer;
39import org.openstreetmap.josm.data.gpx.IGpxTrack;
40import org.openstreetmap.josm.data.gpx.IGpxTrackSegment;
41import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
42import org.openstreetmap.josm.data.projection.Projection;
43import org.openstreetmap.josm.gui.MainApplication;
44import org.openstreetmap.josm.gui.MapView;
45import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
46import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
47import org.openstreetmap.josm.gui.io.importexport.GpxImporter;
48import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToMarkerLayer;
49import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToNextMarker;
50import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToPreviousMarker;
51import org.openstreetmap.josm.gui.layer.gpx.ChooseTrackVisibilityAction;
52import org.openstreetmap.josm.gui.layer.gpx.ConvertFromGpxLayerAction;
53import org.openstreetmap.josm.gui.layer.gpx.CustomizeDrawingAction;
54import org.openstreetmap.josm.gui.layer.gpx.DownloadAlongTrackAction;
55import org.openstreetmap.josm.gui.layer.gpx.DownloadWmsAlongTrackAction;
56import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper;
57import org.openstreetmap.josm.gui.layer.gpx.ImportAudioAction;
58import org.openstreetmap.josm.gui.layer.gpx.ImportImagesAction;
59import org.openstreetmap.josm.gui.layer.gpx.MarkersFromNamedPointsAction;
60import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
61import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
62import org.openstreetmap.josm.gui.util.GuiHelper;
63import org.openstreetmap.josm.gui.widgets.HtmlPanel;
64import org.openstreetmap.josm.tools.ImageProvider;
65import org.openstreetmap.josm.tools.Logging;
66import org.openstreetmap.josm.tools.Utils;
67import org.openstreetmap.josm.tools.date.Interval;
68
69/**
70 * A layer that displays data from a Gpx file / the OSM gpx downloads.
71 */
72public class GpxLayer extends AbstractModifiableLayer implements GpxDataContainer, ExpertModeChangeListener, JumpToMarkerLayer {
73
74 /** GPX data */
75 public GpxData data;
76 private boolean isLocalFile;
77 private boolean isExpertMode;
78
79 /**
80 * used by {@link ChooseTrackVisibilityAction} to determine which tracks to show/hide
81 *
82 * Call {@link #invalidate()} after each change!
83 *
84 * TODO: Make it private, make it respond to track changes.
85 */
86 public boolean[] trackVisibility = new boolean[0];
87 /**
88 * Added as field to be kept as reference.
89 */
90 private final GpxDataChangeListener dataChangeListener = new GpxDataChangeListener() {
91 @Override
92 public void gpxDataChanged(GpxDataChangeEvent e) {
93 invalidate();
94 }
95
96 @Override
97 public void modifiedStateChanged(boolean modified) {
98 GuiHelper.runInEDT(() -> propertyChangeSupport.firePropertyChange(REQUIRES_SAVE_TO_DISK_PROP, !modified, modified));
99 }
100 };
101 /**
102 * The MarkerLayer imported from the same file.
103 */
104 private MarkerLayer linkedMarkerLayer;
105
106 /**
107 * Current segment for {@link JumpToMarkerLayer}.
108 */
109 private IGpxTrackSegment currentSegment;
110
111 /**
112 * Constructs a new {@code GpxLayer} without name.
113 * @param d GPX data
114 */
115 public GpxLayer(GpxData d) {
116 this(d, null, false);
117 }
118
119 /**
120 * Constructs a new {@code GpxLayer} with a given name.
121 * @param d GPX data
122 * @param name layer name
123 */
124 public GpxLayer(GpxData d, String name) {
125 this(d, name, false);
126 }
127
128 /**
129 * Constructs a new {@code GpxLayer} with a given name, that can be attached to a local file.
130 * @param d GPX data
131 * @param name layer name
132 * @param isLocal whether data is attached to a local file
133 */
134 public GpxLayer(GpxData d, String name, boolean isLocal) {
135 super(name);
136 data = d;
137 data.addWeakChangeListener(dataChangeListener);
138 trackVisibility = new boolean[data.getTracks().size()];
139 Arrays.fill(trackVisibility, true);
140 isLocalFile = isLocal;
141 ExpertToggleAction.addExpertModeChangeListener(this, true);
142 }
143
144 @Override
145 public Color getColor() {
146 if (data == null)
147 return null;
148 Color[] c = data.getTracks().stream().map(t -> t.getColor()).distinct().toArray(Color[]::new);
149 return c.length == 1 ? c[0] : null; //only return if exactly one distinct color present
150 }
151
152 @Override
153 public void setColor(Color color) {
154 data.beginUpdate();
155 for (IGpxTrack trk : data.getTracks()) {
156 trk.setColor(color);
157 }
158 GPXSettingsPanel.putLayerPrefLocal(this, "colormode", "0");
159 data.endUpdate();
160 }
161
162 @Override
163 public boolean hasColor() {
164 return data != null;
165 }
166
167 /**
168 * Returns a human readable string that shows the timespan of the given track
169 * @param trk The GPX track for which timespan is displayed
170 * @return The timespan as a string
171 */
172 public static String getTimespanForTrack(IGpxTrack trk) {
173 return GpxData.getMinMaxTimeForTrack(trk).map(Interval::format).orElse("");
174 }
175
176 @Override
177 public Icon getIcon() {
178 return ImageProvider.get("layer", "gpx_small");
179 }
180
181 @Override
182 public Object getInfoComponent() {
183 StringBuilder info = new StringBuilder(128)
184 .append("<html><head><style>td { padding: 4px 16px; }</style></head><body>");
185
186 if (data != null) {
187 fillDataInfoComponent(info);
188 }
189
190 info.append("<br></body></html>");
191
192 final JScrollPane sp = new JScrollPane(new HtmlPanel(info.toString()));
193 sp.setPreferredSize(new Dimension(sp.getPreferredSize().width+20, 370));
194 SwingUtilities.invokeLater(() -> sp.getVerticalScrollBar().setValue(0));
195 return sp;
196 }
197
198 private void fillDataInfoComponent(StringBuilder info) {
199 if (data.attr.containsKey("name")) {
200 info.append(tr("Name: {0}", data.get(GpxConstants.META_NAME))).append("<br>");
201 }
202
203 if (data.attr.containsKey("desc")) {
204 info.append(tr("Description: {0}", data.get(GpxConstants.META_DESC))).append("<br>");
205 }
206
207 if (!Utils.isStripEmpty(data.creator)) {
208 info.append(tr("Creator: {0}", data.creator)).append("<br>");
209 }
210
211 if (!data.getTracks().isEmpty()) {
212 info.append("<table><thead align='center'><tr><td colspan='5'>")
213 .append(trn("{0} track, {1} track segments", "{0} tracks, {1} track segments",
214 data.getTrackCount(), data.getTrackCount(),
215 data.getTrackSegsCount(), data.getTrackSegsCount()))
216 .append("</td></tr><tr align='center'><td>").append(tr("Name"))
217 .append("</td><td>").append(tr("Description"))
218 .append("</td><td>").append(tr("Timespan"))
219 .append("</td><td>").append(tr("Length"))
220 .append("</td><td>").append(tr("Number of<br/>Segments"))
221 .append("</td><td>").append(tr("URL"))
222 .append("</td></tr></thead>");
223
224 for (IGpxTrack trk : data.getTracks()) {
225 info.append("<tr><td>");
226 info.append(trk.getAttributes().getOrDefault(GpxConstants.GPX_NAME, ""));
227 info.append("</td><td>");
228 info.append(trk.getAttributes().getOrDefault(GpxConstants.GPX_DESC, ""));
229 info.append("</td><td>");
230 info.append(getTimespanForTrack(trk));
231 info.append("</td><td>");
232 info.append(SystemOfMeasurement.getSystemOfMeasurement().getDistText(trk.length()));
233 info.append("</td><td>");
234 info.append(trk.getSegments().size());
235 info.append("</td><td>");
236 if (trk.getAttributes().containsKey("url")) {
237 info.append(trk.get("url"));
238 }
239 info.append("</td></tr>");
240 }
241 info.append("</table><br><br>");
242 }
243
244 info.append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length()))).append("<br>")
245 .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size()))
246 .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size()));
247 }
248
249 @Override
250 public boolean isInfoResizable() {
251 return true;
252 }
253
254 @Override
255 public Action[] getMenuEntries() {
256 JumpToNextMarker jumpToNext = new JumpToNextMarker(this);
257 jumpToNext.putValue(Action.NAME, tr("Jump to next segment"));
258 JumpToPreviousMarker jumpToPrevious = new JumpToPreviousMarker(this);
259 jumpToPrevious.putValue(Action.NAME, tr("Jump to previous segment"));
260 List<Action> entries = new ArrayList<>(Arrays.asList(
261 LayerListDialog.getInstance().createShowHideLayerAction(),
262 LayerListDialog.getInstance().createDeleteLayerAction(),
263 MainApplication.getMenu().autoScaleActions.get(AutoScaleAction.AutoScaleMode.LAYER),
264 LayerListDialog.getInstance().createMergeLayerAction(this),
265 SeparatorLayerAction.INSTANCE,
266 new LayerSaveAction(this),
267 new LayerSaveAsAction(this),
268 new CustomizeColor(this),
269 new CustomizeDrawingAction(this),
270 new ImportImagesAction(this),
271 new ImportAudioAction(this),
272 new MarkersFromNamedPointsAction(this),
273 jumpToNext,
274 jumpToPrevious,
275 new ConvertFromGpxLayerAction(this),
276 new DownloadAlongTrackAction(Collections.singleton(data)),
277 new DownloadWmsAlongTrackAction(data),
278 SeparatorLayerAction.INSTANCE,
279 new ChooseTrackVisibilityAction(this),
280 new RenameLayerAction(getAssociatedFile(), this)));
281
282 List<Action> expert = Arrays.asList(
283 new CombineTracksToSegmentedTrackAction(this),
284 new SplitTrackSegmentsToTracksAction(this),
285 new SplitTracksToLayersAction(this));
286
287 if (isExpertMode && expert.stream().anyMatch(Action::isEnabled)) {
288 entries.add(SeparatorLayerAction.INSTANCE);
289 expert.stream().filter(Action::isEnabled).forEach(entries::add);
290 }
291
292 entries.add(SeparatorLayerAction.INSTANCE);
293 entries.add(new LayerListPopup.InfoAction(this));
294 return entries.toArray(new Action[0]);
295 }
296
297 /**
298 * Determines if data is attached to a local file.
299 * @return {@code true} if data is attached to a local file, {@code false} otherwise
300 */
301 public boolean isLocalFile() {
302 return isLocalFile;
303 }
304
305 @Override
306 public String getToolTipText() {
307 StringBuilder info = new StringBuilder(48).append("<html>");
308
309 if (data != null) {
310 fillDataToolTipText(info);
311 }
312
313 info.append("<br></html>");
314
315 return info.toString();
316 }
317
318 private void fillDataToolTipText(StringBuilder info) {
319 if (data.attr.containsKey(GpxConstants.META_NAME)) {
320 info.append(tr("Name: {0}", data.get(GpxConstants.META_NAME))).append("<br>");
321 }
322
323 if (data.attr.containsKey(GpxConstants.META_DESC)) {
324 info.append(tr("Description: {0}", data.get(GpxConstants.META_DESC))).append("<br>");
325 }
326
327 info.append(trn("{0} track", "{0} tracks", data.getTrackCount(), data.getTrackCount()))
328 .append(trn(" ({0} segment)", " ({0} segments)", data.getTrackSegsCount(), data.getTrackSegsCount()))
329 .append(", ")
330 .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size()))
331 .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size())).append("<br>")
332 .append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length())));
333
334 if (Logging.isDebugEnabled() && !data.getLayerPrefs().isEmpty()) {
335 info.append("<br><br>")
336 .append(data.getLayerPrefs().entrySet().stream()
337 .map(e -> e.getKey() + "=" + e.getValue())
338 .collect(Collectors.joining("<br>")));
339 }
340 }
341
342 @Override
343 public boolean isMergable(Layer other) {
344 return data != null && other instanceof GpxLayer;
345 }
346
347 /**
348 * Shows/hides all tracks of a given date range by setting them to visible/invisible.
349 * @param fromDate The min date
350 * @param toDate The max date
351 * @param showWithoutDate Include tracks that don't have any date set..
352 */
353 public void filterTracksByDate(Instant fromDate, Instant toDate, boolean showWithoutDate) {
354 if (data == null)
355 return;
356 int i = 0;
357 long from = fromDate.toEpochMilli();
358 long to = toDate.toEpochMilli();
359 for (IGpxTrack trk : data.getTracks()) {
360 Interval t = GpxData.getMinMaxTimeForTrack(trk).orElse(null);
361
362 if (t == null) continue;
363 long tm = t.getEnd().toEpochMilli();
364 trackVisibility[i] = (tm == 0 && showWithoutDate) || (from <= tm && tm <= to);
365 i++;
366 }
367 invalidate();
368 }
369
370 @Override
371 public void mergeFrom(Layer from) {
372 if (!(from instanceof GpxLayer))
373 throw new IllegalArgumentException("not a GpxLayer: " + from);
374 mergeFrom((GpxLayer) from, false, false);
375 }
376
377 /**
378 * Merges the given GpxLayer into this layer and can remove timewise overlapping parts of the given track
379 * @param from The GpxLayer that gets merged into this one
380 * @param cutOverlapping whether overlapping parts of the given track should be removed
381 * @param connect whether the tracks should be connected on cuts
382 * @since 14338
383 */
384 public void mergeFrom(GpxLayer from, boolean cutOverlapping, boolean connect) {
385 data.mergeFrom(from.data, cutOverlapping, connect);
386 invalidate();
387 }
388
389 @Override
390 public String getLabel() {
391 return isDirty() ? super.getLabel() + ' ' + IS_DIRTY_SYMBOL : super.getLabel();
392 }
393
394 @Override
395 public void visitBoundingBox(BoundingXYVisitor v) {
396 if (data != null) {
397 v.visit(data.recalculateBounds());
398 }
399 }
400
401 @Override
402 public File getAssociatedFile() {
403 return data != null ? data.storageFile : null;
404 }
405
406 @Override
407 public void setAssociatedFile(File file) {
408 data.storageFile = file;
409 }
410
411 /**
412 * Returns the linked MarkerLayer.
413 * @return the linked MarkerLayer (imported from the same file)
414 * @since 15496
415 */
416 public MarkerLayer getLinkedMarkerLayer() {
417 return linkedMarkerLayer;
418 }
419
420 /**
421 * Sets the linked MarkerLayer.
422 * @param linkedMarkerLayer the linked MarkerLayer
423 * @since 15496
424 */
425 public void setLinkedMarkerLayer(MarkerLayer linkedMarkerLayer) {
426 this.linkedMarkerLayer = linkedMarkerLayer;
427 }
428
429 @Override
430 public void projectionChanged(Projection oldValue, Projection newValue) {
431 if (newValue == null || data == null) return;
432 data.resetEastNorthCache();
433 }
434
435 @Override
436 public boolean isSavable() {
437 return data != null; // With GpxExporter
438 }
439
440 @Override
441 public boolean checkSaveConditions() {
442 return data != null;
443 }
444
445 @Override
446 public File createAndOpenSaveFileChooser() {
447 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save GPX file"), GpxImporter.getFileFilter());
448 }
449
450 @Override
451 public LayerPositionStrategy getDefaultLayerPosition() {
452 return LayerPositionStrategy.AFTER_LAST_DATA_LAYER;
453 }
454
455 @Override
456 public void paint(Graphics2D g, MapView mv, Bounds bbox) {
457 // unused - we use a painter so this is not called.
458 }
459
460 @Override
461 protected LayerPainter createMapViewPainter(MapViewEvent event) {
462 return new GpxDrawHelper(this);
463 }
464
465 /**
466 * Action to merge tracks into a single segmented track
467 *
468 * @since 13210
469 */
470 public static class CombineTracksToSegmentedTrackAction extends AbstractAction {
471 private final transient GpxLayer layer;
472
473 /**
474 * Create a new CombineTracksToSegmentedTrackAction
475 * @param layer The layer with the data to work on.
476 */
477 public CombineTracksToSegmentedTrackAction(GpxLayer layer) {
478 // FIXME: icon missing, create a new icon for this action
479 //new ImageProvider(..."gpx_tracks_to_segmented_track").getResource().attachImageIcon(this, true);
480 putValue(SHORT_DESCRIPTION, tr("Collect segments of all tracks and combine in a single track."));
481 putValue(NAME, tr("Combine tracks of this layer"));
482 this.layer = layer;
483 }
484
485 @Override
486 public void actionPerformed(ActionEvent e) {
487 layer.data.combineTracksToSegmentedTrack();
488 layer.invalidate();
489 }
490
491 @Override
492 public boolean isEnabled() {
493 return layer.data.getTrackCount() > 1;
494 }
495 }
496
497 /**
498 * Action to split track segments into a multiple tracks with one segment each
499 *
500 * @since 13210
501 */
502 public static class SplitTrackSegmentsToTracksAction extends AbstractAction {
503 private final transient GpxLayer layer;
504
505 /**
506 * Create a new SplitTrackSegmentsToTracksAction
507 * @param layer The layer with the data to work on.
508 */
509 public SplitTrackSegmentsToTracksAction(GpxLayer layer) {
510 // FIXME: icon missing, create a new icon for this action
511 //new ImageProvider(..."gpx_segmented_track_to_tracks").getResource().attachImageIcon(this, true);
512 putValue(SHORT_DESCRIPTION, tr("Split multiple track segments of one track into multiple tracks."));
513 putValue(NAME, tr("Split track segments to tracks"));
514 this.layer = layer;
515 }
516
517 @Override
518 public void actionPerformed(ActionEvent e) {
519 layer.data.splitTrackSegmentsToTracks(!layer.getName().isEmpty() ? layer.getName() : "GPX split result");
520 layer.invalidate();
521 }
522
523 @Override
524 public boolean isEnabled() {
525 return layer.data.getTrackSegsCount() > layer.data.getTrackCount();
526 }
527 }
528
529 /**
530 * Action to split tracks of one gpx layer into multiple gpx layers,
531 * the result is one GPX track per gpx layer.
532 *
533 * @since 13210
534 */
535 public static class SplitTracksToLayersAction extends AbstractAction {
536 private final transient GpxLayer layer;
537
538 /**
539 * Create a new SplitTrackSegmentsToTracksAction
540 * @param layer The layer with the data to work on.
541 */
542 public SplitTracksToLayersAction(GpxLayer layer) {
543 // FIXME: icon missing, create a new icon for this action
544 //new ImageProvider(..."gpx_split_tracks_to_layers").getResource().attachImageIcon(this, true);
545 putValue(SHORT_DESCRIPTION, tr("Split the tracks of this layer to one new layer each."));
546 putValue(NAME, tr("Split tracks to new layers"));
547 this.layer = layer;
548 }
549
550 @Override
551 public void actionPerformed(ActionEvent e) {
552 layer.data.splitTracksToLayers(!layer.getName().isEmpty() ? layer.getName() : "GPX split result");
553 // layer is not modified by this action
554 }
555
556 @Override
557 public boolean isEnabled() {
558 return layer.data.getTrackCount() > 1;
559 }
560 }
561
562 @Override
563 public void expertChanged(boolean isExpert) {
564 this.isExpertMode = isExpert;
565 }
566
567 @Override
568 public boolean isModified() {
569 return data != null && data.isModified();
570 }
571
572 @Override
573 public boolean requiresSaveToFile() {
574 return data != null && isModified() && (isLocalFile() || data.fromSession);
575 }
576
577 @Override
578 public void onPostSaveToFile() {
579 isLocalFile = true;
580 data.invalidate();
581 data.setModified(false);
582 }
583
584 @Override
585 public String getChangesetSourceTag() {
586 // no i18n for international values
587 return isLocalFile ? "survey" : null;
588 }
589
590 @Override
591 public Data getData() {
592 return data;
593 }
594
595 @Override
596 public GpxData getGpxData() {
597 return data;
598 }
599
600 /**
601 * Jump (move the viewport) to the next track segment.
602 */
603 @Override
604 public void jumpToNextMarker() {
605 if (data != null) {
606 jumpToNext(data.getTrackSegmentsStream().collect(Collectors.toList()));
607 }
608 }
609
610 /**
611 * Jump (move the viewport) to the previous track segment.
612 */
613 @Override
614 public void jumpToPreviousMarker() {
615 if (data != null) {
616 List<IGpxTrackSegment> segments = data.getTrackSegmentsStream().collect(Collectors.toList());
617 Collections.reverse(segments);
618 jumpToNext(segments);
619 }
620 }
621
622 private void jumpToNext(List<IGpxTrackSegment> segments) {
623 if (segments.isEmpty()) {
624 return;
625 } else if (currentSegment == null) {
626 currentSegment = segments.get(0);
627 MainApplication.getMap().mapView.zoomTo(currentSegment.getBounds());
628 } else {
629 try {
630 int index = segments.indexOf(currentSegment);
631 currentSegment = segments.listIterator(index + 1).next();
632 MainApplication.getMap().mapView.zoomTo(currentSegment.getBounds());
633 } catch (IndexOutOfBoundsException | NoSuchElementException ignore) {
634 Logging.trace(ignore);
635 }
636 }
637 }
638
639 @Override
640 public synchronized void destroy() {
641 if (linkedMarkerLayer != null && MainApplication.getLayerManager().containsLayer(linkedMarkerLayer)) {
642 linkedMarkerLayer.data.transferLayerPrefs(data.getLayerPrefs());
643 }
644 data.clear();
645 data = null;
646 super.destroy();
647 }
648}
Note: See TracBrowser for help on using the repository browser.