Index: data/gpx-drawing-extensions-1.0.xsd
===================================================================
--- data/gpx-drawing-extensions-1.0.xsd	(nonexistent)
+++ data/gpx-drawing-extensions-1.0.xsd	(working copy)
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schema targetNamespace="https://josm.openstreetmap.de/gpx-drawing-extensions-1.0"
+	elementFormDefault="qualified"
+	xmlns="http://www.w3.org/2001/XMLSchema"
+	xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:gpxd="https://josm.openstreetmap.de/gpx-drawing-extensions-1.0"
+	xsi:schemaLocation="https://josm.openstreetmap.de/gpx-drawing-extensions-1.0 https://josm.openstreetmap.de/gpx-drawing-extensions-1.0.xsd">
+
+	<xsd:annotation>
+		<xsd:documentation>
+			This schema defines drawing extensions for the GPX 1.1 schema (http://www.topografix.com/GPX/1/1/gpx.xsd).
+			Elements in this schema should be used as child elements of the "extensions" element defined by the GPX schema.
+		</xsd:documentation>
+	</xsd:annotation>
+	
+	<!-- Elements -->
+
+	<xsd:element name="color" type="gpxd:hexColor_type">
+		<xsd:annotation>
+			<xsd:documentation>
+				The color of the element, i.e. #RRGGBB or #RRGGBBAA.
+				Note that applications should apply possible alpha values to the lines and opacity to the whole track. This means that overlapping parts of the
+				track with alpha values will look more intense than individual lines, whereas the opacity affects the whole track including overlapping parts.
+			</xsd:documentation>
+		</xsd:annotation>
+	</xsd:element>
+	
+	<xsd:element name="opacity" type="gpxd:opacity_type">
+		<xsd:annotation>
+			<xsd:documentation>
+				The opacity of the element between 0.00 and 1.00.
+			</xsd:documentation>
+		</xsd:annotation>
+	</xsd:element>
+	
+	<xsd:element name="width" type="xsd:positiveInteger">
+		<xsd:annotation>
+			<xsd:documentation>
+				The width of the line in pixels, applications may use a width relative to this value if required.
+			</xsd:documentation>
+		</xsd:annotation>
+	</xsd:element>
+	
+	<xsd:element name="dashPattern" type="gpxd:dashPattern_type">
+		<xsd:annotation>
+			<xsd:documentation>
+				The dash pattern of the line, see gpxd:dashPattern_type. Should always be relative to the width.
+			</xsd:documentation>
+		</xsd:annotation>
+	</xsd:element>
+	
+	<!-- Types -->
+
+	<xsd:simpleType name="hexColor_type">
+		<xsd:annotation>
+			<xsd:documentation>
+				The hexColor_type must be a # followed by a 6 or 8-digit hex representation of the color (with or without the alpha value).			
+			</xsd:documentation>
+		</xsd:annotation>
+		<xsd:restriction base="xsd:string">
+			<xsd:pattern value="\#([a-fA-F0-9]{6}|[a-fA-F0-9]{8})" />
+			<xsd:whiteSpace value="collapse" />
+		</xsd:restriction>
+	</xsd:simpleType>
+
+	<xsd:simpleType name="opacity_type">
+		<xsd:annotation>
+			<xsd:documentation>
+				The opacity_type must be a decimal value between 0 and 1.
+			</xsd:documentation>
+		</xsd:annotation>
+		<xsd:restriction base="xsd:decimal">
+			<xsd:minInclusive value="0" />
+			<xsd:maxInclusive value="1" />
+		</xsd:restriction>
+	</xsd:simpleType>
+	
+	<xsd:simpleType name="dashPattern_type">
+		<xsd:annotation>
+			<xsd:documentation>
+				The dashPattern_type can be 
+					- a representation of the pattern as y-n-y-n-... with y being the relative length of the line that is
+					  visible and n being the relative length of the line that is hidden to create a dashed / dotted line.
+					  Has to have an even number of segments (at least two) and can contain multi-digit numbers.
+					- one of the following predefined values:
+					  none, dash-long, dash-medium, dash-short, dot-sparse, dot-normal, dot-dense, dash-dot, dash-dot-dot
+			</xsd:documentation>
+		</xsd:annotation>
+		<xsd:restriction base="xsd:string"> <!-- use string based pattern instead of enum because both pattern and enums are allowed -->
+			<xsd:pattern value="\d+\-\d+(\-\d+\-\d+)*" /> <!-- pattern, see documentation above -->
+			<xsd:pattern value="none" /> 		<!-- 1-0, default value/line -->
+			<xsd:pattern value="dash-long" />	<!-- 6-2 -->
+			<xsd:pattern value="dash-medium" />	<!-- 4-4 -->
+			<xsd:pattern value="dash-short" />	<!-- 2-6 -->
+			<xsd:pattern value="dot-sparse" />	<!-- 1-4 -->
+			<xsd:pattern value="dot-normal" />	<!-- 1-2 -->
+			<xsd:pattern value="dot-dense" />	<!-- 1-1 -->
+			<xsd:pattern value="dash-dot" />	<!-- 4-2-1-2 -->
+			<xsd:pattern value="dash-dot-dot" /><!-- 4-2-1-2-1-2 -->			
+		</xsd:restriction>
+	</xsd:simpleType>
+	
+</schema>
\ No newline at end of file
Index: src/org/openstreetmap/josm/data/gpx/Extensions.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/Extensions.java	(revision 15439)
+++ src/org/openstreetmap/josm/data/gpx/Extensions.java	(working copy)
@@ -1,19 +1,62 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.gpx;
 
-import java.util.LinkedHashMap;
+import java.util.TreeMap;
 
+import org.apache.commons.jcs.access.exception.InvalidArgumentException;
+
 /**
- * Data class for extensions in a GPX-File.
+ * Data class for extensions of a specific type in a GPX-File (keys are case-insensitive).
  */
-public class Extensions extends LinkedHashMap<String, String> {
+public final class Extensions extends TreeMap<String, String> {
 
     private static final long serialVersionUID = 1L;
+    private String type;
 
     /**
      * Constructs a new {@code Extensions}.
+     * @param extensionType type of the extension, can be any <code>GpxConstants.EXTENSIONS_*</code>
      */
-    public Extensions() {
-        super();
+    public Extensions(String extensionType) {
+        super(String.CASE_INSENSITIVE_ORDER);
+        if (!extensionType.startsWith(GpxConstants.EXTENSIONS_PREFIX))
+            throw new InvalidExtensionException();
+        type = extensionType;
     }
+
+    /**
+     * Returns the prefix for the XML namespace.
+     * @return the prefix including the <code>:</code>
+     */
+    public String getPrefix() {
+        return type.substring(type.lastIndexOf(".") + 1) + ":";
+    }
+
+    /**
+     * @return type of the extension
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * InvalidExtensionException is thrown if the Extension value does not match the expected format
+     * @see GpxConstants#EXTENSIONS_PREFIX
+     */
+    public static class InvalidExtensionException extends InvalidArgumentException {
+        /**
+         * Constructs a new {@link InvalidExtensionException} with a default error message
+         */
+        public InvalidExtensionException() {
+            super("The extensionType must start with the extensions prefix.");
+        }
+        /**
+         * Constructs a new {@link InvalidExtensionException} with the given error message
+         * @param message
+         */
+        public InvalidExtensionException(String message) {
+            super(message);
+        }
+    }
+
 }
Index: src/org/openstreetmap/josm/data/gpx/GpxConstants.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/GpxConstants.java	(revision 15439)
+++ src/org/openstreetmap/josm/data/gpx/GpxConstants.java	(working copy)
@@ -1,10 +1,13 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.gpx;
 
+import java.awt.Color;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.spi.preferences.Config;
@@ -95,18 +98,57 @@
      * @see GpxData#getMetaBounds()
      */
     String META_BOUNDS = META_PREFIX + "bounds";
+
     /**
-     * A constant for the metadata hash map: the extension data. This is a {@link Extensions} object
-     * @see GpxData#addExtension(String, String)
+     * Prefix used for all extension values.
+     * Note that all extension values <b>must</b> end with the according XML namespace prefix (i.e <code>.josm</code> or <code>.gpxd</code>)
+     */
+    String EXTENSIONS_PREFIX = "extensions.";
+
+    /**
+     * The JOSM extension data (josm:*). This is a {@link Extensions} object
+     * @see GpxData#addExtensionKey(String, String, String)
      * @see GpxData#get(String)
      */
-    String META_EXTENSIONS = META_PREFIX + "extensions";
+    String EXTENSIONS_JOSM = EXTENSIONS_PREFIX + "josm";
 
     /**
-     * A namespace for josm GPX extensions
+     * The GPX drawing extension data (gpxd:*). This is a {@link Extensions} object
      */
-    String JOSM_EXTENSIONS_NAMESPACE_URI = Config.getUrls().getXMLBase() + "/gpx-extensions-1.0";
+    String EXTENSIONS_DRAWING = EXTENSIONS_PREFIX + "gpxd";
 
+    /**
+     * The Garmin GPX extension data (gpxx:*). This is a {@link Extensions} object
+     */
+    String EXTENSIONS_GARMIN = EXTENSIONS_PREFIX + "gpxx";
+
+    /**
+     * Namespace for JOSM GPX extensions
+     */
+    String XML_URI_EXTENSIONS_JOSM = Config.getUrls().getXMLBase() + "/gpx-extensions-1.0";
+    /**
+     * Location of the XSD schema for JOSM GPX extensions
+     */
+    String XML_XSD_EXTENSIONS_JOSM = Config.getUrls().getXMLBase() + "/gpx-extensions-1.0.xsd";
+
+    /**
+     * Namespace for GPX drawing extensions
+     */
+    String XML_URI_EXTENSIONS_DRAWING = Config.getUrls().getXMLBase() + "/gpx-drawing-extensions-1.0";
+    /**
+     * Location of the XSD schema for GPX drawing extensions
+     */
+    String XML_XSD_EXTENSIONS_DRAWING = Config.getUrls().getXMLBase() + "/gpx-drawing-extensions-1.0.xsd";
+
+    /**
+     * Namespace for Garmin GPX extensions
+     */
+    String XML_URI_EXTENSIONS_GARMIN = "http://www.garmin.com/xmlschemas/GpxExtensions/v3";
+    /**
+     * Location of the XSD schema for GPX drawing extensions
+     */
+    String XML_XSD_EXTENSIONS_GARMIN = "http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd";
+
     /** Elevation (in meters) of the point. */
     String PT_ELE = "ele";
 
@@ -154,15 +196,47 @@
      */
     List<String> WPT_KEYS = Collections.unmodifiableList(Arrays.asList(PT_ELE, PT_TIME, PT_MAGVAR, PT_GEOIDHEIGHT,
             GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, PT_SYM, PT_TYPE,
-            PT_FIX, PT_SAT, PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID, META_EXTENSIONS));
+            PT_FIX, PT_SAT, PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID,
+            EXTENSIONS_JOSM, EXTENSIONS_DRAWING, EXTENSIONS_GARMIN));
 
     /**
      * Ordered list of all possible route and track keys.
      */
     List<String> RTE_TRK_KEYS = Collections.unmodifiableList(Arrays.asList(
-            GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, "number", PT_TYPE, META_EXTENSIONS));
+            GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, "number", PT_TYPE,
+            EXTENSIONS_JOSM, EXTENSIONS_DRAWING, EXTENSIONS_GARMIN));
 
     /**
+     * Possible extension namespaces
+     */
+    List<String> SUPPORTED_EXTENSION_NAPMESPACES = Collections.unmodifiableList(Arrays.asList(
+            "josm", "gpxx", "gpxd"));
+
+    /**
+     * Map with all supported Garmin colors
+     */
+    Map<String, Color> GARMIN_COLORS = Collections.unmodifiableMap(
+            new TreeMap<String, Color>(String.CASE_INSENSITIVE_ORDER) {{
+                put("Black", Color.BLACK);
+                put("DarkRed", new Color(139, 0, 0));
+                put("DarkGreen", new Color(0, 100, 0));
+                put("DarkYellow", new Color(255, 170, 0));
+                put("DarkBlue", new Color(0, 0, 139));
+                put("DarkMagenta", new Color(139, 0, 139));
+                put("DarkCyan", new Color(0, 139, 139));
+                put("LightGray", Color.LIGHT_GRAY);
+                put("DarkGray", Color.DARK_GRAY);
+                put("Red", Color.RED);
+                put("Green", Color.GREEN);
+                put("Yellow", Color.YELLOW);
+                put("Blue", Color.BLUE);
+                put("Magenta", Color.MAGENTA);
+                put("Cyan", Color.CYAN);
+                put("White", Color.WHITE);
+                put("Transparent", new Color(0, 0, 0, 255));
+            }});
+            
+    /**
      * Possible fix values. NMEA 0183 Version 4.00
      */
     Collection<String> FIX_VALUES = Collections.unmodifiableList(
Index: src/org/openstreetmap/josm/data/gpx/GpxData.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/GpxData.java	(revision 15439)
+++ src/org/openstreetmap/josm/data/gpx/GpxData.java	(working copy)
@@ -65,7 +65,8 @@
      * Addidionaly waypoints for this file.
      */
     private final ArrayList<WayPoint> privateWaypoints = new ArrayList<>();
-    private final GpxTrackChangeListener proxy = e -> fireInvalidate();
+    private final GpxTrackChangeListener proxy = e -> {fireInvalidate(); modified = true;};
+    private boolean modified = false;
 
     /**
      * Tracks. Access is discouraged, use {@link #getTracks()} to read.
@@ -888,6 +889,7 @@
         private Line next;
         private final boolean[] trackVisibility;
         private Map<String, Object> trackAttributes;
+        private GpxTrack curTrack;
 
         /**
          * Constructs a new {@code LinesIterator}.
@@ -921,17 +923,17 @@
         private Line getNext() {
             if (itTracks != null) {
                 if (itTrackSegments != null && itTrackSegments.hasNext()) {
-                    return new Line(itTrackSegments.next(), trackAttributes);
+                    return new Line(itTrackSegments.next(), trackAttributes, curTrack.getColor());
                 } else {
                     while (itTracks.hasNext()) {
-                        GpxTrack nxtTrack = itTracks.next();
-                        trackAttributes = nxtTrack.getAttributes();
+                        curTrack = itTracks.next();
+                        trackAttributes = curTrack.getAttributes();
                         idxTracks++;
                         if (trackVisibility != null && !trackVisibility[idxTracks])
                             continue;
-                        itTrackSegments = nxtTrack.getSegments().iterator();
+                        itTrackSegments = curTrack.getSegments().iterator();
                         if (itTrackSegments.hasNext()) {
-                            return new Line(itTrackSegments.next(), trackAttributes);
+                            return new Line(itTrackSegments.next(), trackAttributes, curTrack.getColor());
                         }
                     }
                     // if we get here, all the Tracks are finished; Continue with Routes
@@ -1067,4 +1069,11 @@
             return source;
         }
     }
+
+    /**
+     * @return whether anything (i.e. colors) have been modified
+     */
+    public boolean isModified() {
+        return modified;
+    }
 }
