Index: trunk/src/org/openstreetmap/josm/actions/GpxExportAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/GpxExportAction.java	(revision 13208)
+++ trunk/src/org/openstreetmap/josm/actions/GpxExportAction.java	(revision 13210)
@@ -37,4 +37,22 @@
                 Shortcut.registerShortcut("file:exportgpx", tr("Export to GPX..."), KeyEvent.VK_E, Shortcut.CTRL));
         putValue("help", ht("/Action/GpxExport"));
+    }
+
+    /**
+     * Deferring constructor for child classes.
+     *
+     * @param name see {@code DiskAccessAction}
+     * @param iconName see {@code DiskAccessAction}
+     * @param tooltip see {@code DiskAccessAction}
+     * @param shortcut see {@code DiskAccessAction}
+     * @param register see {@code DiskAccessAction}
+     * @param toolbarId see {@code DiskAccessAction}
+     * @param installAdapters see {@code DiskAccessAction}
+     *
+     * @since 13210
+     */
+    protected GpxExportAction(String name, String iconName, String tooltip, Shortcut shortcut,
+            boolean register, String toolbarId, boolean installAdapters) {
+        super(name, iconName, tooltip, shortcut, register, toolbarId, installAdapters);
     }
 
Index: trunk/src/org/openstreetmap/josm/actions/relation/ExportRelationToGpxAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/relation/ExportRelationToGpxAction.java	(revision 13210)
+++ trunk/src/org/openstreetmap/josm/actions/relation/ExportRelationToGpxAction.java	(revision 13210)
@@ -0,0 +1,234 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions.relation;
+
+import static org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode.FROM_FIRST_MEMBER;
+import static org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode.TO_FILE;
+import static org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode.TO_LAYER;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Stack;
+
+import org.openstreetmap.josm.actions.GpxExportAction;
+import org.openstreetmap.josm.actions.OsmPrimitiveAction;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType;
+import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.SubclassFilteredCollection;
+
+/**
+ * Exports the current relation to a single GPX track,
+ * currently for type=route and type=superroute relations only.
+ *
+ * @since 13210
+ */
+public class ExportRelationToGpxAction extends GpxExportAction
+    implements OsmPrimitiveAction {
+
+    /** Enumeration of export variants */
+    public enum Mode {
+        /** concatenate members from first to last element */
+        FROM_FIRST_MEMBER,
+        /** concatenate members from last to first element */
+        FROM_LAST_MEMBER,
+        /** export to GPX layer and add to LayerManager */
+        TO_LAYER,
+        /** export to GPX file and open FileChooser */
+        TO_FILE
+    }
+
+    /** Mode of this ExportToGpxAction */
+    protected final EnumSet<Mode> mode;
+
+    /** Primitives this action works on */
+    protected Collection<Relation> relations = Collections.<Relation>emptySet();
+
+    /** Construct a new ExportRelationToGpxAction with default mode */
+    public ExportRelationToGpxAction() {
+        this(EnumSet.of(FROM_FIRST_MEMBER, TO_FILE));
+    }
+
+    /**
+     * Constructs a new {@code ExportRelationToGpxAction}
+     *
+     * @param mode which mode to use, see {@code ExportRelationToGpxAction.Mode}
+     */
+    public ExportRelationToGpxAction(EnumSet<Mode> mode) {
+        super(tr("{0} starting from {1} member",
+                 mode.contains(TO_FILE) ? tr("Export GPX file") : tr("Convert to GPX layer"),
+                 mode.contains(FROM_FIRST_MEMBER) ? tr("first") : tr("last")),
+              mode.contains(TO_FILE) ? "exportgpx" : "dialogs/layerlist",
+              tr("Flatten this relation to a single gpx track "+
+                 "recursively, starting with the {0} member(s), "+
+                 "successively continuing to the {1}.",
+                 mode.contains(FROM_FIRST_MEMBER) ? tr("first") : tr("last"),
+                 mode.contains(FROM_FIRST_MEMBER) ? tr("last") : tr("first")),
+              null, false, null, false);
+        putValue("help", ht("/Action/ExportRelationToGpx"));
+        this.mode = mode;
+    }
+
+    private static final class BidiIterableList {
+        private final List<RelationMember> l;
+
+        private BidiIterableList(List<RelationMember> l) {
+            this.l = l;
+        }
+
+        public Iterator<RelationMember> iterator() {
+            return l.iterator();
+        }
+
+        public Iterator<RelationMember> reverseIterator() {
+            ListIterator<RelationMember> li = l.listIterator(l.size());
+            return new Iterator<RelationMember>() {
+                @Override
+                public boolean hasNext() {
+                    return li.hasPrevious();
+                }
+
+                @Override
+                public RelationMember next() {
+                    return li.previous();
+                }
+
+                @Override
+                public void remove() {
+                    li.remove();
+                }
+            };
+        }
+    }
+
+    @Override
+    protected Layer getLayer() {
+        List<RelationMember> flat = new ArrayList<>();
+
+        List<RelationMember> init = new ArrayList<>();
+        relations.forEach(t -> init.add(new RelationMember("", t)));
+        BidiIterableList l = new BidiIterableList(init);
+
+        Stack<Iterator<RelationMember>> stack = new Stack<>();
+        stack.push(mode.contains(FROM_FIRST_MEMBER) ? l.iterator() : l.reverseIterator());
+
+        List<Relation> relsFound = new ArrayList<>();
+        do {
+            Iterator<RelationMember> i = stack.peek();
+            if (!i.hasNext())
+                stack.pop();
+            while (i.hasNext()) {
+                RelationMember m = i.next();
+                if (m.isRelation() && !m.getRelation().isIncomplete()) {
+                    l = new BidiIterableList(m.getRelation().getMembers());
+                    stack.push(mode.contains(FROM_FIRST_MEMBER) ? l.iterator() : l.reverseIterator());
+                    relsFound.add(m.getRelation());
+                    break;
+                }
+                if (m.isWay()) {
+                    flat.add(m);
+                }
+            }
+        } while (!stack.isEmpty());
+
+        GpxData gpxData = new GpxData();
+        String layerName = " (GPX export)";
+        long time = System.currentTimeMillis()-24*3600*1000;
+
+        if (!flat.isEmpty()) {
+            Map<String, Object> trkAttr = new HashMap<>();
+            Collection<Collection<WayPoint>> trk = new ArrayList<>();
+            List<WayPoint> trkseg = new ArrayList<>();
+            trk.add(trkseg);
+
+            //RelationSorter.sortMembersByConnectivity(defaultMembers); // preserve given order
+            List<WayConnectionType> wct = new WayConnectionTypeCalculator().updateLinks(flat);
+            final HashMap<String, Integer> names = new HashMap<>();
+            for (int i = 0; i < flat.size(); i++) {
+                if (!wct.get(i).isOnewayLoopBackwardPart) {
+                    if (!wct.get(i).direction.isRoundabout()) {
+                        if (!wct.get(i).linkPrev && !trkseg.isEmpty()) {
+                            gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr));
+                            trkAttr.clear();
+                            trk.clear();
+                            trkseg.clear();
+                            trk.add(trkseg);
+                        }
+                        if (trkAttr.isEmpty()/* && (!wct.get(i).linkNext || (i+1 == flat.size()))*/) {
+                            Relation r = Way.getParentRelations(Arrays.asList(flat.get(i).getWay()))
+                                    .stream().filter(relsFound::contains).findFirst().orElseGet(null);
+                            if (r != null)
+                                trkAttr.put("name", r.getName() != null ? r.getName() : r.getId());
+                            GpxData.ensureUniqueName(trkAttr, names);
+                        }
+                        List<Node> ln = flat.get(i).getWay().getNodes();
+                        if (wct.get(i).direction == WayConnectionType.Direction.BACKWARD)
+                            Collections.reverse(ln);
+                        for (Node n: ln) {
+                            trkseg.add(OsmDataLayer.nodeToWayPoint(n, time));
+                            time += 1000;
+                        }
+                    }
+                }
+            }
+            gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr));
+
+            String lprefix = relations.iterator().next().getName();
+            if (lprefix == null || relations.size() > 1)
+                lprefix = tr("Selected Relations");
+            layerName = lprefix + layerName;
+        }
+
+        //Relation debug = new Relation(); debug.setMembers(flat);
+        //MainApplication.getLayerManager().getEditDataSet().addPrimitive(debug);
+        return new GpxLayer(gpxData, layerName, true);
+    }
+
+    /**
+     *
+     * @param e the ActionEvent
+     */
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (mode.contains(TO_LAYER))
+            MainApplication.getLayerManager().addLayer(getLayer());
+        if (mode.contains(TO_FILE))
+            super.actionPerformed(e);
+    }
+
+    @Override
+    public void setPrimitives(Collection<? extends OsmPrimitive> primitives) {
+        relations = Collections.<Relation>emptySet();
+        if (primitives != null && !primitives.isEmpty()) {
+            relations = new SubclassFilteredCollection<>(primitives,
+                r -> r instanceof Relation && r.hasTag("type", Arrays.asList("route", "superroute")));
+        }
+        updateEnabledState();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(!relations.isEmpty());
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java	(revision 13208)
+++ trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java	(revision 13210)
@@ -5,13 +5,17 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.DoubleSummaryStatistics;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -22,4 +26,6 @@
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.gpx.GpxTrack.GpxTrackChangeListener;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
 import org.openstreetmap.josm.tools.ListenerList;
 import org.openstreetmap.josm.tools.ListeningCollection;
@@ -141,4 +147,21 @@
 
     /**
+     * Get Stream<> of track segments as introduced in Java 8.
+     * @return {@code Stream<GPXTrack>}
+     */
+    private synchronized Stream<GpxTrackSegment> getTrackSegmentsStream() {
+        return getTracks().stream().flatMap(trk -> trk.getSegments().stream());
+    }
+
+    /**
+     * Clear all tracks, empties the current privateTracks container,
+     * helper method for some gpx manipulations.
+     */
+    private synchronized void clearTracks() {
+        privateTracks.forEach(t -> t.removeListener(proxy));
+        privateTracks.clear();
+    }
+
+    /**
      * Add a new track
      * @param track The new track
@@ -165,4 +188,102 @@
         track.removeListener(proxy);
         fireInvalidate();
+    }
+
+    /**
+     * Combine tracks into a single, segmented track.
+     * The attributes of the first track are used, the rest discarded.
+     *
+     * @since 13210
+     */
+    public synchronized void combineTracksToSegmentedTrack() {
+        List<GpxTrackSegment> segs = getTrackSegmentsStream()
+                .collect(Collectors.toCollection(ArrayList<GpxTrackSegment>::new));
+        Map<String, Object> attrs = new HashMap<>(privateTracks.get(0).getAttributes());
+
+        // do not let the name grow if split / combine operations are called iteratively
+        attrs.put("name", attrs.get("name").toString().replaceFirst(" #\\d+$", ""));
+
+        clearTracks();
+        addTrack(new ImmutableGpxTrack(segs, attrs));
+    }
+
+    /**
+     * @param attrs attributes of/for an gpx track, written to if the name appeared previously in {@code counts}.
+     * @param counts a {@code HashMap} of previously seen names, associated with their count.
+     * @return the unique name for the gpx track.
+     *
+     * @since 13210
+     */
+    public static String ensureUniqueName(Map<String, Object> attrs, Map<String, Integer> counts) {
+        String name = attrs.getOrDefault("name", "GPX split result").toString();
+        Integer count = counts.getOrDefault(name, 0) + 1;
+        counts.put(name, count);
+
+        attrs.put("name", MessageFormat.format("{0}{1}", name, (count > 1) ? " #"+count : ""));
+        return attrs.get("name").toString();
+    }
+
+    /**
+     * Split tracks so that only single-segment tracks remain.
+     * Each segment will make up one individual track after this operation.
+     *
+     * @since 13210
+     */
+    public synchronized void splitTrackSegmentsToTracks() {
+        final HashMap<String, Integer> counts = new HashMap<>();
+
+        List<GpxTrack> trks = getTracks().stream()
+            .flatMap(trk -> {
+                return trk.getSegments().stream().map(seg -> {
+                    HashMap<String, Object> attrs = new HashMap<>(trk.getAttributes());
+                    ensureUniqueName(attrs, counts);
+                    return new ImmutableGpxTrack(Arrays.asList(seg), attrs);
+                });
+            })
+            .collect(Collectors.toCollection(ArrayList<GpxTrack>::new));
+
+        clearTracks();
+        trks.stream().forEachOrdered(trk -> addTrack(trk));
+    }
+
+    /**
+     * Split tracks into layers, the result is one layer for each track.
+     * If this layer currently has only one GpxTrack this is a no-operation.
+     *
+     * The new GpxLayers are added to the LayerManager, the original GpxLayer
+     * is untouched as to preserve potential route or wpt parts.
+     *
+     * @since 13210
+     */
+    public synchronized void splitTracksToLayers() {
+        final HashMap<String, Integer> counts = new HashMap<>();
+
+        getTracks().stream()
+            .filter(trk -> privateTracks.size() > 1)
+            .map(trk -> {
+                HashMap<String, Object> attrs = new HashMap<>(trk.getAttributes());
+                GpxData d = new GpxData();
+                d.addTrack(trk);
+                return new GpxLayer(d, ensureUniqueName(attrs, counts)); })
+            .forEachOrdered(layer -> MainApplication.getLayerManager().addLayer(layer));
+    }
+
+    /**
+     * Replies the current number of tracks in this GpxData
+     * @return track count
+     * @since 13210
+     */
+    public synchronized int getTrackCount() {
+        return privateTracks.size();
+    }
+
+    /**
+     * Replies the accumulated total of all track segments,
+     * the sum of segment counts for each track present.
+     * @return track segments count
+     * @since 13210
+     */
+    public synchronized int getTrackSegsCount() {
+        return privateTracks.stream().collect(Collectors.summingInt(t -> t.getSegments().size()));
     }
 
Index: trunk/src/org/openstreetmap/josm/data/gpx/ImmutableGpxTrack.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/gpx/ImmutableGpxTrack.java	(revision 13208)
+++ trunk/src/org/openstreetmap/josm/data/gpx/ImmutableGpxTrack.java	(revision 13210)
@@ -35,4 +35,20 @@
         this.attr = Collections.unmodifiableMap(new HashMap<>(attributes));
         this.segments = Collections.unmodifiableList(newSegments);
+        this.length = calculateLength();
+        this.bounds = calculateBounds();
+    }
+
+    /**
+     * Constructs a new {@code ImmutableGpxTrack} from {@code GpxTrackSegment} objects.
+     * @param segments The segments to build the track from.  Input is not deep-copied,
+     *                 which means the caller may reuse the same segments to build
+     *                 multiple ImmutableGpxTrack instances from.  This should not be
+     *                 a problem, since this object cannot modify {@code this.segments}.
+     * @param attributes Attributes for the GpxTrack, the input map is copied.
+     * @since 13210
+     */
+    public ImmutableGpxTrack(List<GpxTrackSegment> segments, Map<String, Object> attributes) {
+        this.attr = Collections.unmodifiableMap(new HashMap<>(attributes));
+        this.segments = Collections.unmodifiableList(segments);
         this.length = calculateLength();
         this.bounds = calculateBounds();
Index: trunk/src/org/openstreetmap/josm/data/gpx/WayPoint.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/gpx/WayPoint.java	(revision 13208)
+++ trunk/src/org/openstreetmap/josm/data/gpx/WayPoint.java	(revision 13210)
@@ -143,4 +143,13 @@
     public void setTime() {
         setTimeFromAttribute();
+    }
+
+    /**
+     * Set the the time stamp of the waypoint into seconds from the epoch,
+     * @param time millisecond from the epoch
+     * @since 13210
+     */
+    public void setTime(long time) {
+        this.time = time / 1000.;
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 13208)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 13210)
@@ -13,4 +13,5 @@
 import java.util.Collection;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
