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: data/gpx-extensions-1.1.xsd
===================================================================
--- data/gpx-extensions-1.1.xsd	(nonexistent)
+++ data/gpx-extensions-1.1.xsd	(working copy)
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schema targetNamespace="http://josm.openstreetmap.de/gpx-extensions-1.1"
+	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:josm="http://josm.openstreetmap.de/gpx-extensions-1.1"
+	xsi:schemaLocation="http://josm.openstreetmap.de/gpx-extensions-1.1 http://josm.openstreetmap.de/gpx-extensions-1.1.xsd">
+	
+    <!-- true, if gpx data has been downloaded from the osm server -->
+    <!-- it this case, JOSM improves the rendering of clouds of anonymous TrackPoints -->
+    <element name="from-server" type="boolean"/>
+    
+    <!-- the following properties are only set for marker layer export -->
+    <element name="offset" type="decimal"/>
+    <element name="sync-offset" type="decimal"/>
+    <element name="text" type="string" />
+    
+    <xsd:element name="layerPreferences" type="josm:preferences_type">
+    	<xsd:annotation>
+    		<xsd:documentation>
+    			The layerPreferences contain the preferences that can be set for the layer, e.g. in the "Customize track drawing" dialog in JOSM.
+    		</xsd:documentation>
+    	</xsd:annotation>
+    </xsd:element>
+
+	<xsd:complexType name="preferences_type">
+		<xsd:sequence>
+			<xsd:element name="entry" type="josm:entry_type" minOccurs="0" />
+		</xsd:sequence>
+	</xsd:complexType>
+
+	<xsd:complexType name="entry_type">
+		<xsd:attribute name="key" type="xsd:string" use="required" />
+		<xsd:attribute name="value" type="xsd:string" use="required" />
+	</xsd:complexType>
+	
+
+</schema>
\ No newline at end of file
Index: src/org/openstreetmap/josm/actions/ShowStatusReportAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/ShowStatusReportAction.java	(revision 15454)
+++ src/org/openstreetmap/josm/actions/ShowStatusReportAction.java	(working copy)
@@ -293,8 +293,7 @@
         text.append(reportHeader);
 
         Preferences.main().getAllSettings().forEach((key, setting) -> {
-            if (key.startsWith("marker.show")
-                    || "file-open.history".equals(key)
+            if ("file-open.history".equals(key)
                     || "download.overpass.query".equals(key)
                     || "download.overpass.queries".equals(key)
                     || key.contains("username")
Index: src/org/openstreetmap/josm/data/gpx/Extensions.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/Extensions.java	(revision 15454)
+++ 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 15454)
+++ 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.1";
+    /**
+     * Location of the XSD schema for JOSM GPX extensions
+     */
+    String XML_XSD_EXTENSIONS_JOSM = Config.getUrls().getXMLBase() + "/gpx-extensions-1.1.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 15454)
+++ src/org/openstreetmap/josm/data/gpx/GpxData.java	(working copy)
@@ -54,6 +54,12 @@
     public String creator;
 
     /**
+     * The layer specific prefs formerly saved in the preferences, e.g. drawing options.
+     * NOT the segment specific settings (e.g. color, width)
+     */
+    public Map<String, String> layerPrefs = new HashMap<String, String>()  {};
+
+    /**
      * A list of tracks this file consists of
      */
     private final ArrayList<GpxTrack> privateTracks = new ArrayList<>();
@@ -65,7 +71,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(); setModified();};
+    private boolean modified = false;
 
     /**
      * Tracks. Access is discouraged, use {@link #getTracks()} to read.
@@ -888,6 +895,7 @@
         private Line next;
         private final boolean[] trackVisibility;
         private Map<String, Object> trackAttributes;
+        private GpxTrack curTrack;
 
         /**
          * Constructs a new {@code LinesIterator}.
@@ -921,17 +929,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
@@ -981,6 +989,11 @@
                 return false;
         } else if (!dataSources.equals(other.dataSources))
             return false;
+        if (layerPrefs == null) {
+            if (other.layerPrefs != null)
+                return false;
+        } else if (!layerPrefs.equals(other.layerPrefs))
+            return false;
         if (privateRoutes == null) {
             if (other.privateRoutes != null)
                 return false;
@@ -1067,4 +1080,26 @@
             return source;
         }
     }
+
+    /**
+     * @return whether anything (i.e. colors) have been modified
+     */
+    public boolean isModified() {
+        return modified;
+    }
+
+    /**
+     * Sets the modified flag to true.
+     */
+    public void setModified() {
+        setModified(true);
+    }
+
+    /**
+     * Sets the modified flag to the value.
+     * @param value
+     */
+    public void setModified(boolean value) {
+        modified = value;
+    }
 }
Index: src/org/openstreetmap/josm/data/gpx/GpxTrack.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/GpxTrack.java	(revision 15454)
+++ 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 15454)
+++ 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 15454)
+++ 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 15454)
+++ 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 15454)
+++ 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 15454)
+++ 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 15454)
+++ 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 15454)
+++ 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,22 @@
         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.eol().fill(GBC.HORIZONTAL).insets(20, 0, 0, 0));
+
+        boolean hasPrefs = !gpxData.layerPrefs.isEmpty();
+        JCheckBox layerPrefs = new JCheckBox(tr("Save layer specific preferences"),
+                hasPrefs && Config.getPref().getBoolean("gpx.export.prefs", true));
+        layerPrefs.setEnabled(hasPrefs);
+        p.add(layerPrefs, GBC.eop().fill(GBC.HORIZONTAL));
+
         ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(),
                 tr("Export options"),
                 tr("Export and Save"), tr("Cancel"))
@@ -154,6 +169,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 +200,17 @@
         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());
+        if (hasPrefs) {
+            Config.getPref().putBoolean("gpx.export.prefs", layerPrefs.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 +247,12 @@
             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, layerPrefs.isSelected());
+            w.close();
+            fo.flush();
+            gpxData.setModified(false);
         }
     }
 
Index: src/org/openstreetmap/josm/gui/io/importexport/GpxImporter.java
===================================================================
--- src/org/openstreetmap/josm/gui/io/importexport/GpxImporter.java	(revision 15454)
+++ src/org/openstreetmap/josm/gui/io/importexport/GpxImporter.java	(working copy)
@@ -149,6 +149,8 @@
             markerLayer = new MarkerLayer(data, markerLayerName, data.storageFile, gpxLayer);
             if (markerLayer.data.isEmpty()) {
                 markerLayer = null;
+            } else {
+                gpxLayer.linkedMarkerLayer = markerLayer;
             }
         }
         Runnable postLayerTask = () -> {
Index: src/org/openstreetmap/josm/gui/layer/CustomizeColor.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/CustomizeColor.java	(revision 15454)
+++ 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(Layer::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 15454)
+++ 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;
@@ -46,6 +46,8 @@
 import org.openstreetmap.josm.gui.layer.gpx.ImportAudioAction;
 import org.openstreetmap.josm.gui.layer.gpx.ImportImagesAction;
 import org.openstreetmap.josm.gui.layer.gpx.MarkersFromNamedPointsAction;
+import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
+import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
 import org.openstreetmap.josm.gui.widgets.HtmlPanel;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Utils;
@@ -54,13 +56,19 @@
 /**
  * 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;
     private final boolean isLocalFile;
     private boolean isExpertMode;
+
     /**
+     * The MarkerLayer imported from the same file.
+     */
+    public MarkerLayer linkedMarkerLayer;
+
+    /**
      * used by {@link ChooseTrackVisibilityAction} to determine which tracks to show/hide
      *
      * Call {@link #invalidate()} after each change!
@@ -108,10 +116,24 @@
     }
 
     @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);
+        }
+        GPXSettingsPanel.putLayerPrefLocal(this, "colormode", "0");
+    }
+
+    @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
@@ -480,6 +502,16 @@
     }
 
     @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 15454)
+++ 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, <code>null</code> for default
+     */
+    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 15454)
+++ 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 15454)
+++ 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,42 @@
             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()));