Index: src/org/openstreetmap/josm/data/gpx/GpxTrack.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/GpxTrack.java	(revision 15439)
+++ src/org/openstreetmap/josm/data/gpx/GpxTrack.java	(working copy)
@@ -1,6 +1,7 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.gpx;
 
+import java.awt.Color;
 import java.util.Collection;
 import java.util.Map;
 
@@ -7,7 +8,7 @@
 import org.openstreetmap.josm.data.Bounds;
 
 /**
- * Read-only gpx track. Implementations doesn't have to be immutable, but should always be thread safe.
+ * Gpx track. Implementations doesn't have to be immutable, but should always be thread safe.
  * @since 444
  */
 public interface GpxTrack extends IWithAttributes {
@@ -37,6 +38,24 @@
     double length();
 
     /**
+     * Gets the color of this track.
+     * @return The color, <code>null</code> if not set or not supported by the implementation.
+     * @since xxx
+     */
+    default Color getColor() {
+        return null;
+    }
+
+    /**
+     * Sets the color of this track. Not necessarily supported by all implementations.
+     * @param color
+     * @since xxx
+     */
+    default void setColor(Color color) {
+        return;
+    }
+
+    /**
      * Add a listener that listens to changes in the GPX track.
      * @param l The listener
      */
Index: src/org/openstreetmap/josm/data/gpx/IWithAttributes.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/IWithAttributes.java	(revision 15439)
+++ src/org/openstreetmap/josm/data/gpx/IWithAttributes.java	(working copy)
@@ -2,6 +2,7 @@
 package org.openstreetmap.josm.data.gpx;
 
 import java.util.Collection;
+import java.util.Map;
 
 /**
  * Object with attributes (in the context of GPX data).
@@ -50,11 +51,28 @@
     void put(String key, Object value);
 
     /**
-     * Add a key / value pair that is not part of the GPX schema as an extension.
-     *
+     * Add a key / value pair that is not part of the GPX schema as an extension and the extension attribute if not already present.
+     * @param extensionType type of the extension, can be any <code>GpxConstants.EXTENSIONS_*</code> or a string of format <code>"extensions.<i>xmlns_prefix</i>"</code>
      * @param key the key
      * @param value the value
+     * @see GpxConstants#EXTENSIONS_PREFIX
      */
-    void addExtension(String key, String value);
+    void addExtensionKey(String extensionType, String key, String value);
 
+    /**
+     * Add key / value pairs that are not part of the GPX schema as extensions.
+     * @param extensionMap the <code>Map&lt;String, String&gt;</code> with the key being the qualified name including prefix of the namespace
+     * @see GpxConstants#EXTENSIONS_PREFIX
+     * @see Extensions
+     */
+    void addExtensions(Map<String, String> extensionMap);
+
+    /**
+     * Removes a key from the extension if present and the extension if empty
+     * @param extensionType type of the extension, can be any <code>GpxConstants.EXTENSIONS_*</code> or a string of format <code>"extensions.<i>xmlns_prefix</i>"</code>
+     * @param key the key, not case-sensitive
+     * @see GpxConstants#EXTENSIONS_PREFIX
+     */
+    void removeExtensionKey(String extensionType, String key);
+
 }
Index: src/org/openstreetmap/josm/data/gpx/ImmutableGpxTrack.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/ImmutableGpxTrack.java	(revision 15439)
+++ src/org/openstreetmap/josm/data/gpx/ImmutableGpxTrack.java	(working copy)
@@ -1,6 +1,7 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.gpx;
 