@@ -29,7 +30,10 @@
 import javax.swing.KeyStroke;
 import javax.swing.ListSelectionModel;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.ExpertToggleAction;
+import org.openstreetmap.josm.actions.OsmPrimitiveAction;
 import org.openstreetmap.josm.actions.relation.AddSelectionToRelations;
 import org.openstreetmap.josm.actions.relation.DeleteRelationsAction;
@@ -38,4 +42,6 @@
 import org.openstreetmap.josm.actions.relation.DuplicateRelationAction;
 import org.openstreetmap.josm.actions.relation.EditRelationAction;
+import org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction;
+import org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode;
 import org.openstreetmap.josm.actions.relation.RecentRelationsAction;
 import org.openstreetmap.josm.actions.relation.SelectMembersAction;
@@ -122,4 +128,14 @@
     private transient JMenuItem addSelectionToRelationMenuItem;
 
+    /** export relation to GPX track action */
+    private final ExportRelationToGpxAction exportRelationFromFirstAction =
+            new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_FIRST_MEMBER, Mode.TO_FILE));
+    private final ExportRelationToGpxAction exportRelationFromLastAction =
+            new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_LAST_MEMBER, Mode.TO_FILE));
+    private final ExportRelationToGpxAction exportRelationFromFirstToLayerAction =
+            new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_FIRST_MEMBER, Mode.TO_LAYER));
+    private final ExportRelationToGpxAction exportRelationFromLastToLayerAction =
+            new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_LAST_MEMBER, Mode.TO_LAYER));
+
     private final transient HighlightHelper highlightHelper = new HighlightHelper();
     private final boolean highlightEnabled = Config.getPref().getBoolean("draw.target-highlight", true);