+            layer.data.layerPrefs.put("colors", "0"); //set Colormode to none
+            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 +176,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 +209,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 +335,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 +349,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 +379,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 +394,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 15454)
+++ 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/CustomizeDrawingAction.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/gpx/CustomizeDrawingAction.java	(revision 15454)
+++ src/org/openstreetmap/josm/gui/layer/gpx/CustomizeDrawingAction.java	(working copy)
@@ -9,6 +9,7 @@
 import java.awt.event.ActionEvent;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import javax.swing.AbstractAction;
 import javax.swing.Action;
@@ -59,12 +60,7 @@
 
     @Override
     public boolean supportLayers(List<Layer> layers) {
-        for (Layer layer : layers) {
-            if (!(layer instanceof GpxLayer)) {
-                return false;
-            }
-        }
-        return true;
+        return layers.stream().allMatch(l -> l instanceof GpxLayer);
     }
 
     @Override
@@ -79,18 +75,7 @@
 
     @Override
     public void actionPerformed(ActionEvent e) {
-        boolean hasLocal = false;
-        boolean hasNonlocal = false;
-        for (Layer layer : layers) {
-            if (layer instanceof GpxLayer) {
-                if (((GpxLayer) layer).isLocalFile()) {
-                    hasLocal = true;
-                } else {
-                    hasNonlocal = true;
-                }
-            }
-        }
-        GPXSettingsPanel panel = new GPXSettingsPanel(layers.get(0).getName(), hasLocal, hasNonlocal);
+        GPXSettingsPanel panel = new GPXSettingsPanel(layers.stream().filter(l -> l instanceof GpxLayer).map(l -> (GpxLayer) l).collect(Collectors.toList()));
         JScrollPane scrollpane = GuiHelper.embedInVerticalScrollPane(panel);
         scrollpane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
         int screenHeight = GuiHelper.getScreenSize().height;
@@ -103,15 +88,9 @@
         if (answer == JOptionPane.CANCEL_OPTION || answer == JOptionPane.CLOSED_OPTION) {
             return;
         }
-        for (Layer layer : layers) {
-            // save preferences for all layers
-            boolean f = false;
-            if (layer instanceof GpxLayer) {
-                f = ((GpxLayer) layer).isLocalFile();
-            }
-            panel.savePreferences(layer.getName(), f);
-        }
-        MainApplication.getMap().repaint();
+        panel.savePreferences();
+        MainApplication.getMainPanel().repaint();
+        layers.stream().forEach(l -> l.invalidate());
     }
 
 }
Index: src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java	(revision 15454)
+++ src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java	(working copy)
@@ -32,7 +32,6 @@
 import javax.swing.ImageIcon;
 
 import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.PreferencesUtils;
 import org.openstreetmap.josm.data.SystemOfMeasurement;
 import org.openstreetmap.josm.data.SystemOfMeasurement.SoMChangeListener;
 import org.openstreetmap.josm.data.coor.LatLon;
@@ -51,6 +50,7 @@
 import org.openstreetmap.josm.gui.layer.MapViewPaintable.MapViewEvent;
 import org.openstreetmap.josm.gui.layer.MapViewPaintable.PaintableInvalidationEvent;
 import org.openstreetmap.josm.gui.layer.MapViewPaintable.PaintableInvalidationListener;
+import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
 import org.openstreetmap.josm.io.CachedFile;
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.ColorScale;
@@ -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;
@@ -78,7 +78,7 @@
     // use alpha blending for line draw
     private boolean alphaLines;
     // draw direction arrows on the lines
-    private boolean direction;
+    private boolean arrows;
     /** width of line for paint **/
     private int lineWidth;
     /** don't draw lines if longer than x meters **/
@@ -90,7 +90,7 @@
     private int largesize;
     private boolean hdopCircle;
     /** paint direction arrow with alternate math. may be faster **/
-    private boolean alternateDirection;
+    private boolean arrowsFast;
     /** don't draw arrows nearer to each other than this **/
     private int delta;
     private double minTrackDurationForTimeColoring;
@@ -106,7 +106,7 @@
     private Color computeCacheColorUsed;
     private boolean computeCacheColorDynamic;
     private ColorMode computeCacheColored;
-    private int computeCacheColorTracksTune;
+    private int computeCacheVelocityTune;
     private int computeCacheHeatMapDrawColorTableIdx;
     private boolean computeCacheHeatMapDrawPointMode;
     private int computeCacheHeatMapDrawGain;
@@ -116,7 +116,7 @@
     /** Mode of the line coloring **/
     private ColorMode colored;
     /** max speed for coloring - allows to tweak line coloring for different speed levels. **/
-    private int colorTracksTune;
+    private int velocityTune;
     private boolean colorModeDynamic;
     private Color neutralColor;
     private int largePointAlpha;
@@ -127,7 +127,7 @@
     private ColorScale hdopScale;
     private ColorScale qualityScale;
     private ColorScale dateScale;
-    private ColorScale directionScale;
+    private ColorScale arrowScale;
 
     /** Opacity for hdop points **/
     private int hdopAlpha;
@@ -145,8 +145,6 @@
 
     /** heat map parameters **/
 
-    // enabled or not (override by settings)
-    private boolean heatMapEnabled;
     // draw small extra line
     private boolean heatMapDrawExtraLine;
     // used index for color table (parameter)
@@ -199,7 +197,7 @@
         hdopScale = ColorScale.createHSBScale(256).makeReversed().addTitle(tr("HDOP"));
         qualityScale = ColorScale.createFixedScale(rtkLibQualityColors).addTitle(tr("Quality"));
         dateScale = ColorScale.createHSBScale(256).addTitle(tr("Time"));
-        directionScale = ColorScale.createCyclicScale(256).setIntervalCount(4).addTitle(tr("Direction"));
+        arrowScale = ColorScale.createCyclicScale(256).setIntervalCount(4).addTitle(tr("Direction"));
 
         systemOfMeasurementChanged(null, null);
     }
@@ -268,35 +266,14 @@
         setupColors();
     }
 
-    private static String specName(String layerName) {
-        return "layer " + layerName;
-    }
-
     /**
-     * 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
      */