+import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -9,9 +10,12 @@
 import java.util.Map;
 
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.tools.ListenerList;
+import org.openstreetmap.josm.tools.Logging;
 
 /**
  * Immutable GPX track.
+ * Note that the color attributes are not immutable and may be modified by the user.
  * @since 2907
  */
 public class ImmutableGpxTrack extends WithAttributes implements GpxTrack {
@@ -19,13 +23,16 @@
     private final List<GpxTrackSegment> segments;
     private final double length;
     private final Bounds bounds;
+    private Color colorCache;
+    private final ListenerList<GpxTrackChangeListener> listeners = ListenerList.create();
 
     /**
-     * Constructs a new {@code ImmutableGpxTrack}.
+     * Constructs a new {@code ImmutableGpxTrack} and adds the extensions.
      * @param trackSegs track segments
      * @param attributes track attributes
+     * @param extensionMap map of extensions
      */
-    public ImmutableGpxTrack(Collection<Collection<WayPoint>> trackSegs, Map<String, Object> attributes) {
+    public ImmutableGpxTrack(Collection<Collection<WayPoint>> trackSegs, Map<String, Object> attributes, Map<String, String> extensionMap) {
         List<GpxTrackSegment> newSegments = new ArrayList<>();
         for (Collection<WayPoint> trackSeg: trackSegs) {
             if (trackSeg != null && !trackSeg.isEmpty()) {
@@ -32,7 +39,10 @@
                 newSegments.add(new ImmutableGpxTrackSegment(trackSeg));
             }
         }
-        this.attr = Collections.unmodifiableMap(new HashMap<>(attributes));
+        this.attr = new HashMap<>(attributes);
+        if (extensionMap != null) {
+            addExtensions(extensionMap);
+        }
         this.segments = Collections.unmodifiableList(newSegments);
         this.length = calculateLength();
         this.bounds = calculateBounds();
@@ -39,6 +49,15 @@
     }
 
     /**
+     * Constructs a new {@code ImmutableGpxTrack}.
+     * @param trackSegs track segments
+     * @param attributes track attributes
+     */
+    public ImmutableGpxTrack(Collection<Collection<WayPoint>> trackSegs, Map<String, Object> attributes) {
+        this(trackSegs, attributes, null);
+    }
+
+    /**
      * 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
@@ -48,7 +67,7 @@
      * @since 13210
      */
     public ImmutableGpxTrack(List<GpxTrackSegment> segments, Map<String, Object> attributes) {
-        this.attr = Collections.unmodifiableMap(new HashMap<>(attributes));
+        this.attr = new HashMap<>(attributes);
         this.segments = Collections.unmodifiableList(segments);
         this.length = calculateLength();
         this.bounds = calculateBounds();
@@ -79,6 +98,69 @@
     }
 
     @Override
+    public void setColor(Color color) {
+        setColorAttr(color);
+        colorCache = color;
+    }
+
+    private void setColorAttr(Color color) {
+        removeExtensionKey(GpxConstants.EXTENSIONS_GARMIN, "displaycolor");
+        if (color != null) {
+            addExtensionKey(EXTENSIONS_DRAWING, "color", String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()));
+        } else {
+            removeExtensionKey(EXTENSIONS_DRAWING, "color");
+        }
+    }
+
+    @Override
+    public Color getColor() {
+        if (colorCache == null) {
+            colorCache = getColorFromAttr(attr);
+        }
+        return colorCache;
+    }
+
+
+    private Color getColorFromAttr(Map<String, Object> attr) {
+        Extensions gpxd = (Extensions) attr.get(GpxConstants.EXTENSIONS_DRAWING);
+        if (gpxd != null) {
+            String cs = gpxd.get("color");
+            try {
+                return Color.decode(cs);
+            } catch (NumberFormatException ex) {
+                Logging.warn("Could not read gpxd color: " + cs);
+            }
+        } else {
+            Extensions gpxx = (Extensions) attr.get(GpxConstants.EXTENSIONS_GARMIN);
+            if (gpxx != null) {
+                String cs = gpxx.get("displaycolor");
+                if (cs != null) {
+                    Color cc = GpxConstants.GARMIN_COLORS.get(cs);
+                    if (cc != null) {
+                        return cc;
+                    }
+                }
+                Logging.warn("Could not read garmin color: " + cs);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addExtensionKey(String extensionType, String key, String value) {
+        colorCache = null;
+        super.addExtensionKey(extensionType, key, value);
+        listeners.fireEvent(l -> l.gpxDataChanged(new GpxTrackChangeEvent(this)));
+    }
+
+    @Override
+    public void removeExtensionKey(String extensionType, String key) {
+        colorCache = null;
+        super.removeExtensionKey(extensionType, key);
+        listeners.fireEvent(l -> l.gpxDataChanged(new GpxTrackChangeEvent(this)));
+    }
+
+    @Override
     public Map<String, Object> getAttributes() {
         return attr;
     }
@@ -119,4 +201,15 @@
             return false;
         return true;
     }
+
+    @Override
+    public void addListener(GpxTrackChangeListener l) {
+        listeners.addListener(l);
+    }
+
+    @Override
+    public void removeListener(GpxTrackChangeListener l) {
+        listeners.removeListener(l);
+    }
+
 }
Index: src/org/openstreetmap/josm/data/gpx/Line.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/Line.java	(revision 15439)
+++ src/org/openstreetmap/josm/data/gpx/Line.java	(working copy)
@@ -1,9 +1,10 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.data.gpx;
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.Map;
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.gpx;
+
+import java.awt.Color;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -10,51 +11,64 @@
  * Line represents a linear collection of GPX waypoints with the ordered/unordered distinction.
  * @since 14451
  */
-public class Line implements Collection<WayPoint> {
-    private final Collection<WayPoint> waypoints;
-    private final boolean unordered;
+public class Line implements Collection<WayPoint> {
+    private final Collection<WayPoint> waypoints;
+    private final boolean unordered;
+    private final Color color;
+
+    /**
+     * Constructs a new {@code Line}.
+     * @param waypoints collection of waypoints
+     * @param attributes track/route attributes
+     * @param color color of the track
+     *
+     */
+    public Line(Collection<WayPoint> waypoints, Map<String, Object> attributes, Color color) {
+        this.color = color;
+        this.waypoints = Objects.requireNonNull(waypoints);
+        unordered = attributes.isEmpty() && waypoints.stream().allMatch(x -> x.get(GpxConstants.PT_TIME) == null);
+    }
 
     /**
      * Constructs a new {@code Line}.
-     * @param waypoints collection of waypoints
-     * @param attributes track/route attributes
-     */
-    public Line(Collection<WayPoint> waypoints, Map<String, Object> attributes) {
-        this.waypoints = Objects.requireNonNull(waypoints);
-        unordered = attributes.isEmpty() && waypoints.stream().allMatch(x -> x.get(GpxConstants.PT_TIME) == null);
-    }
-
-    /**
+     * @param trackSegment track segment
+     * @param trackAttributes track attributes
+     * @param color color of the track
+     */
+    public Line(GpxTrackSegment trackSegment, Map<String, Object> trackAttributes, Color color) {
+        this(trackSegment.getWayPoints(), trackAttributes, color);
+    }
+
+    /**
      * Constructs a new {@code Line}.
-     * @param trackSegment track segment
-     * @param trackAttributes track attributes
-     */
-    public Line(GpxTrackSegment trackSegment, Map<String, Object> trackAttributes) {
-        this(trackSegment.getWayPoints(), trackAttributes);
-    }
-
-    /**
-     * Constructs a new {@code Line}.
-     * @param route route
-     */
-    public Line(GpxRoute route) {
-        this(route.routePoints, route.attr);
-    }
-
-    /**
+     * @param route route
+     */
+    public Line(GpxRoute route) {
+        this(route.routePoints, route.attr, null);
+    }
+
+    /**
      * Determines if waypoints are ordered.
      * @return {@code true} if waypoints are ordered
      */
     public boolean isUnordered() {
-        return unordered;
+        return unordered;
+    }
+
+    /**
+     * Returns the track/route color
+     * @return the color
+     */
+    public Color getColor() {
+        return color;
+    }
+
+    @Override
+    public int size() {
+        return waypoints.size();
     }
 
     @Override
-    public int size() {
-        return waypoints.size();
-    }
-
-    @Override
     public boolean isEmpty() {
         return waypoints.isEmpty();
     }
Index: src/org/openstreetmap/josm/data/gpx/WithAttributes.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/WithAttributes.java	(revision 15439)
+++ src/org/openstreetmap/josm/data/gpx/WithAttributes.java	(working copy)
@@ -4,6 +4,7 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * Default implementation for IWithAttributes.
@@ -64,7 +65,6 @@
 
     /**
      * Put a key / value pair as a new attribute.
-     *
      * Overrides key / value pair with the same key (if present).
      *
      * @param key the key
@@ -75,22 +75,41 @@
         attr.put(key, value);
     }
 
-    /**
-     * Add a key / value pair that is not part of the GPX schema as an extension.
-     *
-     * @param key the key
-     * @param value the value
-     */
     @Override
-    public void addExtension(String key, String value) {
-        if (!attr.containsKey(META_EXTENSIONS)) {
-            attr.put(META_EXTENSIONS, new Extensions());
+    public void addExtensionKey(String extensionType, String key, String value) {
+        Extensions ext;
+        if (!attr.containsKey(extensionType)) {
+            ext = new Extensions(extensionType);
+            attr.put(extensionType, ext);
+        } else {
+            ext = (Extensions) attr.get(extensionType);
         }
-        Extensions ext = (Extensions) attr.get(META_EXTENSIONS);
         ext.put(key, value);
     }
 
     @Override
+    public void addExtensions(Map<String, String> extensionMap) {
+        for (Entry<String, String> e : extensionMap.entrySet()) {
+            String k = e.getKey();
+            int dot = k.indexOf(":");
+            if (dot != -1) {
+                addExtensionKey(GpxConstants.EXTENSIONS_PREFIX + k.substring(0, dot), k.substring(dot + 1), e.getValue());
+            }
+        }
+    }
+
+    @Override
+    public void removeExtensionKey(String extensionType, String key) {
+        Extensions ext = (Extensions) attr.get(extensionType);
+        if (ext != null) {
+            ext.remove(key);
+            if (ext.isEmpty()) {
+                attr.remove(extensionType);
+            }
+        }
+    }
+
+    @Override
     public int hashCode() {
         return 31 + ((attr == null) ? 0 : attr.hashCode());
     }
@@ -111,4 +130,5 @@
             return false;
         return true;
     }
+
 }
Index: src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(working copy)
@@ -3,7 +3,6 @@
 
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.Color;
 import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.Font;
@@ -17,7 +16,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.swing.AbstractAction;
@@ -41,7 +40,6 @@
 import org.openstreetmap.josm.actions.MergeLayerAction;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.imagery.OffsetBookmark;
-import org.openstreetmap.josm.data.preferences.AbstractProperty;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.MapView;
@@ -583,18 +581,9 @@
                 label.setFont(label.getFont().deriveFont(Font.BOLD));
             }
             if (Config.getPref().getBoolean("dialog.layer.colorname", true)) {
-                AbstractProperty<Color> prop = layer.getColorProperty();
-                Color c = prop == null ? null : prop.get();
-                if (c == null || model.getLayers().stream()
-                        .map(Layer::getColorProperty)
-                        .filter(Objects::nonNull)
-                        .map(AbstractProperty::get)
-                        .noneMatch(oc -> oc != null && !oc.equals(c))) {
-                    /* not more than one color, don't use coloring */
-                    label.setForeground(UIManager.getColor(isSelected ? "Table.selectionForeground" : "Table.foreground"));
-                } else {
-                    label.setForeground(c);
-                }
+                label.setForeground(Optional
+                        .ofNullable(layer.getColor())
+                        .orElse(UIManager.getColor(isSelected ? "Table.selectionForeground" : "Table.foreground")));
             }
             label.setIcon(layer.getIcon());
             label.setToolTipText(layer.getToolTipText());
Index: src/org/openstreetmap/josm/gui/dialogs/layer/LayerVisibilityAction.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/layer/LayerVisibilityAction.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/dialogs/layer/LayerVisibilityAction.java	(working copy)
@@ -593,7 +593,7 @@
                     List<Layer> layers = layerSupplier.get();
                     for (Layer l : layers) {
                         if (l instanceof GpxLayer) {
-                            l.getColorProperty().put(color);
+                            l.setColor(color);
                         }
                     }
                     highlightColor(color);
@@ -602,7 +602,7 @@
             add(colorPanel, GBC.std().weight(1, 1).fill().insets(5));
             panels.put(color, colorPanel);
 
-            List<Color> colors = layerSupplier.get().stream().map(l -> l.getColorProperty().get()).distinct().collect(Collectors.toList());
+            List<Color> colors = layerSupplier.get().stream().map(l -> l.getColor()).distinct().collect(Collectors.toList());
             if (colors.size() == 1) {
                 highlightColor(colors.get(0));
             }
@@ -611,7 +611,7 @@
         @Override
         public void updateLayers(List<Layer> layers, boolean allVisible, boolean allHidden) {
             List<Color> colors = layers.stream().filter(l -> l instanceof GpxLayer)
-                    .map(l -> ((GpxLayer) l).getColorProperty().get())
+                    .map(l -> ((GpxLayer) l).getColor())
                     .distinct()
                     .collect(Collectors.toList());
             if (colors.size() == 1) {
Index: src/org/openstreetmap/josm/gui/io/importexport/GpxExporter.java
===================================================================
--- src/org/openstreetmap/josm/gui/io/importexport/GpxExporter.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/io/importexport/GpxExporter.java	(working copy)
@@ -25,6 +25,7 @@
 
 import org.openstreetmap.josm.data.gpx.GpxConstants;
 import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
 import org.openstreetmap.josm.gui.ExtendedDialog;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.layer.GpxLayer;
@@ -146,8 +147,17 @@
         p.add(new JLabel(tr("Keywords")), GBC.eol());
         JosmTextField keywords = new JosmTextField();
         keywords.setText(gpxData.getString(META_KEYWORDS));
-        p.add(keywords, GBC.eop().fill(GBC.HORIZONTAL));
+        p.add(keywords, GBC.eol().fill(GBC.HORIZONTAL));
 
+        boolean sel = Config.getPref().getBoolean("gpx.export.colors", true);
+        JCheckBox colors = new JCheckBox(tr("Save track colors in GPX file"), sel);
+        p.add(colors, GBC.eol().fill(GBC.HORIZONTAL));
+        JCheckBox garmin = new JCheckBox(tr("Use Garmin compatible GPX extensions"),
+                Config.getPref().getBoolean("gpx.export.colors.garmin", false));
+        garmin.setEnabled(sel);
+        p.add(garmin, GBC.eop().fill(GBC.HORIZONTAL).insets(20, 0, 0, 0));
+
+
         ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(),
                 tr("Export options"),
                 tr("Export and Save"), tr("Cancel"))
@@ -154,6 +164,24 @@
             .setButtonIcons("exportgpx", "cancel")
             .setContent(p);
 
+        colors.addActionListener(l -> {
+            garmin.setEnabled(colors.isSelected());
+        });
+
+        garmin.addActionListener(l -> {
+            if (garmin.isSelected() &&
+                    !ConditionalOptionPaneUtil.showConfirmationDialog(
+                            "gpx_color_garmin",
+                            ed,
+                            new JLabel(tr("<html>Garmin track extensions only support 16 colors. If you continue, the closest supported<br>track color will be used and information such as width and opacity will be lost.</html>")),
+                            tr("Information"),
+                            JOptionPane.OK_CANCEL_OPTION,
+                            JOptionPane.INFORMATION_MESSAGE,
+                            JOptionPane.OK_OPTION)) {
+                garmin.setSelected(false);
+            }
+        });
+
         if (ed.showDialog().getValue() != 1) {
             setCanceled(true);
             return;
@@ -167,6 +195,14 @@
         if (!copyright.getText().isEmpty()) {
             Config.getPref().put("lastCopyright", copyright.getText());
         }
+        Config.getPref().putBoolean("gpx.export.colors", colors.isSelected());
+        Config.getPref().putBoolean("gpx.export.colors.garmin", garmin.isSelected());
+        String colorFormat = null;
+        if (colors.isSelected()) {
+            colorFormat = garmin.isSelected()
+                    ? GpxConstants.EXTENSIONS_GARMIN
+                    : GpxConstants.EXTENSIONS_DRAWING;
+        }
 
         if (layer instanceof OsmDataLayer) {
             gpxData = ((OsmDataLayer) layer).toGpxData();
@@ -203,8 +239,13 @@
             gpxData.put(META_KEYWORDS, keywords.getText());
         }
 
-        try (OutputStream fo = Compression.getCompressedFileOutputStream(file); GpxWriter writer = new GpxWriter(fo)) {
-            writer.write(gpxData);
+        try (OutputStream fo = Compression.getCompressedFileOutputStream(file)) {
+            GpxWriter w = new GpxWriter(fo);
+            w.write(gpxData, colorFormat);
+            w.close();
+            fo.flush();
+        } catch (IOException | InvalidPathException ex) {
+            Logging.error(ex);
         }
     }
 
Index: src/org/openstreetmap/josm/gui/layer/CustomizeColor.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/CustomizeColor.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/layer/CustomizeColor.java	(working copy)
@@ -18,12 +18,10 @@
 import javax.swing.JMenuItem;
 import javax.swing.JOptionPane;
 
-import org.openstreetmap.josm.data.preferences.AbstractProperty;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
 import org.openstreetmap.josm.gui.layer.Layer.MultiLayerAction;
-import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.ImageProvider;
 
 /**
@@ -33,7 +31,7 @@
  * of a certain {@link GpxLayer} or {@link org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer}.
  */
 public class CustomizeColor extends AbstractAction implements LayerAction, MultiLayerAction {
-    private final transient List<AbstractProperty<Color>> colors;
+    private final transient List<Layer> colorLayers;
 
     /**
      * Constructs a new {@code CustomizeColor} for a given list of layers.
@@ -42,8 +40,7 @@
     public CustomizeColor(List<Layer> l) {
         super(tr("Customize Color"));
         new ImageProvider("colorchooser").getResource().attachImageIcon(this, true);
-        colors = l.stream().map(Layer::getColorProperty).collect(Collectors.toList());
-        CheckParameterUtil.ensureThat(colors.stream().allMatch(Objects::nonNull), "All layers must have colors.");
+        colorLayers = l.stream().filter(Objects::nonNull).filter(Layer::hasColor).collect(Collectors.toList());
         putValue("help", ht("/Action/LayerCustomizeColor"));
     }
 
@@ -57,7 +54,7 @@
 
     @Override
     public boolean supportLayers(List<Layer> layers) {
-        return layers.stream().allMatch(l -> l.getColorProperty() != null);
+        return layers.stream().allMatch(l -> l.hasColor());
     }
 
     @Override
@@ -72,7 +69,7 @@
 
     @Override
     public void actionPerformed(ActionEvent e) {
-        Color cl = colors.stream().map(AbstractProperty::get).filter(Objects::nonNull).findAny().orElse(Color.GRAY);
+        Color cl = colorLayers.stream().filter(Objects::nonNull).map(Layer::getColor).filter(Objects::nonNull).findAny().orElse(Color.GRAY);
         JColorChooser c = new JColorChooser(cl);
         Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
         int answer = JOptionPane.showOptionDialog(
@@ -87,12 +84,12 @@
         );
         switch (answer) {
         case 0:
-            colors.stream().forEach(prop -> prop.put(c.getColor()));
+            colorLayers.stream().forEach(l -> l.setColor(c.getColor()));
             break;
         case 1:
             return;
         case 2:
-            colors.stream().forEach(prop -> prop.put(null));
+            colorLayers.stream().forEach(l -> l.setColor(null));
             break;
         }
         // TODO: Make the layer dialog listen to property change events so that this is not needed any more.
Index: src/org/openstreetmap/josm/gui/layer/GpxLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(working copy)
@@ -4,6 +4,7 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 import static org.openstreetmap.josm.tools.I18n.trn;
 
+import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.Graphics2D;
 import java.awt.event.ActionEvent;
@@ -31,7 +32,6 @@
 import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener;
 import org.openstreetmap.josm.data.gpx.GpxTrack;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
-import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
@@ -54,7 +54,7 @@
 /**
  * A layer that displays data from a Gpx file / the OSM gpx downloads.
  */
-public class GpxLayer extends Layer implements ExpertModeChangeListener {
+public class GpxLayer extends AbstractModifiableLayer implements ExpertModeChangeListener {
 
     /** GPX data */
     public GpxData data;
@@ -108,10 +108,23 @@
     }
 
     @Override
-    protected NamedColorProperty getBaseColorProperty() {
-        return GpxDrawHelper.DEFAULT_COLOR;
+    public Color getColor() {
+        Color[] c = data.getTracks().stream().map(t -> t.getColor()).distinct().toArray(Color[]::new);
+        return c.length == 1 ? c[0] : null; //only return if exactly one distinct color present
     }
 
+    @Override
+    public void setColor(Color color) {
+        for (GpxTrack trk : data.getTracks()) {
+            trk.setColor(color);
+        }
+    }
+
+    @Override
+    public boolean hasColor() {
+        return true;
+    }
+
     /**
      * Returns a human readable string that shows the timespan of the given track
      * @param trk The GPX track for which timespan is displayed
@@ -478,8 +491,18 @@
     public void expertChanged(boolean isExpert) {
         this.isExpertMode = isExpert;
     }
+    
+        @Override
+    public boolean isModified() {
+        return data.isModified();
+    }
 
     @Override
+    public boolean requiresSaveToFile() {
+        return isModified() && isLocalFile();
+    }
+
+    @Override
     public String getChangesetSourceTag() {
         // no i18n for international values
         return "survey";
Index: src/org/openstreetmap/josm/gui/layer/Layer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/Layer.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/layer/Layer.java	(working copy)
@@ -25,9 +25,6 @@
 import org.openstreetmap.josm.actions.SaveAsAction;
 import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
-import org.openstreetmap.josm.data.preferences.AbstractProperty;
-import org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeListener;
-import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
 import org.openstreetmap.josm.gui.MainApplication;
@@ -164,7 +161,6 @@
      */
     private File associatedFile;
 
-    private final ValueChangeListener<Object> invalidateListener = change -> invalidate();
     private boolean isDestroyed;
 
     /**
@@ -198,42 +194,27 @@
     public abstract Icon getIcon();
 
     /**
-     * Gets the color property to use for this layer.
-     * @return The color property.
-     * @since 10824
+     * @return whether the layer has / can handle colors.
      */
-    public AbstractProperty<Color> getColorProperty() {
-        NamedColorProperty base = getBaseColorProperty();
-        if (base != null) {
-            return base.getChildColor(NamedColorProperty.COLOR_CATEGORY_LAYER, getName(), base.getName());
-        } else {
-            return null;
-        }
+    public boolean hasColor() {
+        return false;
     }
 
     /**
-     * Gets the color property that stores the default color for this layer.
-     * @return The property or <code>null</code> if this layer is not colored.
-     * @since 10824
+     * Return the current color of the layer
+     * @return null when not present or not supported
      */
-    protected NamedColorProperty getBaseColorProperty() {
+    public Color getColor() {
         return null;
     }
 
-    private void addColorPropertyListener() {
-        AbstractProperty<Color> colorProperty = getColorProperty();
-        if (colorProperty != null) {
-            colorProperty.addListener(invalidateListener);
-        }
+    /**
+     * Sets the color for this layer. Nothing happens if not supported by the layer
+     * @param color the color to be set
+     */
+    public void setColor(Color color) {
     }
 
-    private void removeColorPropertyListener() {
-        AbstractProperty<Color> colorProperty = getColorProperty();
-        if (colorProperty != null) {
-            colorProperty.removeListener(invalidateListener);
-        }
-    }
-
     /**
      * @return A small tooltip hint about some statistics for this layer.
      */
@@ -302,7 +283,6 @@
         }
         isDestroyed = true;
         // Override in subclasses if needed
-        removeColorPropertyListener();
     }
 
     /**
@@ -339,17 +319,11 @@
      * @param name the name. If null, the name is set to the empty string.
      */
     public void setName(String name) {
-        if (this.name != null) {
-            removeColorPropertyListener();
-        }
         String oldValue = this.name;
         this.name = Optional.ofNullable(name).orElse("");
         if (!this.name.equals(oldValue)) {
             propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
         }
-
-        // re-add listener
-        addColorPropertyListener();
         invalidate();
     }
 
Index: src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(working copy)
@@ -31,6 +31,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -780,11 +781,18 @@
             Collection<Collection<WayPoint>> trk = new ArrayList<>();
             Map<String, Object> trkAttr = new HashMap<>();
 
-            String name = gpxVal(w, "name");
-            if (name != null) {
-                trkAttr.put("name", name);
+            Map<String, String> trkExts = new HashMap<>();
+            for (Entry<String, String> e : w.getKeys().entrySet()) {
+                //String k = e.getKey().replaceFirst("/^" + GpxConstants.GPX_PREFIX + "/", "");
+                String k = e.getKey().startsWith(GpxConstants.GPX_PREFIX) ? e.getKey().substring(GpxConstants.GPX_PREFIX.length()) : e.getKey();
+                String v = e.getValue();
+                if (GpxConstants.RTE_TRK_KEYS.contains(k)) {
+                    trkAttr.put(k, v);
+                } else if (GpxConstants.SUPPORTED_EXTENSION_NAPMESPACES.stream().anyMatch(
+                        s -> k.startsWith(s + ":"))) {
+                    trkExts.put(k, v);
+                }
             }
-
             List<WayPoint> trkseg = null;
             for (Node n : w.getNodes()) {
                 if (!n.isUsable()) {
@@ -801,7 +809,7 @@
                 trkseg.add(nodeToWayPoint(n));
             }
 
-            gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr));
+            gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr, trkExts));
         });
     }
 
Index: src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java	(working copy)
@@ -4,6 +4,7 @@
 import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.awt.Color;
 import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.GridBagLayout;
@@ -12,23 +13,30 @@
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 
 import javax.swing.AbstractAction;
+import javax.swing.JColorChooser;
 import javax.swing.JComponent;
 import javax.swing.JLabel;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
 import javax.swing.JToggleButton;
 import javax.swing.ListSelectionModel;
+import javax.swing.event.TableModelEvent;
 import javax.swing.table.DefaultTableModel;
 import javax.swing.table.TableCellRenderer;
 import javax.swing.table.TableRowSorter;
 
+import org.apache.commons.jcs.access.exception.InvalidArgumentException;
 import org.openstreetmap.josm.data.SystemOfMeasurement;
 import org.openstreetmap.josm.data.gpx.GpxConstants;
 import org.openstreetmap.josm.data.gpx.GpxTrack;
@@ -55,7 +63,7 @@
      * @param layer The associated GPX layer
      */
     public ChooseTrackVisibilityAction(final GpxLayer layer) {
-        super(tr("Choose visible tracks"));
+        super(tr("Choose track visibility and colors"));
         new ImageProvider("dialogs/filter").getResource().attachImageIcon(this, true);
         this.layer = layer;
         putValue("help", ht("/Action/ChooseTrackVisibility"));
@@ -116,12 +124,41 @@
             String time = GpxLayer.getTimespanForTrack(trk);
             TrackLength length = new TrackLength(trk.length());
             String url = (String) Optional.ofNullable(attr.get("url")).orElse("");
-            tracks[i] = new Object[]{name, desc, time, length, url};
+            tracks[i] = new Object[]{name, desc, time, length, url, trk};
             i++;
         }
         return tracks;
     }
 
+    private void showColorDialog(List<GpxTrack> tracks) {
+        Color cl = tracks.stream().filter(Objects::nonNull)
+                .map(GpxTrack::getColor).filter(Objects::nonNull)
+                .findAny().orElse(GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get());
+        JColorChooser c = new JColorChooser(cl);
+        Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
+        int answer = JOptionPane.showOptionDialog(
+                MainApplication.getMainFrame(),
+                c,
+                tr("Choose a color"),
+                JOptionPane.OK_CANCEL_OPTION,
+                JOptionPane.PLAIN_MESSAGE,
+                null,
+                options,
+                options[0]
+        );
+        switch (answer) {
+        case 0:
+            tracks.stream().forEach(t -> t.setColor(c.getColor()));
+            break;
+        case 1:
+            return;
+        case 2:
+            tracks.stream().forEach(t -> t.setColor(null));
+            break;
+        }
+        table.repaint();
+    }
+
     /**
      * Builds an non-editable table whose 5th column will open a browser when double clicked.
      * The table will fill its parent.
@@ -138,6 +175,33 @@
                 if (c instanceof JComponent) {
                     JComponent jc = (JComponent) c;
                     jc.setToolTipText(getValueAt(row, col).toString());
+                    if (content.length > row
+                            && content[row].length > 5
+                            && content[row][5] instanceof GpxTrack) {
+                        Color color = ((GpxTrack) content[row][5]).getColor();
+                        if (color != null) {
+                            double brightness = Math.sqrt(Math.pow(color.getRed(), 2) * .241
+                                    + Math.pow(color.getGreen(), 2) * .691
+                                    + Math.pow(color.getBlue(), 2) * .068);
+                            if (brightness > 250) {
+                                color = color.darker();
+                            }
+                            if (isRowSelected(row)) {
+                                jc.setBackground(color);
+                                if (brightness <= 130) {
+                                    jc.setForeground(Color.WHITE);
+                                } else {
+                                    jc.setForeground(Color.BLACK);
+                                }
+                            } else {
+                                if (brightness > 200) {
+                                    color = color.darker(); //brightness >250 is darkened twice on purpose
+                                }
+                                jc.setForeground(color);
+                                jc.setBackground(Color.WHITE);
+                            }
+                        }
+                    }
                 }
                 return c;
             }
@@ -144,8 +208,29 @@
 
             @Override
             public boolean isCellEditable(int rowIndex, int colIndex) {
-                return false;
+                return colIndex <= 1;
             }
+
+            @Override
+            public void tableChanged(TableModelEvent e) {
+                super.tableChanged(e);
+                int col = e.getColumn();
+                int row = e.getFirstRow();
+                if (row >= 0 && row < content.length && col >= 0 && col <= 1) {
+                    Object t = content[row][5];
+                    String val = (String) getValueAt(row, col);
+                    if (t != null && t instanceof GpxTrack) {
+                        GpxTrack trk = (GpxTrack) t;
+                        if (col == 0) {
+                            trk.put("name", val);
+                        } else {
+                            trk.put("desc", val);
+                        }
+                    } else {
+                        throw new InvalidArgumentException();
+                    }
+                }
+            }
         };
         // define how to sort row
         TableRowSorter<DefaultTableModel> rowSorter = new TableRowSorter<>();
@@ -249,11 +334,12 @@
 
         msg.add(new JLabel(tr("<html>Select all tracks that you want to be displayed. " +
                 "You can drag select a range of tracks or use CTRL+Click to select specific ones. " +
-                "The map is updated live in the background. Open the URLs by double clicking them.</html>")),
+                "The map is updated live in the background. Open the URLs by double clicking them, edit name and description by double clicking the cell.</html>")),
                 GBC.eop().fill(GBC.HORIZONTAL));
         // build table
         final boolean[] trackVisibilityBackup = layer.trackVisibility.clone();
-        table = buildTable(buildTableContents());
+        Object[][] content = buildTableContents();
+        table = buildTable(content);
         selectVisibleTracksInTable();
         listenToSelectionChanges();
         // make the table scrollable
@@ -262,9 +348,26 @@
 
         int v = 1;
         // build dialog
-        ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), tr("Set track visibility for {0}", layer.getName()),
-                tr("Show all"), tr("Show selected only"), tr("Cancel"));
-        ed.setButtonIcons("eye", "dialogs/filter", "cancel");
+        ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(),
+                tr("Set track visibility for {0}", layer.getName()),
+                tr("Set color for selected tracks..."), tr("Show all"), tr("Show selected only"), tr("Cancel")) {
+            @Override
+            protected void buttonAction(int buttonIndex, ActionEvent evt) {
+                if (buttonIndex == 0) {
+                    List<GpxTrack> trks = new ArrayList<>();
+                    for (int i : table.getSelectedRows()) {
+                        Object trk = content[i][5];
+                        if (trk != null && trk instanceof GpxTrack) {
+                            trks.add((GpxTrack) trk);
+                        }
+                    }
+                    showColorDialog(trks);
+                } else {
+                    super.buttonAction(buttonIndex, evt);
+                }
+            }
+        };
+        ed.setButtonIcons("colorchooser", "eye", "dialogs/filter", "cancel");
         ed.setContent(msg, false);
         ed.setDefaultButton(2);
         ed.setCancelButton(3);
@@ -275,15 +378,15 @@
         dateFilter.saveInPrefs();
         v = ed.getValue();
         // cancel for unknown buttons and copy back original settings
-        if (v != 1 && v != 2) {
+        if (v != 2 && v != 3) {
             layer.trackVisibility = Arrays.copyOf(trackVisibilityBackup, layer.trackVisibility.length);
             MainApplication.getMap().repaint();
             return;
         }
-        // set visibility (1 = show all, 2 = filter). If no tracks are selected
+        // set visibility (2 = show all, 3 = filter). If no tracks are selected
         // set all of them visible and...
         ListSelectionModel s = table.getSelectionModel();
-        final boolean all = v == 1 || s.isSelectionEmpty();
+        final boolean all = v == 2 || s.isSelectionEmpty();
         for (int i = 0; i < layer.trackVisibility.length; i++) {
             layer.trackVisibility[table.convertRowIndexToModel(i)] = all || s.isSelectedIndex(i);
         }
@@ -290,6 +393,6 @@
         // layer has been changed
         layer.invalidate();
         // ...sync with layer visibility instead to avoid having two ways to hide everything
-        layer.setVisible(v == 1 || !s.isSelectionEmpty());
+        layer.setVisible(v == 2 || !s.isSelectionEmpty());
     }
 }
Index: src/org/openstreetmap/josm/gui/layer/gpx/ConvertFromGpxLayerAction.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/gpx/ConvertFromGpxLayerAction.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/layer/gpx/ConvertFromGpxLayerAction.java	(working copy)
@@ -10,6 +10,7 @@
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 
 import javax.swing.BorderFactory;
@@ -19,6 +20,7 @@
 import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 
+import org.openstreetmap.josm.data.gpx.Extensions;
 import org.openstreetmap.josm.data.gpx.GpxConstants;
 import org.openstreetmap.josm.data.gpx.GpxTrack;
 import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
@@ -25,6 +27,7 @@
 import org.openstreetmap.josm.data.gpx.WayPoint;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.gui.ExtendedDialog;
 import org.openstreetmap.josm.gui.MainApplication;
@@ -64,29 +67,13 @@
                 List<Node> nodes = new ArrayList<>();
                 for (WayPoint p : segment.getWayPoints()) {
                     Node n = new Node(p.getCoor());
-                    for (Entry<String, Object> entry : p.attr.entrySet()) {
-                        String key = entry.getKey();
-                        Object obj = p.get(key);
-                        if (check && !keys.contains(key) && (obj instanceof String || obj instanceof Number || obj instanceof Date)) {
-                            keys.add(key);
-                        }
-                        if (!none && (obj instanceof String || obj instanceof Number)) {
-                            // only convert when required
-                            n.put(GpxConstants.GPX_PREFIX + key, obj.toString());
-                        } else if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) {
-                            // timestamps should always be converted
-                            Date date = (Date) obj;
-                            if (!none) { //... but the tag will only be set when required
-                                n.put(GpxConstants.GPX_PREFIX + key, DateUtils.fromDate(date));
-                            }
-                            n.setTimestamp(date);
-                        }
-                    }
+                    addAttributes(p.attr, n, keys, check, none);
                     ds.addPrimitive(n);
                     nodes.add(n);
                 }
                 Way w = new Way();
                 w.setNodes(nodes);
+                addAttributes(trk.getAttributes(), w, keys, check, none);
                 ds.addPrimitive(w);
             }
         }
@@ -122,6 +109,37 @@
         return ds;
     }
 
+    private static void addAttributes(Map<String, Object> attr, OsmPrimitive n, List<String> keys, boolean check, boolean none) {
+        for (Entry<String, Object> entry : attr.entrySet()) {
+            String key = entry.getKey();
+            Object obj = entry.getValue();
+            if (check && !keys.contains(key) && (obj instanceof String || obj instanceof Number || obj instanceof Date)) {
+                keys.add(key);
+            }
+            if (!none && (obj instanceof String || obj instanceof Number)) {
+                // only convert when required
+                n.put(GpxConstants.GPX_PREFIX + key, obj.toString());
+            } else if (!none && obj instanceof Extensions) {
+                Extensions ext = (Extensions) obj;
+                String pre = ext.getPrefix();
+                for (Entry<String, String> e : ext.entrySet()) {
+                    String k = pre + e.getKey();
+                    if (check && !keys.contains(k)) {
+                        keys.add(k);
+                    }
+                    n.put(k, e.getValue());
+                }
+            } else if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) {
+                // timestamps should always be converted
+                Date date = (Date) obj;
+                if (!none) { //... but the tag will only be set when required
+                    n.put(GpxConstants.GPX_PREFIX + key, DateUtils.fromDate(date));
+                }
+                n.setTimestamp(date);
+            }
+        }
+    }
+
     /**
      * Filters the tags of the given {@link DataSet}
      * @param ds The {@link DataSet}
@@ -131,10 +149,10 @@
      */
     public DataSet filterDataSet(DataSet ds, List<String> listPos) {
         Collection<Node> nodes = ds.getNodes();
-        for (Node n : nodes) {
-            for (String key : n.keySet()) {
+        for (OsmPrimitive p : ds.getPrimitives(p -> p instanceof Node || p instanceof Way)) {
+            for (String key : p.keySet()) {
                 if (listPos == null || !listPos.contains(key.substring(GpxConstants.GPX_PREFIX.length()))) {
-                    n.put(key, null);
+                   p.put(key, null);
                 }
             }
         }
Index: src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java	(working copy)
@@ -68,7 +68,7 @@
      * The color that is used for drawing GPX points.
      * @since 10824
      */
-    public static final NamedColorProperty DEFAULT_COLOR = new NamedColorProperty(marktr("gps point"), Color.magenta);
+    public static final NamedColorProperty DEFAULT_COLOR_PROPERTY = new NamedColorProperty(marktr("gps point"), Color.magenta);
 
     private final GpxData data;
     private final GpxLayer layer;
@@ -273,23 +273,6 @@
     }
 
     /**
-     * Get the default color for gps tracks for specified layer
-     * @param layerName name of the GpxLayer
-     * @param ignoreCustom do not use preferences
-     * @return the color or null if the color is not constant
-     */
-    public Color getColor(String layerName, boolean ignoreCustom) {
-        if (ignoreCustom || getColorMode(layerName) == ColorMode.NONE) {
-            return DEFAULT_COLOR.getChildColor(
-                    NamedColorProperty.COLOR_CATEGORY_LAYER,
-                    layerName,
-                    DEFAULT_COLOR.getName()).get();
-        } else {
-            return null;
-        }
-    }
-
-    /**
      * Read coloring mode for specified layer from preferences
      * @param layerName name of the GpxLayer
      * @return coloring mode
@@ -304,13 +287,6 @@
         return ColorMode.NONE;
     }
 
-    /** Reads generic color from preferences (usually gray)
-     * @return the color
-     **/
-    public static Color getGenericColor() {
-        return DEFAULT_COLOR.get();
-    }
-
     /**
      * Read all drawing-related settings from preferences
      * @param layerName layer name used to access its specific preferences
@@ -352,8 +328,7 @@
 
         // shrink to range
         heatMapDrawGain = Utils.clamp(heatMapDrawGain, -10, 10);
-
-        neutralColor = getColor(layerName, true);
+        neutralColor = DEFAULT_COLOR_PROPERTY.get();
         velocityScale.setNoDataColor(neutralColor);
         dateScale.setNoDataColor(neutralColor);
         hdopScale.setNoDataColor(neutralColor);
@@ -594,7 +569,7 @@
             }
             for (WayPoint trkPnt : segment) {
                 LatLon c = trkPnt.getCoor();
-                trkPnt.customColoring = neutralColor;
+                trkPnt.customColoring = segment.getColor();
                 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
                     continue;
                 }
@@ -642,7 +617,7 @@
                     }
                 } else { // make sure we reset outdated data
                     trkPnt.drawLine = false;
-                    color = neutralColor;
+                    color = segment.getColor();
                 }
                 if (color != null) {
                     trkPnt.customColoring = color;
Index: src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java	(working copy)
@@ -103,8 +103,8 @@
         GpxLink link = new GpxLink(audioUrl.toString());
         link.type = "audio";
         wpt.put(GpxConstants.META_LINKS, Collections.singleton(link));
-        wpt.addExtension("offset", Double.toString(offset));
-        wpt.addExtension("sync-offset", Double.toString(syncOffset));
+        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "offset", Double.toString(offset));
+        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "sync-offset", Double.toString(syncOffset));
         return wpt;
     }
 }
Index: src/org/openstreetmap/josm/gui/layer/markerlayer/DefaultMarkerProducers.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/markerlayer/DefaultMarkerProducers.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/layer/markerlayer/DefaultMarkerProducers.java	(working copy)
@@ -45,10 +45,10 @@
             return Collections.singleton(marker);
         } else if (Utils.hasExtension(urlStr, "wav", "mp3", "aac", "aif", "aiff")) {
             final AudioMarker audioMarker = new AudioMarker(wpt.getCoor(), wpt, url, parentLayer, time, offset);
-            Extensions exts = (Extensions) wpt.get(GpxConstants.META_EXTENSIONS);
-            if (exts != null && exts.containsKey("offset")) {
+            Extensions josmExts = (Extensions) wpt.get(GpxConstants.EXTENSIONS_JOSM);
+            if (josmExts != null && josmExts.containsKey("offset")) {
                 try {
-                    audioMarker.syncOffset = Double.parseDouble(exts.get("sync-offset"));
+                    audioMarker.syncOffset = Double.parseDouble(josmExts.get("sync-offset"));
                 } catch (NumberFormatException nfe) {
                     Logging.warn(nfe);
                 }
Index: src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java	(working copy)
@@ -257,7 +257,7 @@
         WayPoint wpt = new WayPoint(getCoor());
         wpt.setTimeInMillis((long) (time * 1000));
         if (text != null) {
-            wpt.addExtension("text", text);
+            wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "text", text);
         } else if (dataProvider != null) {
             for (String key : dataProvider.getTemplateKeys()) {
                 Object value = dataProvider.getTemplateValue(key, false);
Index: src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java	(working copy)
@@ -77,7 +77,10 @@
     public AudioMarker syncAudioMarker;
 
     private static final Color DEFAULT_COLOR = Color.magenta;
-    private static final NamedColorProperty COLOR_PROPERTY = new NamedColorProperty(marktr("gps marker"), DEFAULT_COLOR);
+    /**
+     * The color that is used for drawing markers.
+     */
+    public static final NamedColorProperty DEFAULT_COLOR_PROPERTY = new NamedColorProperty(marktr("gps marker"), DEFAULT_COLOR);
 
     /**
      * Constructs a new {@code MarkerLayer}.
@@ -123,10 +126,10 @@
             // audio file) calculate the offset relative to the first marker of
             // that group. This way the user can jump to the corresponding
             // playback positions in a long audio track.
-            Extensions exts = (Extensions) wpt.get(GpxConstants.META_EXTENSIONS);
-            if (exts != null && exts.containsKey("offset")) {
+            Extensions josmExts = (Extensions) wpt.get(GpxConstants.EXTENSIONS_JOSM);
+            if (josmExts != null && josmExts.containsKey("offset")) {
                 try {
-                    offset = Double.valueOf(exts.get("offset"));
+                    offset = Double.valueOf(josmExts.get("offset"));
                 } catch (NumberFormatException nfe) {
                     Logging.warn(nfe);
                 }
@@ -173,20 +176,9 @@
     }
 
     @Override
-    protected NamedColorProperty getBaseColorProperty() {
-        return COLOR_PROPERTY;
-    }
-
-    /* for preferences */
-    public static Color getGenericColor() {
-        return COLOR_PROPERTY.get();
-    }
-
-    @Override
     public void paint(Graphics2D g, MapView mv, Bounds box) {
         boolean showTextOrIcon = isTextOrIconShown();
-        g.setColor(getColorProperty().get());
-
+        g.setColor(DEFAULT_COLOR_PROPERTY.get());
         if (mousePressed) {
             boolean mousePressedTmp = mousePressed;
             Point mousePos = mv.getMousePosition(); // Get mouse position only when necessary (it's the slowest part of marker layer painting)
Index: src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java
===================================================================
--- src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java	(working copy)
@@ -390,8 +390,8 @@
         PaintColors.values();
         ConflictColors.getColors();
         Severity.getColors();
-        MarkerLayer.getGenericColor();
-        GpxDrawHelper.getGenericColor();
+        MarkerLayer.DEFAULT_COLOR_PROPERTY.get();
+        GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get();
         OsmDataLayer.getOutsideColor();
         MapScaler.getColor();
         MapStatus.getColors();
Index: src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java
===================================================================
--- src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java	(revision 15439)
+++ src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java	(working copy)
@@ -4,7 +4,6 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 import static org.openstreetmap.josm.tools.I18n.trc;
 
-import java.awt.Color;
 import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.GridBagLayout;
@@ -24,7 +23,6 @@
 
 import org.openstreetmap.josm.actions.ExpertToggleAction;
 import org.openstreetmap.josm.data.PreferencesUtils;
-import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper;
 import org.openstreetmap.josm.gui.layer.markerlayer.Marker;
@@ -331,15 +329,10 @@
             if (null != dim) {
                 // get image size of environment
                 final int iconSize = (int) dim.getHeight();
-                final Color color;
-                // ask the GPX draw for the correct color of that layer ( if there is one )
-                if (null != layerName) {
-                    color = GpxDrawHelper.DEFAULT_COLOR.getChildColor(
-                            NamedColorProperty.COLOR_CATEGORY_LAYER, layerName, GpxDrawHelper.DEFAULT_COLOR.getName()).get();
-                } else {
-                    color = GpxDrawHelper.DEFAULT_COLOR.getDefaultValue();
-                }
-                colorTypeHeatIconLabel.setIcon(GpxDrawHelper.getColorMapImageIcon(color, colorTypeHeatMapTune.getSelectedIndex(), iconSize));
+                colorTypeHeatIconLabel.setIcon(GpxDrawHelper.getColorMapImageIcon(
+                        GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get(),
+                        colorTypeHeatMapTune.getSelectedIndex(),
+                        iconSize));
             }
         });
 
Index: src/org/openstreetmap/josm/io/GpxReader.java
===================================================================
--- src/org/openstreetmap/josm/io/GpxReader.java	(revision 15439)
+++ src/org/openstreetmap/josm/io/GpxReader.java	(working copy)
@@ -76,7 +76,7 @@
         private State currentState = State.INIT;
 
         private GpxLink currentLink;
-        private Extensions currentExtensions;
+        private Map<String, String> currentExtensionMap = new HashMap<>();
         private Stack<State> states;
         private final Stack<String> elements = new Stack<>();
 
@@ -159,7 +159,6 @@
                 case "extensions":
                     states.push(currentState);
                     currentState = State.EXT;
-                    currentExtensions = new Extensions();
                     break;
                 case "gpx":
                     if (atts.getValue("creator") != null && atts.getValue("creator").startsWith("Nokia Sports Tracker")) {
@@ -178,7 +177,6 @@
                 case "extensions":
                     states.push(currentState);
                     currentState = State.EXT;
-                    currentExtensions = new Extensions();
                     break;
                 case "copyright":
                     states.push(currentState);
@@ -228,7 +226,6 @@
                 case "extensions":
                     states.push(currentState);
                     currentState = State.EXT;
-                    currentExtensions = new Extensions();
                     break;
                 default: // Do nothing
                 }
@@ -250,7 +247,6 @@
                 case "extensions":
                     states.push(currentState);
                     currentState = State.EXT;
-                    currentExtensions = new Extensions();
                     break;
                 default: // Do nothing
                 }
@@ -270,7 +266,6 @@
                 case "extensions":
                     states.push(currentState);
                     currentState = State.EXT;
-                    currentExtensions = new Extensions();
                     break;
                 default: // Do nothing
                 }
@@ -349,9 +344,7 @@
                     if ((currentState == State.METADATA && "metadata".equals(localName)) ||
                         (currentState == State.GPX && "gpx".equals(localName))) {
                         convertUrlToLink(data.attr);
-                        if (currentExtensions != null && !currentExtensions.isEmpty()) {
-                            data.put(META_EXTENSIONS, currentExtensions);
-                        }
+                        data.addExtensions(currentExtensionMap);
                         currentState = states.pop();
                     }
                     break;
@@ -464,10 +457,9 @@
                 case "wpt":
                     currentState = states.pop();
                     convertUrlToLink(currentWayPoint.attr);
-                    if (currentExtensions != null && !currentExtensions.isEmpty()) {
-                        currentWayPoint.put(META_EXTENSIONS, currentExtensions);
-                    }
+                    currentWayPoint.addExtensions(currentExtensionMap);
                     data.waypoints.add(currentWayPoint);
+                    currentExtensionMap = new HashMap<>();
                     break;
                 default: // Do nothing
                 }
@@ -483,7 +475,9 @@
                 case "trk":
                     currentState = states.pop();
                     convertUrlToLink(currentTrackAttr);
-                    data.addTrack(new ImmutableGpxTrack(currentTrack, currentTrackAttr));
+                    ImmutableGpxTrack trk = new ImmutableGpxTrack(currentTrack, currentTrackAttr, currentExtensionMap);
+                    data.addTrack(trk);
+                    currentExtensionMap = new HashMap<>();
                     break;
                 case "name":
                 case "cmt":
@@ -499,11 +493,11 @@
                 }
                 break;
             case EXT:
+                String acc;
                 if ("extensions".equals(localName)) {
                     currentState = states.pop();
-                } else if (JOSM_EXTENSIONS_NAMESPACE_URI.equals(namespaceURI)) {
-                    // only interested in extensions written by JOSM
-                    currentExtensions.put(localName, accumulator.toString());
+                } else if ((acc = accumulator.toString().trim()).length() > 0) {
+                    currentExtensionMap.put(qName, acc);
                 }
                 break;
             default:
@@ -519,6 +513,7 @@
                 default: // Do nothing
                 }
             }