@@ -603,4 +619,5 @@
 
     private void setupPopupMenuHandler() {
+        List<JMenuItem> checkDisabled = new ArrayList<>();
 
         // -- select action
@@ -612,10 +629,16 @@
         popupMenuHandler.addAction(addMembersToSelectionAction);
 
+        // -- download members action
         popupMenuHandler.addSeparator();
-        // -- download members action
         popupMenuHandler.addAction(downloadMembersAction);
-
-        // -- download incomplete members action
         popupMenuHandler.addAction(downloadSelectedIncompleteMembersAction);
+
+        // -- export relation to gpx action
+        popupMenuHandler.addSeparator();
+        checkDisabled.add(popupMenuHandler.addAction(exportRelationFromFirstAction));
+        checkDisabled.add(popupMenuHandler.addAction(exportRelationFromLastAction));
+        popupMenuHandler.addSeparator();
+        checkDisabled.add(popupMenuHandler.addAction(exportRelationFromFirstToLayerAction));
+        checkDisabled.add(popupMenuHandler.addAction(exportRelationFromLastToLayerAction));
 
         popupMenuHandler.addSeparator();
@@ -625,4 +648,27 @@
 
         addSelectionToRelationMenuItem = popupMenuHandler.addAction(addSelectionToRelations);
+
+        popupMenuHandler.addListener(new PopupMenuListener() {
+            @Override
+            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+                for (JMenuItem mi: checkDisabled) {
+                    mi.setVisible(((OsmPrimitiveAction) mi.getAction()).isEnabled());
+
+                    Component sep = popupMenu.getComponent(
+                            Math.max(0, popupMenu.getComponentIndex(mi)-1));
+                    if (!(sep instanceof JMenuItem)) {
+                        sep.setVisible(mi.isVisible());
+                    }
+                }
+            }
+
+            @Override
+            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+            }
+
+            @Override
+            public void popupMenuCanceled(PopupMenuEvent e) {
+            }
+        });
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 13208)
+++ trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 13210)
@@ -7,9 +7,13 @@
 import java.awt.Dimension;
 import java.awt.Graphics2D;