-    public ColorMode getColorMode(String layerName) {
+    public ColorMode getColorMode() {
         try {
-            int i = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.colors", specName(layerName), 0);
+            int i = optInt("colormode");
+            if (i == -1) i = 0; //global
             return ColorMode.fromIndex(i);
         } catch (IndexOutOfBoundsException e) {
             Logging.warn(e);
@@ -304,39 +281,43 @@
         return ColorMode.NONE;
     }
 
-    /** Reads generic color from preferences (usually gray)
-     * @return the color
-     **/
-    public static Color getGenericColor() {
-        return DEFAULT_COLOR.get();
+    private String opt(String key) {
+        return GPXSettingsPanel.getLayerPref(layer, key);
     }
 
+    private boolean optBool(String key) {
+        return Boolean.parseBoolean(opt(key));
+    }
+
+    private int optInt(String key) {
+        return GPXSettingsPanel.getLayerPrefInt(layer, key);
+    }
+
     /**
      * Read all drawing-related settings from preferences
-     * @param layerName layer name used to access its specific preferences
      **/
-    public void readPreferences(String layerName) {
-        String spec = specName(layerName);
-        forceLines = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines.force", spec, false);
-        direction = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.direction", spec, false);
-        lineWidth = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.linewidth", spec, 0);
-        alphaLines = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines.alpha-blend", spec, false);
+    public void readPreferences() {
+        forceLines = optBool("lines.force");
+        arrows = optBool("lines.arrows");
+        arrowsFast = optBool("lines.arrows.fast");
+        lineWidth = optInt("lines.width");
+        alphaLines = optBool("lines.alpha-blend");
 
+        int l = optInt("lines");
         if (!data.fromServer) {
-            maxLineLength = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.max-line-length.local", spec, -1);
-            lines = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines.local", spec, true);
+            maxLineLength = optInt("lines.max-length.local");
+            lines = l != 0; //draw for -1 (default), 1 (local) and 2 (all)
         } else {
-            maxLineLength = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.max-line-length", spec, 200);
-            lines = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines", spec, true);
+            maxLineLength = optInt("lines.max-length");
+            lines = l == 2; //draw only for 2 (all)
         }
-        large = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.large", spec, false);
-        largesize = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.large.size", spec, 3);
-        hdopCircle = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.hdopcircle", spec, false);
-        colored = getColorMode(layerName);
-        alternateDirection = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.alternatedirection", spec, false);
-        delta = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.min-arrow-distance", spec, 40);
-        colorTracksTune = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.colorTracksTune", spec, 45);
-        colorModeDynamic = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.colors.dynamic", spec, false);
+        large = optBool("points.large");
+        largesize = optInt("points.large.size");
+        hdopCircle = optBool("points.hdopcircle");
+        colored = getColorMode();
+        delta = optInt("lines.arrows.min-distance");
+        velocityTune = optInt("colormode.velocity.tune");
+        colorModeDynamic = optBool("colormode.dynamic-range");
         /* good HDOP's are between 1 and 3, very bad HDOP's go into 3 digit values */
         hdoprange = Config.getPref().getInt("hdop.range", 7);
         minTrackDurationForTimeColoring = Config.getPref().getInt("draw.rawgps.date-coloring-min-dt", 60);
@@ -343,22 +324,20 @@
         largePointAlpha = Config.getPref().getInt("draw.rawgps.large.alpha", -1) & 0xFF;
 
         // get heatmap parameters
-        heatMapEnabled = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.heatmap.enabled", spec, false);
-        heatMapDrawExtraLine = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.heatmap.line-extra", spec, false);
-        heatMapDrawColorTableIdx = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.heatmap.colormap", spec, 0);
-        heatMapDrawPointMode = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.heatmap.use-points", spec, false);
-        heatMapDrawGain = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.heatmap.gain", spec, 0);
-        heatMapDrawLowerLimit = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.heatmap.lower-limit", spec, 0);
+        heatMapDrawExtraLine = optBool("colormode.heatmap.line-extra");
+        heatMapDrawColorTableIdx = optInt("colormode.heatmap.colormap");
+        heatMapDrawPointMode = optBool("colormode.heatmap.use-points");
+        heatMapDrawGain = optInt("colormode.heatmap.gain");
+        heatMapDrawLowerLimit = optInt("colormode.heatmap.lower-limit");
 
         // 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);
         qualityScale.setNoDataColor(neutralColor);
-        directionScale.setNoDataColor(neutralColor);
+        arrowScale.setNoDataColor(neutralColor);
 
         largesize += lineWidth;
     }
@@ -368,7 +347,7 @@
         Bounds clipBounds = graphics.getClipBounds().getLatLonBoundsBox();
         List<WayPoint> visibleSegments = listVisibleSegments(clipBounds);
         if (!visibleSegments.isEmpty()) {
-            readPreferences(layer.getName());
+            readPreferences();
             drawAll(graphics.getDefaultGraphics(), graphics.getMapView(), visibleSegments, clipBounds);
             if (graphics.getMapView().getLayerManager().getActiveLayer() == layer) {
                 drawColorBar(graphics.getDefaultGraphics(), graphics.getMapView());
@@ -462,7 +441,7 @@
         }
 
         // global enabled or select via color
-        boolean useHeatMap = heatMapEnabled || ColorMode.HEATMAP == colored;
+        boolean useHeatMap = ColorMode.HEATMAP == colored;
 
         // default global alpha level
         float layerAlpha = 1.00f;
@@ -570,7 +549,7 @@
             }
             oldWp = null;
         } else { // color mode not dynamic
-            velocityScale.setRange(0, colorTracksTune);
+            velocityScale.setRange(0, velocityTune);
             hdopScale.setRange(0, hdoprange);
             qualityScale.setRange(1, rtkLibQualityColors.length);
         }
@@ -594,7 +573,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;
                 }
@@ -620,7 +599,7 @@
                         break;
                     case DIRECTION:
                         double dirColor = oldWp.getCoor().bearing(trkPnt.getCoor());
-                        color = directionScale.getColor(dirColor);
+                        color = arrowScale.getColor(dirColor);
                         break;
                     case TIME:
                         double t = trkPnt.getTime();
@@ -642,7 +621,7 @@
                     }
                 } else { // make sure we reset outdated data
                     trkPnt.drawLine = false;
-                    color = neutralColor;
+                    color = segment.getColor();
                 }
                 if (color != null) {
                     trkPnt.customColoring = color;
@@ -700,7 +679,7 @@
         /****************************************************************
          ********** STEP 3b - DRAW NICE ARROWS **************************
          ****************************************************************/
-        if (lines && direction && !alternateDirection) {
+        if (lines && arrows && !arrowsFast) {
             Point old = null;
             Point oldA = null; // last arrow painted
             for (WayPoint trkPnt : visibleSegments) {
@@ -730,7 +709,7 @@
         /****************************************************************
          ********** STEP 3c - DRAW FAST ARROWS **************************
          ****************************************************************/
-        if (lines && direction && alternateDirection) {
+        if (lines && arrows && arrowsFast) {
             Point old = null;
             Point oldA = null; // last arrow painted
             for (WayPoint trkPnt : visibleSegments) {
@@ -1480,7 +1459,7 @@
         // CHECKSTYLE.OFF: BooleanExpressionComplexity
         if ((computeCacheMaxLineLengthUsed != maxLineLength)
                 || (computeCacheColored != colored)
-                || (computeCacheColorTracksTune != colorTracksTune)
+                || (computeCacheVelocityTune != velocityTune)
                 || (computeCacheColorDynamic != colorModeDynamic)
                 || (computeCacheHeatMapDrawColorTableIdx != heatMapDrawColorTableIdx)
                 || (!neutralColor.equals(computeCacheColorUsed)
@@ -1493,7 +1472,7 @@
             computeCacheInSync = false;
             computeCacheColorUsed = neutralColor;
             computeCacheColored = colored;
-            computeCacheColorTracksTune = colorTracksTune;
+            computeCacheVelocityTune = velocityTune;
             computeCacheColorDynamic = colorModeDynamic;
             computeCacheHeatMapDrawColorTableIdx = heatMapDrawColorTableIdx;
             computeCacheHeatMapDrawPointMode = heatMapDrawPointMode;
@@ -1529,7 +1508,7 @@
             SystemOfMeasurement som = SystemOfMeasurement.getSystemOfMeasurement();
             velocityScale.drawColorBar(g, w-30, 50, 20, 100, som.speedValue);
         } else if (colored == ColorMode.DIRECTION) {
-            directionScale.drawColorBar(g, w-30, 50, 20, 100, 180.0/Math.PI);
+            arrowScale.drawColorBar(g, w-30, 50, 20, 100, 180.0/Math.PI);
         }
     }
 
Index: src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java	(revision 15454)
+++ 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 15454)
+++ 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("sync-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 15454)
+++ 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 15454)
+++ src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java	(working copy)
@@ -20,6 +20,7 @@
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Optional;
 
 import javax.swing.AbstractAction;
 import javax.swing.Action;
@@ -48,6 +49,7 @@
 import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToPreviousMarker;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.gpx.ConvertFromMarkerLayerAction;
+import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
 import org.openstreetmap.josm.io.audio.AudioPlayer;
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -75,9 +77,12 @@
     public GpxLayer fromLayer;
     private Marker currentMarker;
     public AudioMarker syncAudioMarker;
+    private Color color, realcolor;
 
-    private static final Color DEFAULT_COLOR = Color.magenta;
-    private static final NamedColorProperty COLOR_PROPERTY = new NamedColorProperty(marktr("gps marker"), DEFAULT_COLOR);
+    /**
+     * The default color that is used for drawing markers.
+     */
+    public static final NamedColorProperty DEFAULT_COLOR_PROPERTY = new NamedColorProperty(marktr("gps marker"), Color.magenta);
 
     /**
      * Constructs a new {@code MarkerLayer}.
@@ -94,6 +99,17 @@
         double firstTime = -1.0;
         String lastLinkedFile = "";
 
+        Color c = null;
+        String cs = GPXSettingsPanel.tryGetLayerPrefLocal(indata, "markers.color");
+        if (cs != null) {
+            try {
+                c = Color.decode(cs);
+            } catch (NumberFormatException ex) {
+                Logging.warn("Could not read marker color: " + cs);
+            }
+        }
+        setPrivateColors(c);
+
         for (WayPoint wpt : indata.waypoints) {
             /* calculate time differences in waypoints */
             double time = wpt.getTime();
@@ -123,10 +139,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 +189,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(realcolor);
         if (mousePressed) {
             boolean mousePressedTmp = mousePressed;
             Point mousePos = mv.getMousePosition(); // Get mouse position only when necessary (it's the slowest part of marker layer painting)
@@ -450,10 +455,37 @@
      * @return <code>true</code> if text should be shown, <code>false</code> otherwise.
      */
     private boolean isTextOrIconShown() {
-        String current = Config.getPref().get("marker.show "+getName(), "show");
-        return "show".equalsIgnoreCase(current);
+        return Boolean.parseBoolean(GPXSettingsPanel.getLayerPref(fromLayer, "markers.show-text"));
     }
 
+    @Override
+    public boolean hasColor() {
+        return true;
+    }
+
+    @Override
+    public Color getColor() {
+        return color;
+    }
+
+    @Override
+    public void setColor(Color color) {
+        setPrivateColors(color);
+        if (fromLayer != null) {
+            String cs = null;
+            if (color != null) {
+                cs = String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue());
+            }
+            GPXSettingsPanel.putLayerPrefLocal(fromLayer, "markers.color", cs);
+        }
+        invalidate();
+    }
+
+    private void setPrivateColors(Color color) {
+        this.color = color;
+        this.realcolor = Optional.ofNullable(color).orElse(DEFAULT_COLOR_PROPERTY.get());
+    }
+
     private final class MarkerMouseAdapter extends MouseAdapter {
         @Override
         public void mousePressed(MouseEvent e) {
@@ -503,7 +535,7 @@
 
         @Override
         public void actionPerformed(ActionEvent e) {
-            Config.getPref().put("marker.show "+layer.getName(), layer.isTextOrIconShown() ? "hide" : "show");
+            GPXSettingsPanel.putLayerPrefLocal(layer.fromLayer, "markers.show-text", Boolean.toString(!layer.isTextOrIconShown()));
             layer.invalidate();
         }
 
Index: src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java
===================================================================
--- src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java	(revision 15454)
+++ 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 15454)
+++ src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java	(working copy)
@@ -4,12 +4,15 @@
 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;
 import java.awt.event.ActionListener;
 import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 
 import javax.swing.AbstractButton;
 import javax.swing.BorderFactory;
@@ -22,10 +25,11 @@
 import javax.swing.JRadioButton;
 import javax.swing.JSlider;
 
+import org.apache.commons.jcs.access.exception.InvalidArgumentException;
 import org.openstreetmap.josm.actions.ExpertToggleAction;
-import org.openstreetmap.josm.data.PreferencesUtils;
-import org.openstreetmap.josm.data.preferences.NamedColorProperty;
+import org.openstreetmap.josm.data.gpx.GpxData;
 import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
 import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper;
 import org.openstreetmap.josm.gui.layer.markerlayer.Marker;
 import org.openstreetmap.josm.gui.layer.markerlayer.Marker.TemplateEntryProperty;
@@ -67,7 +71,7 @@
     private final JRadioButton colorTypeQuality = new JRadioButton(tr("Quality (RTKLib only, if available)"));
     private final JRadioButton colorTypeTime = new JRadioButton(tr("Track date"));
     private final JRadioButton colorTypeHeatMap = new JRadioButton(tr("Heat Map (dark = few, bright = many)"));
-    private final JRadioButton colorTypeNone = new JRadioButton(tr("Single Color (can be customized for named layers)"));
+    private final JRadioButton colorTypeNone = new JRadioButton(tr("Single Color (can be customized in the layer manager)"));
     private final JRadioButton colorTypeGlobal = new JRadioButton(tr("Use global settings"));
     private final JosmComboBox<String> colorTypeVelocityTune = new JosmComboBox<>(new String[] {tr("Car"), tr("Bicycle"), tr("Foot")});
     private final JosmComboBox<String> colorTypeHeatMapTune = new JosmComboBox<>(new String[] {
@@ -93,21 +97,51 @@
     private final JCheckBox useGpsAntialiasing = new JCheckBox(tr("Smooth GPX graphics (antialiasing)"));
     private final JCheckBox drawLineWithAlpha = new JCheckBox(tr("Draw with Opacity (alpha blending) "));
 
-    private String layerName;
-    private final boolean local; // flag to display LocalOnly checkbox
-    private final boolean nonlocal; // flag to display AllLines checkbox
+    private final List<GpxLayer> layers;
+    private final GpxLayer firstLayer;
+    private final boolean global; // global settings vs. layer specific settings
+    private final boolean hasLocalFile; // flag to display LocalOnly checkbooks
+    private final boolean hasNonLocalFile; // flag to display AllLines checkbox
 
+    private final static Map<String, Object> defaults = new HashMap<String, Object>() {{
+        put("colormode", -1);
+        put("colormode.dynamic-range", false);
+        put("colormode.heatmap.colormap", 0);
+        put("colormode.heatmap.gain", 0);
+        put("colormode.heatmap.line-extra", false); //Einstein only
+        put("colormode.heatmap.lower-limit", 0);
+        put("colormode.heatmap.use-points", false);
+        put("colormode.velocity.tune", 45);
+        put("lines", -1);
+        put("lines.alpha-blend", false);
+        put("lines.arrows", false);
+        put("lines.arrows.fast", false);
+        put("lines.arrows.min-distance", 40);
+        put("lines.force", false);
+        put("lines.max-length", 200);
+        put("lines.max-length.local", -1);
+        put("lines.width", 0);
+        put("markers.color", "");
+        put("markers.show-text", true);
+        put("points.hdopcircle", false);
+        put("points.large", false);
+        put("points.large.size", 3); //Einstein only
+    }};
+
     /**
-     * Constructs a new {@code GPXSettingsPanel} for a given layer name.
-     * @param layerName The GPX layer name
-     * @param local flag to display LocalOnly checkbox
-     * @param nonlocal flag to display AllLines checkbox
+     * Constructs a new {@code GPXSettingsPanel} for the given layers.
+     * @param layers the GPX layers
      */
-    public GPXSettingsPanel(String layerName, boolean local, boolean nonlocal) {
+    public GPXSettingsPanel(List<GpxLayer> layers) {
         super(new GridBagLayout());
-        this.local = local;
-        this.nonlocal = nonlocal;
-        this.layerName = "layer "+layerName;
+        this.layers = layers;
+        if (layers == null || layers.size() == 0) {
+            throw new InvalidArgumentException("At least one layer required");
+        }
+        firstLayer = layers.get(0);
+        global = false;
+        hasLocalFile = layers.stream().anyMatch(GpxLayer::isLocalFile);
+        hasNonLocalFile = layers.stream().anyMatch(l -> !l.isLocalFile());
         initComponents();
         loadPreferences();
     }
@@ -117,24 +151,153 @@
      */
     public GPXSettingsPanel() {
         super(new GridBagLayout());
+        layers = null;
+        firstLayer = null;
+        global = hasLocalFile = hasNonLocalFile = true;
         initComponents();
-        local = false;
-        nonlocal = false;
         loadPreferences(); // preferences -> controls
     }
 
+    /**
+     * Reads the preference for the given layer or the default preference if not available
+     * @param layer the GpxLayer. Can be <code>null</code>, default preference will be returned then
+     * @param key the drawing key to be read, without "draw.rawgps."
+     * @return the value
+     */
+    public static String getLayerPref(GpxLayer layer, String key) {
+        Object d = defaults.get(key);
+        String ds;
+        if (d != null) {
+            ds = d.toString();
+        } else {
+            Logging.warn("No default value found for layer preference \"" + key + "\".");
+            ds = null;
+        }
+        return Optional.ofNullable(tryGetLayerPrefLocal(layer, key)).orElse(Config.getPref().get("draw.rawgps." + key, ds));
+    }
+
+    /**
+     * Reads the integer preference for the given layer or the default preference if not available
+     * @param layer the GpxLayer. Can be <code>null</code>, default preference will be returned then
+     * @param key the drawing key to be read, without "draw.rawgps."
+     * @return the integer value
+     */
+    public static int getLayerPrefInt(GpxLayer layer, String key) {
+        try {
+            return Integer.parseInt(getLayerPref(layer, key));
+        } catch (NumberFormatException ex) {
+            Object d = defaults.get(key);
+            if (d instanceof Integer) {
+                return (int) d;
+            } else {
+                Logging.warn("No valid default value found for layer preference \"" + key + "\".");
+                return 0;
+            }
+        }
+    }
+
+    /**
+     * Try to read the preference for the given layer
+     * @param layer the GpxLayer
+     * @param key the drawing key to be read, without "draw.rawgps."
+     * @return the value or <code>null</code> if not found
+     */
+    public static String tryGetLayerPrefLocal(GpxLayer layer, String key) {
+        return layer != null ? tryGetLayerPrefLocal(layer.data, key) : null;
+    }
+
+    /**
+     * Try to read the preference for the given GpxData
+     * @param data the GpxData
+     * @param key the drawing key to be read, without "draw.rawgps."
+     * @return the value or <code>null</code> if not found
+     */
+    public static String tryGetLayerPrefLocal(GpxData data, String key) {
+        return data != null ? data.layerPrefs.get(key) : null;
+    }
+
+    /**
+     * Puts the preference for the given layers or the default preference if layers is <code>null</code>
+     * @param layers List of <code>GpxLayer</code> to put the drawingOptions
+     * @param key the drawing key to be written, without "draw.rawgps."
+     * @param value (can be <code>null</code> to remove option)
+     */
+    public static void putLayerPref(List<GpxLayer> layers, String key, Object value) {
+        String v = value == null ? null : value.toString();
+        if (layers != null) {
+            for (GpxLayer l : layers) {
+                putLayerPrefLocal(l.data, key, v);
+            }
+        } else {
+            Config.getPref().put("draw.rawgps." + key, v);
+        }
+    }
+
+    /**
+     * Puts the preference for the given layer
+     * @param layer <code>GpxLayer</code> to put the drawingOptions
+     * @param key the drawing key to be written, without "draw.rawgps."
+     * @param value the value or <code>null</code> to remove key
+     */
+    public static void putLayerPrefLocal(GpxLayer layer, String key, String value) {
+        if (layer == null) return;
+        putLayerPrefLocal(layer.data, key, value);
+    }
+
+    /**
+     * Puts the preference for the given layer
+     * @param data <code>GpxData</code> to put the drawingOptions. Must not be <code>null</code>
+     * @param key the drawing key to be written, without "draw.rawgps."
+     * @param value the value or <code>null</code> to remove key
+     */
+    public static void putLayerPrefLocal(GpxData data, String key, String value) {
+        if (value == null || value.isBlank() || (defaults.get(key) != null && defaults.get(key).toString().equals(value))) {
+            data.layerPrefs.remove(key);
+        } else {
+            data.layerPrefs.put(key, value);
+        }
+        data.setModified();
+    }
+
+    private String pref(String key) {
+        return getLayerPref(firstLayer, key);
+    }
+
+    private boolean prefBool(String key) {
+        return Boolean.parseBoolean(pref(key));
+    }
+
+    private int prefInt(String key) {
+        return getLayerPrefInt(firstLayer, key);
+    }
+
+    private int prefIntLocal(String key) {
+        try {
+            return Integer.parseInt(tryGetLayerPrefLocal(firstLayer, key));
+        } catch (NumberFormatException ex) {
+            return -1;
+        }
+
+    }
+
+    private void putPref(String key, Object value) {
+        putLayerPref(layers, key, value);
+    }
+
     // CHECKSTYLE.OFF: ExecutableStatementCountCheck
     private void initComponents() {
         setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 
-        // makeAutoMarkers
-        makeAutoMarkers.setToolTipText(tr("Automatically make a marker layer from any waypoints when opening a GPX layer."));
-        ExpertToggleAction.addVisibilitySwitcher(makeAutoMarkers);
-        add(makeAutoMarkers, GBC.eol().insets(20, 0, 0, 5));
+        if (global) {
+            // makeAutoMarkers
+            makeAutoMarkers.setToolTipText(tr("Automatically make a marker layer from any waypoints when opening a GPX layer."));
+            ExpertToggleAction.addVisibilitySwitcher(makeAutoMarkers);
+            add(makeAutoMarkers, GBC.eol().insets(20, 0, 0, 5));
+        }
 
         // drawRawGpsLines
         ButtonGroup gpsLinesGroup = new ButtonGroup();
-        if (layerName != null) {
+        if (!global) {
             gpsLinesGroup.add(drawRawGpsLinesGlobal);
         }
         gpsLinesGroup.add(drawRawGpsLinesNone);
@@ -145,14 +308,14 @@
 
         JLabel label = new JLabel(tr("Draw lines between raw GPS points"));
         add(label, GBC.eol().insets(20, 0, 0, 0));
-        if (layerName != null) {
+        if (!global) {
             add(drawRawGpsLinesGlobal, GBC.eol().insets(40, 0, 0, 0));
         }
         add(drawRawGpsLinesNone, GBC.eol().insets(40, 0, 0, 0));
-        if (layerName == null || local) {
+        if (hasLocalFile) {
             add(drawRawGpsLinesLocal, GBC.eol().insets(40, 0, 0, 0));
         }
-        if (layerName == null || nonlocal) {
+        if (hasNonLocalFile) {
             add(drawRawGpsLinesAll, GBC.eol().insets(40, 0, 0, 0));
         }
         ExpertToggleAction.addVisibilitySwitcher(label);
@@ -242,7 +405,7 @@
 
         // colorTracks
         ButtonGroup colorGroup = new ButtonGroup();
-        if (layerName != null) {
+        if (!global) {
             colorGroup.add(colorTypeGlobal);
         }
         colorGroup.add(colorTypeNone);
@@ -253,7 +416,7 @@
         colorGroup.add(colorTypeTime);
         colorGroup.add(colorTypeHeatMap);
 
-        colorTypeNone.setToolTipText(tr("All points and track segments will have the same color. Can be customized in Layer Manager."));
+        colorTypeNone.setToolTipText(tr("All points and track segments will have their own color. Can be customized in Layer Manager."));
         colorTypeVelocity.setToolTipText(tr("Colors points and track segments by velocity."));
         colorTypeDirection.setToolTipText(tr("Colors points and track segments by direction."));
         colorTypeDilution.setToolTipText(
@@ -272,7 +435,7 @@
         add(Box.createVerticalGlue(), GBC.eol().insets(0, 20, 0, 0));
 
         add(new JLabel(tr("Track and Point Coloring")), GBC.eol().insets(20, 0, 0, 0));
-        if (layerName != null) {
+        if (!global) {
             add(colorTypeGlobal, GBC.eol().insets(40, 0, 0, 0));
         }
         add(colorTypeNone, GBC.eol().insets(40, 0, 0, 0));
@@ -331,15 +494,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));
             }
         });
 
@@ -353,7 +511,7 @@
         add(colorDynamic, GBC.eop().insets(40, 0, 0, 0));
         ExpertToggleAction.addVisibilitySwitcher(colorDynamic);
 
-        if (layerName == null) {
+        if (global) {
             // Setting waypoints for gpx layer doesn't make sense - waypoints are shown in marker layer that has different name - so show
             // this only for global config
 
@@ -363,7 +521,7 @@
             label.setLabelFor(waypointLabel);
             add(waypointLabel, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
             waypointLabel.addActionListener(e -> updateWaypointPattern(waypointLabel, waypointLabelPattern));
-            updateWaypointLabelCombobox(waypointLabel, waypointLabelPattern, TemplateEntryProperty.forMarker(layerName));
+            updateWaypointLabelCombobox(waypointLabel, waypointLabelPattern, TemplateEntryProperty.forMarker(null));
             add(waypointLabelPattern, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 0, 0, 5));
             ExpertToggleAction.addVisibilitySwitcher(label);
             ExpertToggleAction.addVisibilitySwitcher(waypointLabel);
@@ -379,7 +537,7 @@
             label.setLabelFor(audioWaypointLabel);
             add(audioWaypointLabel, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
             audioWaypointLabel.addActionListener(e -> updateWaypointPattern(audioWaypointLabel, audioWaypointLabelPattern));
-            updateWaypointLabelCombobox(audioWaypointLabel, audioWaypointLabelPattern, TemplateEntryProperty.forAudioMarker(layerName));
+            updateWaypointLabelCombobox(audioWaypointLabel, audioWaypointLabelPattern, TemplateEntryProperty.forAudioMarker(null));
             add(audioWaypointLabelPattern, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 0, 0, 5));
             ExpertToggleAction.addVisibilitySwitcher(label);
             ExpertToggleAction.addVisibilitySwitcher(audioWaypointLabel);
@@ -395,46 +553,32 @@
      */
     public final void loadPreferences() {
         makeAutoMarkers.setSelected(Config.getPref().getBoolean("marker.makeautomarkers", true));
-        if (layerName != null && Config.getPref().get("draw.rawgps.lines."+layerName).isEmpty()
-                && Config.getPref().get("draw.rawgps.lines.local."+layerName).isEmpty()) {
-            // no line preferences for layer is found
+        int lines = global ? prefInt("lines") : prefIntLocal("lines");
+        if (lines == 2 && hasNonLocalFile) {
+            drawRawGpsLinesAll.setSelected(true);
+        } else if ((lines == 1 && hasLocalFile) || (lines == -1 && global)) {
+            drawRawGpsLinesLocal.setSelected(true);
+        } else if (lines == 0) {
+            drawRawGpsLinesNone.setSelected(true);
+        } else if (lines == -1) {
             drawRawGpsLinesGlobal.setSelected(true);
         } else {
-            Boolean lf = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines.local", layerName, true);
-            if (PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines", layerName, true)) {
-                drawRawGpsLinesAll.setSelected(true);
-            } else if (lf) {
-                drawRawGpsLinesLocal.setSelected(true);
-            } else {
-                drawRawGpsLinesNone.setSelected(true);
-            }
+            Logging.warn("Unknown line type: " + lines);
         }
-
-        drawRawGpsMaxLineLengthLocal.setText(Integer.toString(PreferencesUtils.getInteger(Config.getPref(),
-                "draw.rawgps.max-line-length.local", layerName, -1)));
-        drawRawGpsMaxLineLength.setText(Integer.toString(PreferencesUtils.getInteger(Config.getPref(),
-                "draw.rawgps.max-line-length", layerName, 200)));
-        drawLineWidth.setText(Integer.toString(PreferencesUtils.getInteger(Config.getPref(),
-                "draw.rawgps.linewidth", layerName, 0)));
-        drawLineWithAlpha.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
-                "draw.rawgps.lines.alpha-blend", layerName, false));
-        forceRawGpsLines.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
-                "draw.rawgps.lines.force", layerName, false));
-        drawGpsArrows.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
-                "draw.rawgps.direction", layerName, false));
-        drawGpsArrowsFast.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
-                "draw.rawgps.alternatedirection", layerName, false));
-        drawGpsArrowsMinDist.setText(Integer.toString(PreferencesUtils.getInteger(Config.getPref(),
-                "draw.rawgps.min-arrow-distance", layerName, 40)));
-        hdopCircleGpsPoints.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
-                "draw.rawgps.hdopcircle", layerName, false));
-        largeGpsPoints.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
-                "draw.rawgps.large", layerName, false));
+        drawRawGpsMaxLineLengthLocal.setText(pref("lines.max-length.local"));
+        drawRawGpsMaxLineLength.setText(pref("lines.max-length"));
+        drawLineWidth.setText(pref("lines.width"));
+        drawLineWithAlpha.setSelected(prefBool("lines.alpha-blend"));
+        forceRawGpsLines.setSelected(prefBool("lines.force"));
+        drawGpsArrows.setSelected(prefBool("lines.arrows"));
+        drawGpsArrowsFast.setSelected(prefBool("lines.arrows.fast"));
+        drawGpsArrowsMinDist.setText(pref("lines.arrows.min-distance"));
+        hdopCircleGpsPoints.setSelected(prefBool("points.hdopcircle"));
+        largeGpsPoints.setSelected(prefBool("points.large"));
         useGpsAntialiasing.setSelected(Config.getPref().getBoolean("mappaint.gpx.use-antialiasing", false));
 
         drawRawGpsLinesActionListener.actionPerformed(null);
-
-        if (layerName != null && Config.getPref().get("draw.rawgps.colors."+layerName).isEmpty()) {
+        if (!global && prefIntLocal("colormode") == -1) {
             colorTypeGlobal.setSelected(true);
             colorDynamic.setSelected(false);
             colorDynamic.setEnabled(false);
@@ -442,9 +586,9 @@
             colorTypeHeatMapGain.setValue(0);
             colorTypeHeatMapLowerLimit.setValue(0);
         } else {
-            int colorType = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.colors", layerName, 0);
+            int colorType = prefInt("colormode");
             switch (colorType) {
-            case 0: colorTypeNone.setSelected(true); break;
+            case -1: case 0: colorTypeNone.setSelected(true); break;
             case 1: colorTypeVelocity.setSelected(true); break;
             case 2: colorTypeDilution.setSelected(true); break;
             case 3: colorTypeDirection.setSelected(true); break;
@@ -453,108 +597,89 @@
             case 6: colorTypeQuality.setSelected(true); break;
             default: Logging.warn("Unknown color type: " + colorType);
             }
-            int ccts = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.colorTracksTune", layerName, 45);
+            int ccts = prefInt("colormode.velocity.tune");
             colorTypeVelocityTune.setSelectedIndex(ccts == 10 ? 2 : (ccts == 20 ? 1 : 0));
-            colorTypeHeatMapTune.setSelectedIndex(PreferencesUtils.getInteger(Config.getPref(),
-                    "draw.rawgps.heatmap.colormap", layerName, 0));
-            colorDynamic.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
-                    "draw.rawgps.colors.dynamic", layerName, false));
-            colorTypeHeatMapPoints.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
-                    "draw.rawgps.heatmap.use-points", layerName, false));
-            colorTypeHeatMapGain.setValue(PreferencesUtils.getInteger(Config.getPref(),
-                    "draw.rawgps.heatmap.gain", layerName, 0));
-            colorTypeHeatMapLowerLimit.setValue(PreferencesUtils.getInteger(Config.getPref(),
-                    "draw.rawgps.heatmap.lower-limit", layerName, 0));
+            colorTypeHeatMapTune.setSelectedIndex(prefInt("colormode.heatmap.colormap"));
+            colorDynamic.setSelected(prefBool("colormode.dynamic-range"));
+            colorTypeHeatMapPoints.setSelected(prefBool("colormode.heatmap.use-points"));
+            colorTypeHeatMapGain.setValue(prefInt("colormode.heatmap.gain"));
+            colorTypeHeatMapLowerLimit.setValue(prefInt("colormode.heatmap.lower-limit"));
         }
     }
 
     /**
-     * Save preferences from UI controls, globally or for a specified layer.
-     * @param layerName The GPX layer name. Can be {@code null}, in that case, global preferences are written
-     * @param locLayer {@code true} if the GPX layer is a local one. Ignored if {@code layerName} is null
+     * Save preferences from UI controls, globally or for the specified layers.
      * @return {@code true} when restart is required, {@code false} otherwise
      */
-    public boolean savePreferences(String layerName, boolean locLayer) {
-        String layerNameDot = ".layer "+layerName;
-        if (layerName == null) {
-            layerNameDot = "";
+    public boolean savePreferences() {
+        if (global) {
+            Config.getPref().putBoolean("marker.makeautomarkers", makeAutoMarkers.isSelected());
         }
-        Config.getPref().putBoolean("marker.makeautomarkers"+layerNameDot, makeAutoMarkers.isSelected());
-        if (drawRawGpsLinesGlobal.isSelected()) {
-            Config.getPref().put("draw.rawgps.lines" + layerNameDot, null);
-            Config.getPref().put("draw.rawgps.max-line-length" + layerNameDot, null);
-            Config.getPref().put("draw.rawgps.lines.local" + layerNameDot, null);
-            Config.getPref().put("draw.rawgps.max-line-length.local" + layerNameDot, null);
-            Config.getPref().put("draw.rawgps.lines.force"+layerNameDot, null);
-            Config.getPref().put("draw.rawgps.direction"+layerNameDot, null);
-            Config.getPref().put("draw.rawgps.alternatedirection"+layerNameDot, null);
-            Config.getPref().put("draw.rawgps.min-arrow-distance"+layerNameDot, null);
+        if (!global && drawRawGpsLinesGlobal.isSelected()) {
+            putPref("lines", null);
+            putPref("lines.max-length", null);
+            putPref("lines.max-length.local", null);
+            putPref("lines.force", null);
+            putPref("lines.arrows", null);
+            putPref("lines.arrows.fast", null);
+            putPref("lines.arrows.min-distance", null);
         } else {
-            if (layerName == null || !locLayer) {
-                Config.getPref().putBoolean("draw.rawgps.lines" + layerNameDot, drawRawGpsLinesAll.isSelected());
-                Config.getPref().put("draw.rawgps.max-line-length" + layerNameDot, drawRawGpsMaxLineLength.getText());
+            if (drawRawGpsLinesNone.isSelected()) {
+                putPref("lines", 0);
+            } else if (drawRawGpsLinesLocal.isSelected()) {
+                putPref("lines", 1);
+            } else if (drawRawGpsLinesAll.isSelected()) {
+                putPref("lines", 2);
             }
-            if (layerName == null || locLayer) {
-                Config.getPref().putBoolean("draw.rawgps.lines.local" + layerNameDot,
-                        drawRawGpsLinesAll.isSelected() || drawRawGpsLinesLocal.isSelected());
-                Config.getPref().put("draw.rawgps.max-line-length.local" + layerNameDot,
-                        drawRawGpsMaxLineLengthLocal.getText());
-            }
-            Config.getPref().putBoolean("draw.rawgps.lines.force"+layerNameDot, forceRawGpsLines.isSelected());
-            Config.getPref().putBoolean("draw.rawgps.direction"+layerNameDot, drawGpsArrows.isSelected());
-            Config.getPref().putBoolean("draw.rawgps.alternatedirection"+layerNameDot, drawGpsArrowsFast.isSelected());
-            Config.getPref().put("draw.rawgps.min-arrow-distance"+layerNameDot, drawGpsArrowsMinDist.getText());
+            putPref("lines.max-length", drawRawGpsMaxLineLength.getText());
+            putPref("lines.max-length.local", drawRawGpsMaxLineLengthLocal.getText());
+            putPref("lines.force", forceRawGpsLines.isSelected());
+            putPref("lines.arrows", drawGpsArrows.isSelected());
+            putPref("lines.arrows.fast", drawGpsArrowsFast.isSelected());
+            putPref("lines.arrows.min-distance", drawGpsArrowsMinDist.getText());
         }
 
-        Config.getPref().putBoolean("draw.rawgps.hdopcircle"+layerNameDot, hdopCircleGpsPoints.isSelected());
-        Config.getPref().putBoolean("draw.rawgps.large"+layerNameDot, largeGpsPoints.isSelected());
-        Config.getPref().put("draw.rawgps.linewidth"+layerNameDot, drawLineWidth.getText());
-        Config.getPref().putBoolean("draw.rawgps.lines.alpha-blend"+layerNameDot, drawLineWithAlpha.isSelected());
+        putPref("points.hdopcircle", hdopCircleGpsPoints.isSelected());
+        putPref("points.large", largeGpsPoints.isSelected());
+        putPref("lines.width", drawLineWidth.getText());
+        putPref("lines.alpha-blend", drawLineWithAlpha.isSelected());
 
         Config.getPref().putBoolean("mappaint.gpx.use-antialiasing", useGpsAntialiasing.isSelected());
 
-        TemplateEntryProperty.forMarker(layerName).put(waypointLabelPattern.getText());
-        TemplateEntryProperty.forAudioMarker(layerName).put(audioWaypointLabelPattern.getText());
+        TemplateEntryProperty.forMarker(null).put(waypointLabelPattern.getText());
+        TemplateEntryProperty.forAudioMarker(null).put(audioWaypointLabelPattern.getText());
 
         if (colorTypeGlobal.isSelected()) {
-            Config.getPref().put("draw.rawgps.colors"+layerNameDot, null);
-            Config.getPref().put("draw.rawgps.colors.dynamic"+layerNameDot, null);
-            Config.getPref().put("draw.rawgps.colorTracksTunec"+layerNameDot, null);
+            putPref("colormode", null);
+            putPref("colormode.dynamic-range", null);
+            putPref("colormode.velocity.tune", null);
             return false;
         } else if (colorTypeVelocity.isSelected()) {
-            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 1);
+            putPref("colormode", 1);
         } else if (colorTypeDilution.isSelected()) {
-            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 2);
+            putPref("colormode", 2);
         } else if (colorTypeDirection.isSelected()) {
-            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 3);
+            putPref("colormode", 3);
         } else if (colorTypeTime.isSelected()) {
-            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 4);
+            putPref("colormode", 4);
         } else if (colorTypeHeatMap.isSelected()) {
-            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 5);
+            putPref("colormode", 5);
         } else if (colorTypeQuality.isSelected()) {
-            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 6);
+            putPref("colormode", 6);
         } else {
-            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 0);
+            putPref("colormode", 0);
         }