+            accumulator.setLength(0);
         }
 
         @Override
@@ -525,8 +520,8 @@
         public void endDocument() throws SAXException {
             if (!states.empty())
                 throw new SAXException(tr("Parse error: invalid document structure for GPX document."));
-            Extensions metaExt = (Extensions) data.get(META_EXTENSIONS);
-            if (metaExt != null && "true".equals(metaExt.get("from-server"))) {
+            Extensions josmMetaExt = (Extensions) data.get(EXTENSIONS_JOSM);
+            if (josmMetaExt != null && "true".equals(josmMetaExt.get("from-server"))) {
                 data.fromServer = true;
             }
             gpxData = data;
Index: src/org/openstreetmap/josm/io/GpxWriter.java
===================================================================
--- src/org/openstreetmap/josm/io/GpxWriter.java	(revision 15439)
+++ src/org/openstreetmap/josm/io/GpxWriter.java	(working copy)
@@ -3,13 +3,17 @@
 
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.awt.Color;
 import java.io.BufferedWriter;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -59,11 +63,24 @@
     private static final int ROUTE_POINT = 1;
     private static final int TRACK_POINT = 2;
 
+    private HashMap<String, Entry<String, String>> extlinks = new HashMap<>();
+
     /**
      * Writes the given GPX data.
      * @param data The data to write
      */
     public void write(GpxData data) {
+        write(data, EXTENSIONS_DRAWING);
+    }
+
+    /**
+     * Writes the given GPX data.
+     *
+     * @param data The data to write
+     * @param colorFormat determines if colors are saved and which extension is to be used, can be
+     * {@link GpxConstants#EXTENSIONS_GARMIN}, {@link GpxConstants#EXTENSIONS_DRAWING} or <code>null</code>
+     */
+    public void write(GpxData data, String colorFormat) {
         this.data = data;
         // We write JOSM specific meta information into gpx 'extensions' elements.
         // In particular it is noted whether the gpx data is from the OSM server
@@ -71,22 +88,118 @@
         // and some extra synchronization info for export of AudioMarkers.
         // It is checked in advance, if any extensions are used, so we know whether
         // a namespace declaration is necessary.
-        boolean hasExtensions = data.fromServer;
-        if (!hasExtensions) {
+
+        boolean hasJosmExtension = data.fromServer,
+                hasGpxxExtension = false,
+                hasGpxdExtension = false,
+                searchGpxx = EXTENSIONS_GARMIN.equals(colorFormat),
+                searchGpxd = EXTENSIONS_DRAWING.equals(colorFormat);
+
+        if (!hasJosmExtension) {
             for (WayPoint wpt : data.waypoints) {
-                Extensions extensions = (Extensions) wpt.get(META_EXTENSIONS);
+                Extensions extensions = (Extensions) wpt.get(EXTENSIONS_JOSM);
                 if (extensions != null && !extensions.isEmpty()) {
-                    hasExtensions = true;
+                    hasJosmExtension = true;
                     break;
                 }
             }
         }
 
+        if (searchGpxx || searchGpxd) {
+
+            HashMap<Color, String> closestColorCache = new HashMap<>();
+
+            for (GpxTrack trk : data.getTracks()) {
+                Extensions gpxx = (Extensions) trk.get(EXTENSIONS_GARMIN),
+                           gpxd = (Extensions) trk.get(EXTENSIONS_DRAWING);
+                String gpxxC = null, gpxdC = null;
+
+                if (gpxd != null) {
+                    gpxdC = gpxd.get("color");
+                }
+                if (gpxx != null) {
+                    gpxxC = gpxx.get("displaycolor");
+                }
+
+                if (searchGpxd && gpxdC == null && gpxx != null && gpxxC != null) {
+                    //Convert GPXX to GPXD
+                    Color c = GARMIN_COLORS.get(gpxxC);
+                    if (c != null) {
+                        //Remove GPXX
+                        trk.removeExtensionKey(EXTENSIONS_GARMIN, "displaycolor");
+                        //Put GPXD
+                        trk.setColor(c);
+                        hasGpxdExtension = true;
+                    } else {
+                        Logging.warn("Could not read garmin color: " + gpxxC);
+                    }
+                } else if (searchGpxx && gpxxC == null && gpxd != null && gpxdC != null) {
+                    //Convert GPXD to GPXX
+                    try {
+                        Color c = Color.decode(gpxdC);
+                        //Remove GPXD
+                        trk.removeExtensionKey(EXTENSIONS_DRAWING, "color");
+                        //Put GPXX
+                        String colorString = null;
+                        double closestDiff = -1;
+
+                        if (closestColorCache.containsKey(c)) {
+                            colorString = closestColorCache.get(c);
+                        } else {
+                            //find closest garmin color
+                            for (Entry<String, Color> e : GARMIN_COLORS.entrySet()) {
+                                double diff = colorDist(e.getValue(), c);
+                                if (closestDiff < 0 || diff < closestDiff) {
+                                    colorString = e.getKey();
+                                    closestDiff = diff;
+                                    if (closestDiff == 0) break;
+                                }
+                            }
+                            closestColorCache.put(c, colorString);
+                        }
+                        trk.addExtensionKey(EXTENSIONS_GARMIN, "DisplayColor", colorString);
+                        hasGpxxExtension = true;
+                    } catch (NumberFormatException ex) {
+                        Logging.warn("Could not read gpxd color: " + gpxdC);
+                    }
+                }
+                //Must be checked again because of conversion above
+                if (!hasGpxdExtension && gpxd != null && !gpxd.isEmpty()) {
+                    hasGpxdExtension = true;
+                }
+                if (!hasGpxxExtension && gpxx != null && !gpxx.isEmpty()) {
+                    hasGpxxExtension = true;
+                }
+            }
+        }
+
         out.println("<?xml version='1.0' encoding='UTF-8'?>");
         out.println("<gpx version=\"1.1\" creator=\"JOSM GPX export\" xmlns=\"http://www.topografix.com/GPX/1/1\"");
-        out.println((hasExtensions ? String.format("    xmlns:josm=\"%s\"%n", JOSM_EXTENSIONS_NAMESPACE_URI) : "") +
-                    "    xmlns:xsi=\""+XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI+"\"");
-        out.println("    xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">");
+
+        String schemaLocations = "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd";
+
+        if (hasJosmExtension) {
+            extlinks.put(GpxConstants.EXTENSIONS_JOSM, new SimpleEntry<>(GpxConstants.XML_URI_EXTENSIONS_JOSM, GpxConstants.XML_XSD_EXTENSIONS_JOSM));
+        }
+        if (hasGpxdExtension) {
+            extlinks.put(GpxConstants.EXTENSIONS_DRAWING, new SimpleEntry<>(GpxConstants.XML_URI_EXTENSIONS_DRAWING, GpxConstants.XML_XSD_EXTENSIONS_DRAWING));
+        }
+        if (hasGpxxExtension) {
+            extlinks.put(GpxConstants.EXTENSIONS_GARMIN, new SimpleEntry<>(GpxConstants.XML_URI_EXTENSIONS_GARMIN, GpxConstants.XML_XSD_EXTENSIONS_GARMIN));
+        }
+
+        for (Entry<String, Entry<String, String>> e : extlinks.entrySet()) {
+            String k = e.getKey();
+            Entry<String, String> v = e.getValue();
+            if (!k.startsWith(EXTENSIONS_PREFIX)) {
+                throw new Extensions.InvalidExtensionException();
+            }
+            out.println(String.format("    xmlns:%s=\"%s\"", k.substring(k.lastIndexOf(".") + 1), v.getKey()));
+            schemaLocations += " " + v.getKey() + " " + v.getValue();
+        }
+
+        out.println("    xmlns:xsi=\""+XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI+"\"");
+        out.println(String.format("    xsi:schemaLocation=\"%s\">", schemaLocations));
         indent = "  ";
         writeMetaData();
         writeWayPoints();
@@ -97,6 +210,7 @@
     }
 
     private void writeAttr(IWithAttributes obj, List<String> keys) {
+        List<Extensions> allExtensions = new ArrayList<>();
         for (String key : keys) {
             if (META_LINKS.equals(key)) {
                 Collection<GpxLink> lValue = obj.<GpxLink>getCollection(key);
@@ -105,10 +219,11 @@
                         gpxLink(link);
                     }
                 }
-            } else if (META_EXTENSIONS.equals(key)) {
-                Extensions extensions = (Extensions) obj.get(key);
-                if (extensions != null) {
-                    gpxExtensions(extensions);
+            } else if (key != null && key.startsWith(EXTENSIONS_PREFIX)) {
+                Extensions ex = (Extensions) obj.get(key);
+                if (ex != null && !ex.isEmpty()) {
+                    //ex.setType(key);
+                    allExtensions.add(ex);
                 }
             } else {
                 String value = obj.getString(key);
@@ -126,8 +241,17 @@
                 }
             }
         }
+        gpxExtensions(allExtensions);
     }
 
+    double colorDist(Color c1, Color c2) {
+        // Simple Euclidean distance between two colors
+        return Math.sqrt(Math.pow(c1.getRed() - c2.getRed(), 2)
+                + Math.pow(c1.getGreen() - c2.getGreen(), 2)
+                + Math.pow(c1.getBlue() - c2.getBlue(), 2));
+    }
+
+
     private void writeMetaData() {
         Map<String, Object> attr = data.attr;
         openln("metadata");
@@ -318,11 +442,25 @@
         }
     }
 
-    private void gpxExtensions(Extensions extensions) {
-        if (extensions != null && !extensions.isEmpty()) {
+    private void gpxExtensions(List<Extensions> allExtensions) {
+        if (!allExtensions.isEmpty()) {
             openln("extensions");
-            for (Entry<String, String> e : extensions.entrySet()) {
-                simpleTag("josm:" + e.getKey(), e.getValue());
+            for (Extensions extensions : allExtensions) {
+                if (extlinks.containsKey(extensions.getType())) {
+                    //making sure links were actually added / filter if no colors are to be saved at all.
+                    //TODO: probably makes more sense to do the conversion here instead of in the beginning
+                    //note that the colors will actually change during the conversion to GPXX
+                    boolean garmin = EXTENSIONS_GARMIN.equals(extensions.getType());
+                    if (garmin) { //allow nested Garmin TrackExtension. Not ideal but does the job.
+                        openln("gpxx:TrackExtension");
+                    }
+                    for (Entry<String, String> e : extensions.entrySet()) {
+                        simpleTag(extensions.getPrefix() + e.getKey(), e.getValue());
+                    }
+                    if (garmin) {
+                        closeln("gpxx:TrackExtension");
+                    }
+                }
             }
             closeln("extensions");
         }
Index: test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java	(revision 15439)
+++ test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java	(working copy)
@@ -473,7 +473,7 @@
     public void testEqualsContract() {
         TestUtils.assumeWorkingEqualsVerifier();
         EqualsVerifier.forClass(GpxData.class).usingGetClass()
-            .withIgnoredFields("attr", "creator", "fromServer", "storageFile", "listeners", "tracks", "routes", "waypoints", "proxy", "segSpans")
+            .withIgnoredFields("attr", "creator", "fromServer", "storageFile", "listeners", "tracks", "routes", "waypoints", "proxy", "segSpans", "modified")
             .withPrefabValues(WayPoint.class, new WayPoint(LatLon.NORTH_POLE), new WayPoint(LatLon.SOUTH_POLE))
             .withPrefabValues(ListenerList.class, ListenerList.create(), ListenerList.create())
             .verify();
Index: test/unit/org/openstreetmap/josm/data/gpx/ImmutableGpxTrackTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/data/gpx/ImmutableGpxTrackTest.java	(revision 15439)
+++ test/unit/org/openstreetmap/josm/data/gpx/ImmutableGpxTrackTest.java	(working copy)
@@ -1,10 +1,19 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.gpx;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.openstreetmap.josm.TestUtils;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.openstreetmap.josm.tools.ListenerList;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import nl.jqno.equalsverifier.EqualsVerifier;
@@ -23,6 +32,28 @@
     public JOSMTestRules test = new JOSMTestRules();
 
     /**
+     * Tests weather the track can read and write colors.
+     */
+    @Test
+    public void testColors() {
+        GpxTrack trk = new ImmutableGpxTrack(new ArrayList<GpxTrackSegment>(), new HashMap<>());
+        trk.addExtensions(Map.of("gpxd:color", "#FF0000"));
+        assertEquals(trk.getColor(), Color.RED);
+        trk.addExtensionKey(GpxConstants.EXTENSIONS_DRAWING, "color", "#00FF00");
+        assertEquals(trk.getColor(), Color.GREEN);
+        trk.removeExtensionKey(GpxConstants.EXTENSIONS_DRAWING, "color");
+        assertNull(trk.getColor());
+        trk.addExtensionKey(GpxConstants.EXTENSIONS_GARMIN, "DisplayColor", "Blue");
+        assertEquals(trk.getColor(), Color.BLUE);
+        trk.setColor(null);
+        assertNull(trk.getColor());
+        trk.addExtensions(Map.of("gpxx:DisplayColor", "Cyan"));
+        assertEquals(trk.getColor(), Color.CYAN);
+        trk.setColor(Color.YELLOW);
+        assertEquals(trk.getColor(), Color.YELLOW);
+    }
+
+    /**
      * Unit test of methods {@link ImmutableGpxTrack#equals} and {@link ImmutableGpxTrack#hashCode}.
      */
     @Test
@@ -30,7 +61,8 @@
         TestUtils.assumeWorkingEqualsVerifier();
         EqualsVerifier.forClass(ImmutableGpxTrack.class).usingGetClass()
             .suppress(Warning.NONFINAL_FIELDS)
-            .withIgnoredFields("bounds", "length")
+            .withPrefabValues(ListenerList.class, ListenerList.create(), ListenerList.create())
+            .withIgnoredFields("bounds", "length", "colorCache", "listeners")
             .verify();
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/layer/GpxLayerTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/gui/layer/GpxLayerTest.java	(revision 15439)
+++ test/unit/org/openstreetmap/josm/gui/layer/GpxLayerTest.java	(working copy)
@@ -3,6 +3,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.awt.Color;
@@ -10,6 +11,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.TimeZone;
 
 import javax.swing.JScrollPane;
@@ -18,6 +20,7 @@
 import org.junit.Test;
 import org.openstreetmap.josm.TestUtils;
 import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
 import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
 import org.openstreetmap.josm.data.gpx.WayPoint;
 import org.openstreetmap.josm.data.osm.DataSet;
@@ -73,15 +76,19 @@
     @Test
     public void testGpxLayer() throws Exception {
         GpxLayer layer = new GpxLayer(new GpxData(), "foo", false);
+        ImmutableGpxTrack trk = new ImmutableGpxTrack(new ArrayList<GpxTrackSegment>(), new HashMap<>());
+        trk.addExtensions(Map.of("gpxd:color", "#FF0000"));
+        layer.data.addTrack(trk);
+
         assertEquals("foo", layer.getName());
         assertFalse(layer.isLocalFile());
-        assertEquals(Color.MAGENTA, layer.getColorProperty().get());
-        assertEquals("<html>0 tracks (0 segments), 0 routes, 0 waypoints<br>Length: < 0.01 m<br></html>", layer.getToolTipText());
+        assertEquals(layer.getColor(), Color.RED);
+        assertEquals("<html>1 track (0 segments), 0 routes, 0 waypoints<br>Length: < 0.01 m<br></html>", layer.getToolTipText());
 
         GpxLayer layer2 = new GpxLayer(new GpxData(), "bar", true);
         assertEquals("bar", layer2.getName());
         assertTrue(layer2.isLocalFile());
-        assertEquals(Color.MAGENTA, layer2.getColorProperty().get());
+        assertNull(layer2.getColor());
         assertEquals("<html>0 tracks (0 segments), 0 routes, 0 waypoints<br>Length: < 0.01 m<br></html>", layer2.getToolTipText());
 
         assertTrue(layer.checkSaveConditions());
Index: test/unit/org/openstreetmap/josm/gui/layer/LayerTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/gui/layer/LayerTest.java	(revision 15439)
+++ test/unit/org/openstreetmap/josm/gui/layer/LayerTest.java	(working copy)
@@ -7,14 +7,11 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import java.awt.Color;
 import java.io.File;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.openstreetmap.josm.data.preferences.AbstractProperty;
-import org.openstreetmap.josm.data.preferences.NamedColorProperty;
 import org.openstreetmap.josm.data.projection.ProjectionRegistry;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
 
@@ -43,25 +40,6 @@
     }
 
     /**
-     * Test {@link Layer#getColorProperty()}
-     */
-    @Test
-    public void testGetColorProperty() {
-        assertEquals(null, testLayer.getColorProperty());
-
-        AbstractProperty<Color> color = new LayerManagerTest.TestLayer() {
-            @Override
-            protected NamedColorProperty getBaseColorProperty() {
-                return new NamedColorProperty("x", Color.BLACK);
-            }
-        }.getColorProperty();
-
-        assertEquals(Color.BLACK, color.get());
-        assertEquals(Color.BLACK, color.getDefaultValue());
-        assertEquals("clr.layer.Test Layer.x", color.getKey());
-    }
-
-    /**
      * Test of {@link Layer#isInfoResizable}
      */
     @Test
@@ -97,12 +75,7 @@
         testLayer.setName("Test Layer2");
         assertEquals("Test Layer2", testLayer.getName());
 
-        testLayer = new LayerManagerTest.TestLayer() {
-            @Override
-            public AbstractProperty<Color> getColorProperty() {
-                return new NamedColorProperty("test", Color.RED);
-            }
-        };
+        testLayer = new LayerManagerTest.TestLayer();
 
         testLayer.setName("Test Layer2");
         testLayer.setName(null);
Index: test/unit/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarkerTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarkerTest.java	(revision 15439)
+++ test/unit/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarkerTest.java	(working copy)
@@ -45,7 +45,7 @@
         assertEquals("2", marker.getText());
         WayPoint wpt = marker.convertToWayPoint();
         assertEquals(LatLon.ZERO, wpt.getCoor());
-        Extensions ext = (Extensions) wpt.get(GpxConstants.META_EXTENSIONS);
+        Extensions ext = (Extensions) wpt.get(GpxConstants.EXTENSIONS_JOSM);
         assertEquals("2.0", ext.get("offset"));
     }
 }
Index: test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java	(revision 15439)
+++ test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java	(working copy)
@@ -3,9 +3,9 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import java.awt.Color;
 import java.util.Arrays;
 
 import org.junit.Before;
@@ -16,7 +16,10 @@
 import org.openstreetmap.josm.data.gpx.GpxData;
 import org.openstreetmap.josm.data.gpx.GpxLink;
 import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
 
@@ -28,7 +31,7 @@
 public class MarkerLayerTest {
 
     /**
-     * Setup tests
+     * For creating layers
      */
     @Rule
     @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
@@ -47,12 +50,12 @@
      */
     @Test
     public void testMarkerLayer() {
-        assertEquals(Color.magenta, MarkerLayer.getGenericColor());
+        //assertEquals(Color.magenta, MarkerLayer.getGenericColor());
         MarkerLayer layer = new MarkerLayer(new GpxData(), "foo", null, null);
         MainApplication.getLayerManager().addLayer(layer);
 
         assertEquals("foo", layer.getName());
-        assertEquals(Color.magenta, layer.getColorProperty().get());
+        assertNull(layer.getColor());
         assertNotNull(layer.getIcon());
         assertEquals("0 markers", layer.getToolTipText());
         assertEquals("<html>foo consists of 0 markers</html>", layer.getInfoComponent());
@@ -61,18 +64,38 @@
         GpxData gpx = new GpxData();
         WayPoint wpt = new WayPoint(LatLon.ZERO);
         wpt.attr.put(GpxConstants.META_LINKS, Arrays.asList(new GpxLink("https://josm.openstreetmap.de")));
-        wpt.addExtension("offset", "1.0");
+        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "offset", "1.0");
         gpx.waypoints.add(wpt);
         wpt = new WayPoint(LatLon.ZERO);
-        wpt.addExtension("offset", "NaN");
+        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "offset", "NaN");
         gpx.waypoints.add(wpt);
         layer = new MarkerLayer(gpx, "bar", null, null);
 
         assertEquals("bar", layer.getName());
-        assertEquals(Color.magenta, layer.getColorProperty().get());
+        assertNull(layer.getColor());
         assertNotNull(layer.getIcon());
         assertEquals("3 markers", layer.getToolTipText());
         assertEquals("<html>bar consists of 3 markers</html>", layer.getInfoComponent());
         assertTrue(layer.getMenuEntries().length > 10);
     }
+
+    /**
+     * Unit test of {@code Main.map.mapView.playHeadMarker}.
+     */
+    @Test
+    public void testPlayHeadMarker() {
+        try {
+            MainApplication.getLayerManager().addLayer(new OsmDataLayer(new DataSet(), "", null));
+            MapFrame map = MainApplication.getMap();
+            MarkerLayer layer = new MarkerLayer(new GpxData(), null, null, null);
+            assertNull(map.mapView.playHeadMarker);
+            MainApplication.getLayerManager().addLayer(layer);
+            assertNotNull(map.mapView.playHeadMarker);
+            MainApplication.getLayerManager().removeLayer(layer);
+        } finally {
+            if (MainApplication.isDisplayingMapView()) {
+                MainApplication.getMap().mapView.playHeadMarker = null;
+            }
+        }
+    }
 }
Index: test/unit/org/openstreetmap/josm/io/GpxWriterTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/io/GpxWriterTest.java	(revision 15439)
+++ test/unit/org/openstreetmap/josm/io/GpxWriterTest.java	(working copy)
@@ -87,4 +87,21 @@
                 "    <vdop>0.9</vdop>%n" +
                 "    <pdop>1.2</pdop>%n");
     }
+
+    /**
+     * Tests if extensions are handled correctly when reading and writing.
+     * Source file contains 4 tracks
+     *  - 1x gpxx colors (garmin)
+     *  - 1x gpxd colors
+     *  - 2x no colors
+     * one of the tracks without colors is assigned a new color
+     * Then the layer is exported twice
+     *  - once using gpxx extensions and
+     *  - once using gpxd extensions
+     */
+    @Test
+    public void testExtensions() {
+        //TODO
+    }
+
 }