+import java.awt.event.ActionEvent;
 import java.io.File;
 import java.text.DateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
-
+import java.util.List;
+
+import javax.swing.AbstractAction;
 import javax.swing.Action;
 import javax.swing.Icon;
@@ -17,4 +21,6 @@
 import javax.swing.SwingUtilities;
 
+import org.openstreetmap.josm.actions.ExpertToggleAction;
+import org.openstreetmap.josm.actions.ExpertToggleAction.ExpertModeChangeListener;
 import org.openstreetmap.josm.actions.RenameLayerAction;
 import org.openstreetmap.josm.actions.SaveActionBase;
@@ -48,9 +54,10 @@
  * A layer that displays data from a Gpx file / the OSM gpx downloads.
  */
-public class GpxLayer extends Layer {
+public class GpxLayer extends Layer implements ExpertModeChangeListener {
 
     /** GPX data */
     public GpxData data;
     private final boolean isLocalFile;
+    private boolean isExpertMode;
     /**
      * used by {@link ChooseTrackVisibilityAction} to determine which tracks to show/hide
@@ -84,5 +91,5 @@
 
     /**
-     * Constructs a new {@code GpxLayer} with a given name, thah can be attached to a local file.
+     * Constructs a new {@code GpxLayer} with a given name, that can be attached to a local file.
      * @param d GPX data
      * @param name layer name
@@ -97,4 +104,5 @@
         setName(name);
         isLocalFile = isLocal;
+        ExpertToggleAction.addExpertModeChangeListener(this, true);
     }
 
@@ -139,5 +147,8 @@
     @Override
     public Object getInfoComponent() {
-        StringBuilder info = new StringBuilder(48).append("<html>");
+        StringBuilder info = new StringBuilder(128)
+                .append("<html><head><style>")
+                .append("td { padding: 4px 16px; }")
+                .append("</style></head><body>");
 
         if (data.attr.containsKey("name")) {
@@ -151,8 +162,13 @@
         if (!data.getTracks().isEmpty()) {
             info.append("<table><thead align='center'><tr><td colspan='5'>")
-                .append(trn("{0} track", "{0} tracks", data.tracks.size(), data.tracks.size()))
-                .append("</td></tr><tr align='center'><td>").append(tr("Name")).append("</td><td>")
-                .append(tr("Description")).append("</td><td>").append(tr("Timespan"))
-                .append("</td><td>").append(tr("Length")).append("</td><td>").append(tr("URL"))
+                .append(trn("{0} track, {1} track segments", "{0} tracks, {1} track segments",
+                        data.getTrackCount(), data.getTrackCount(),
+                        data.getTrackSegsCount(), data.getTrackSegsCount()))
+                .append("</td></tr><tr align='center'><td>").append(tr("Name"))
+                .append("</td><td>").append(tr("Description"))
+                .append("</td><td>").append(tr("Timespan"))
+                .append("</td><td>").append(tr("Length"))
+                .append("</td><td>").append(tr("Number of<br/>Segments"))
+                .append("</td><td>").append(tr("URL"))
                 .append("</td></tr></thead>");
 
@@ -171,4 +187,6 @@
                 info.append(SystemOfMeasurement.getSystemOfMeasurement().getDistText(trk.length()));
                 info.append("</td><td>");
+                info.append(trk.getSegments().size());
+                info.append("</td><td>");
                 if (trk.getAttributes().containsKey("url")) {
                     info.append(trk.get("url"));
@@ -181,5 +199,6 @@
         info.append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length()))).append("<br>")
             .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size()))
-            .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size())).append("<br></html>");
+            .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size()))
+            .append("<br></body></html>");
 
         final JScrollPane sp = new JScrollPane(new HtmlPanel(info.toString()));
@@ -196,5 +215,5 @@
     @Override
     public Action[] getMenuEntries() {
-        return new Action[] {
+        List<Action> entries = new ArrayList<>(Arrays.asList(
                 LayerListDialog.getInstance().createShowHideLayerAction(),
                 LayerListDialog.getInstance().createDeleteLayerAction(),
@@ -213,7 +232,19 @@
                 SeparatorLayerAction.INSTANCE,
                 new ChooseTrackVisibilityAction(this),
-                new RenameLayerAction(getAssociatedFile(), this),
-                SeparatorLayerAction.INSTANCE,
-                new LayerListPopup.InfoAction(this) };
+                new RenameLayerAction(getAssociatedFile(), this)));
+
+        List<Action> expert = Arrays.asList(
+                new CombineTracksToSegmentedTrackAction(this),
+                new SplitTrackSegementsToTracksAction(this),
+                new SplitTracksToLayersAction(this));
+
+        if (isExpertMode && expert.stream().anyMatch(t -> t.isEnabled())) {
+            entries.add(SeparatorLayerAction.INSTANCE);
+            expert.stream().filter(t -> t.isEnabled()).forEach(t -> entries.add(t));
+        }
+
+        entries.add(SeparatorLayerAction.INSTANCE);
+        entries.add(new LayerListPopup.InfoAction(this));
+        return entries.toArray(new Action[0]);
     }
 
@@ -238,5 +269,7 @@
         }
 
-        info.append(trn("{0} track, ", "{0} tracks, ", data.getTracks().size(), data.getTracks().size()))
+        info.append(trn("{0} track", "{0} tracks", data.getTrackCount(), data.getTrackCount()))
+            .append(trn(" ({0} segment)", " ({0} segments)", data.getTrackSegsCount(), data.getTrackSegsCount()))
+            .append(", ")
             .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size()))
             .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size())).append("<br>")
@@ -330,3 +363,105 @@
         return new GpxDrawHelper(this);
     }
+
+    /**
+     * Action to merge tracks into a single segmented track
+     *
+     * @since 13210
+     */
+    public static class CombineTracksToSegmentedTrackAction extends AbstractAction {
+        private final transient GpxLayer layer;
+
+        /**
+         * Create a new CombineTracksToSegmentedTrackAction
+         * @param layer The layer with the data to work on.
+         */
+        public CombineTracksToSegmentedTrackAction(GpxLayer layer) {
+            // FIXME: icon missing, create a new icon for this action
+            //new ImageProvider("gpx_tracks_to_segmented_track").getResource().attachImageIcon(this, true);
+            putValue(SHORT_DESCRIPTION, tr("Collect segments of all tracks and combine in a single track."));
+            putValue(NAME, tr("Combine tracks of this layer"));
+            this.layer = layer;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            layer.data.combineTracksToSegmentedTrack();
+            layer.invalidate();
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return layer.data.getTrackCount() > 1;
+        }
+    }
+
+    /**
+     * Action to split track segments into a multiple tracks with one segment each
+     *
+     * @since 13210
+     */
+    public static class SplitTrackSegementsToTracksAction extends AbstractAction {
+        private final transient GpxLayer layer;
+
+        /**
+         * Create a new SplitTrackSegementsToTracksAction
+         * @param layer The layer with the data to work on.
+         */
+        public SplitTrackSegementsToTracksAction(GpxLayer layer) {
+            // FIXME: icon missing, create a new icon for this action
+            //new ImageProvider("gpx_segmented_track_to_tracks").getResource().attachImageIcon(this, true);
+            putValue(SHORT_DESCRIPTION, tr("Split multiple track segments of one track into multiple tracks."));
+            putValue(NAME, tr("Split track segments to tracks"));
+            this.layer = layer;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            layer.data.splitTrackSegmentsToTracks();
+            layer.invalidate();
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return layer.data.getTrackSegsCount() > layer.data.getTrackCount();
+        }
+    }
+
+    /**
+     * Action to split tracks of one gpx layer into multiple gpx layers,
+     * the result is one GPX track per gpx layer.
+     *
+     * @since 13210
+     */
+    public static class SplitTracksToLayersAction extends AbstractAction {
+        private final transient GpxLayer layer;
+
+        /**
+         * Create a new SplitTrackSegementsToTracksAction
+         * @param layer The layer with the data to work on.
+         */
+        public SplitTracksToLayersAction(GpxLayer layer) {
+            // FIXME: icon missing, create a new icon for this action
+            //new ImageProvider("gpx_split_tracks_to_layers").getResource().attachImageIcon(this, true);
+            putValue(SHORT_DESCRIPTION, tr("Split the tracks of this layer to one new layer each."));
+            putValue(NAME, tr("Split tracks to new layers"));
+            this.layer = layer;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            layer.data.splitTracksToLayers();
+            // layer is not modified by this action
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return layer.data.getTrackCount() > 1;
+        }
+    }
+
+    @Override
+    public void expertChanged(boolean isExpert) {
+        this.isExpertMode = isExpert;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 13208)
+++ trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 13210)
@@ -740,5 +740,20 @@
     }
 
-    private static WayPoint nodeToWayPoint(Node n) {
+    /**
+     * @param n the {@code Node} to convert
+     * @return {@code WayPoint} object
+     * @since 13210
+     */
+    public static WayPoint nodeToWayPoint(Node n) {
+        return nodeToWayPoint(n, 0);
+    }
+
+    /**
+     * @param n the {@code Node} to convert
+     * @param time a time value in milliseconds from the epoch.
+     * @return {@code WayPoint} object
+     * @since 13210
+     */
+    public static WayPoint nodeToWayPoint(Node n, long time) {
         WayPoint wpt = new WayPoint(n.getCoor());
 
@@ -747,5 +762,7 @@
         addDoubleIfPresent(wpt, n, GpxConstants.PT_ELE);
 
-        if (!n.isTimestampEmpty()) {
+        if (time > 0) {
+            wpt.setTime(time);
+        } else if (!n.isTimestampEmpty()) {
             wpt.put("time", DateUtils.fromTimestamp(n.getRawTimestamp()));
             wpt.setTime();