-        Config.getPref().putBoolean("draw.rawgps.colors.dynamic"+layerNameDot, colorDynamic.isSelected());
+        putPref("colormode.dynamic-range", colorDynamic.isSelected());
         int ccti = colorTypeVelocityTune.getSelectedIndex();
-        Config.getPref().putInt("draw.rawgps.colorTracksTune"+layerNameDot, ccti == 2 ? 10 : (ccti == 1 ? 20 : 45));
-        Config.getPref().putInt("draw.rawgps.heatmap.colormap"+layerNameDot, colorTypeHeatMapTune.getSelectedIndex());
-        Config.getPref().putBoolean("draw.rawgps.heatmap.use-points"+layerNameDot, colorTypeHeatMapPoints.isSelected());
-        Config.getPref().putInt("draw.rawgps.heatmap.gain"+layerNameDot, colorTypeHeatMapGain.getValue());
-        Config.getPref().putInt("draw.rawgps.heatmap.lower-limit"+layerNameDot, colorTypeHeatMapLowerLimit.getValue());
+        putPref("colormode.velocity.tune", ccti == 2 ? 10 : (ccti == 1 ? 20 : 45));
+        putPref("colormode.heatmap.colormap", colorTypeHeatMapTune.getSelectedIndex());
+        putPref("colormode.heatmap.use-points", colorTypeHeatMapPoints.isSelected());
+        putPref("colormode.heatmap.gain", colorTypeHeatMapGain.getValue());
+        putPref("colormode.heatmap.lower-limit", colorTypeHeatMapLowerLimit.getValue());
 
         return false;
     }
 
-    /**
-     * Save preferences from UI controls for initial layer or globally
-     * @return {@code true} when restart is required, {@code false} otherwise
-     */
-    public boolean savePreferences() {
-        return savePreferences(null, false);
-    }
-
     private static void updateWaypointLabelCombobox(JosmComboBox<String> cb, JosmTextField tf, TemplateEntryProperty property) {
         String labelPattern = property.getAsString();
         boolean found = false;
Index: src/org/openstreetmap/josm/io/GpxReader.java
===================================================================
--- src/org/openstreetmap/josm/io/GpxReader.java	(revision 15454)
+++ src/org/openstreetmap/josm/io/GpxReader.java	(working copy)
@@ -53,6 +53,7 @@
         RTE,
         TRK,
         EXT,
+        JOSMPREFS,
         AUTHOR,
         LINK,
         TRKSEG,
@@ -76,7 +77,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 +160,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 +178,6 @@
                 case "extensions":
                     states.push(currentState);
                     currentState = State.EXT;
-                    currentExtensions = new Extensions();
                     break;
                 case "copyright":
                     states.push(currentState);
@@ -228,7 +227,6 @@
                 case "extensions":
                     states.push(currentState);
                     currentState = State.EXT;
-                    currentExtensions = new Extensions();
                     break;
                 default: // Do nothing
                 }
@@ -250,7 +248,6 @@
                 case "extensions":
                     states.push(currentState);
                     currentState = State.EXT;
-                    currentExtensions = new Extensions();
                     break;
                 default: // Do nothing
                 }
@@ -270,11 +267,22 @@
                 case "extensions":
                     states.push(currentState);
                     currentState = State.EXT;
-                    currentExtensions = new Extensions();
                     break;
                 default: // Do nothing
                 }
                 break;
+            case EXT:
+                if ("layerPreferences".equals(localName)) {
+                    states.push(currentState);
+                    currentState = State.JOSMPREFS;
+                }
+                break;
+            case JOSMPREFS:
+                String k, v;
+                if ("entry".equals(localName) && (k = atts.getValue("key")) != null && (v = atts.getValue("value")) != null) {
+                    data.layerPrefs.put(k, v);
+                }
+                break;
             default: // Do nothing
             }
             accumulator.setLength(0);
@@ -349,9 +357,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 +470,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 +488,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,13 +506,18 @@
                 }
                 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;
+            case JOSMPREFS:
+                if ("layerPreferences".equals(localName)) {
+                    currentState = states.pop();
+                }
+                break;
             default:
                 switch (localName) {
                 case "wpt":
@@ -519,6 +531,7 @@
                 default: // Do nothing
                 }
             }
+            accumulator.setLength(0);
         }
 
         @Override
@@ -525,9 +538,11 @@
         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"))) {
-                data.fromServer = true;
+            Extensions josmMetaExt = (Extensions) data.get(EXTENSIONS_JOSM);
+            if (josmMetaExt != null) {
+                if ("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 15454)
+++ 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,25 @@
     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, true);
+    }
+
+    /**
+     * 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>
+     * @param savePrefs whether layer specific preferences are saved
+     */
+    public void write(GpxData data, String colorFormat, boolean savePrefs) {
         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,24 +89,120 @@
         // 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 || !data.layerPrefs.isEmpty(),
+                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();
+        writeMetaData(savePrefs);
         writeWayPoints();
         writeRoutes();
         writeTracks();
@@ -97,6 +211,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 +220,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,9 +242,18 @@
                 }
             }
         }
+        gpxExtensions(allExtensions);
     }
 
-    private void writeMetaData() {
+    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(boolean savePrefs) {
         Map<String, Object> attr = data.attr;
         openln("metadata");
 
@@ -147,7 +272,7 @@
             if (attr.containsKey(META_AUTHOR_EMAIL)) {
                 String[] tmp = data.getString(META_AUTHOR_EMAIL).split("@");
                 if (tmp.length == 2) {
-                    inline("email", "id=\"" + tmp[0] + "\" domain=\""+tmp[1]+'\"');
+                    inline("email", "id=\"" + encode(tmp[0]) + "\" domain=\"" + encode(tmp[1]) +'\"');
                 }
             }
             // write the author link
@@ -158,7 +283,7 @@
         // write the copyright details
         if (attr.containsKey(META_COPYRIGHT_LICENSE)
                 || attr.containsKey(META_COPYRIGHT_YEAR)) {
-            openAtt("copyright", "author=\""+ data.get(META_COPYRIGHT_AUTHOR) +'\"');
+            openAtt("copyright", "author=\""+ encode(data.get(META_COPYRIGHT_AUTHOR).toString()) +'\"');
             if (attr.containsKey(META_COPYRIGHT_YEAR)) {
                 simpleTag("year", (String) data.get(META_COPYRIGHT_YEAR));
             }
@@ -187,9 +312,24 @@
             inline("bounds", b);
         }
 
-        if (data.fromServer) {
+        if (data.fromServer || (savePrefs && !data.layerPrefs.isEmpty())) {
             openln("extensions");
-            simpleTag("josm:from-server", "true");
+
+            if (data.fromServer) {
+                simpleTag("josm:from-server", "true");
+            }
+
+
+            if (!data.layerPrefs.isEmpty()) {
+                openln("josm:layerPreferences");
+                ArrayList<Entry<String, String>> prefs = new ArrayList<>(data.layerPrefs.entrySet());
+                prefs.sort((o1, o2) -> o1.getKey().compareTo(o2.getKey()));
+                for (Entry<String, String> pref : prefs) {
+                    inline("josm:entry", "key=\"" + encode(pref.getKey()) + "\" value=\"" + encode(pref.getValue()) + "\"");
+                }
+                closeln("josm:layerPreferences");
+            }
+
             closeln("extensions");
         }
 
@@ -278,7 +418,7 @@
      */
     private void gpxLink(GpxLink link) {
         if (link != null) {
-            openAtt("link", "href=\"" + link.uri + '\"');
+            openAtt("link", "href=\"" + encode(link.uri) + '\"');
             simpleTag("text", link.text);
             simpleTag("type", link.type);
             closeln("link");
@@ -318,11 +458,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 15454)
+++ 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 15454)
+++ 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 15454)
+++ 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 15454)
+++ 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 15454)
+++ 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 15454)
+++ 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 15454)
+++ 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
+    }
+
 }
