Ticket #16796: GpxColors-v0.12.diff

File GpxColors-v0.12.diff, 103.8 KB (added by Bjoeni, 5 years ago)
  • data/gpx-drawing-extensions-1.0.xsd

     
     1<?xml version="1.0" encoding="UTF-8"?>
     2<schema targetNamespace="https://josm.openstreetmap.de/gpx-drawing-extensions-1.0"
     3        elementFormDefault="qualified"
     4        xmlns="http://www.w3.org/2001/XMLSchema"
     5        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     6        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     7        xmlns:gpxd="https://josm.openstreetmap.de/gpx-drawing-extensions-1.0"
     8        xsi:schemaLocation="https://josm.openstreetmap.de/gpx-drawing-extensions-1.0 https://josm.openstreetmap.de/gpx-drawing-extensions-1.0.xsd">
     9
     10        <xsd:annotation>
     11                <xsd:documentation>
     12                        This schema defines drawing extensions for the GPX 1.1 schema (http://www.topografix.com/GPX/1/1/gpx.xsd).
     13                        Elements in this schema should be used as child elements of the "extensions" element defined by the GPX schema.
     14                </xsd:documentation>
     15        </xsd:annotation>
     16       
     17        <!-- Elements -->
     18
     19        <xsd:element name="color" type="gpxd:hexColor_type">
     20                <xsd:annotation>
     21                        <xsd:documentation>
     22                                The color of the element, i.e. #RRGGBB or #RRGGBBAA.
     23                                Note that applications should apply possible alpha values to the lines and opacity to the whole track. This means that overlapping parts of the
     24                                track with alpha values will look more intense than individual lines, whereas the opacity affects the whole track including overlapping parts.
     25                        </xsd:documentation>
     26                </xsd:annotation>
     27        </xsd:element>
     28       
     29        <xsd:element name="opacity" type="gpxd:opacity_type">
     30                <xsd:annotation>
     31                        <xsd:documentation>
     32                                The opacity of the element between 0.00 and 1.00.
     33                        </xsd:documentation>
     34                </xsd:annotation>
     35        </xsd:element>
     36       
     37        <xsd:element name="width" type="xsd:positiveInteger">
     38                <xsd:annotation>
     39                        <xsd:documentation>
     40                                The width of the line in pixels, applications may use a width relative to this value if required.
     41                        </xsd:documentation>
     42                </xsd:annotation>
     43        </xsd:element>
     44       
     45        <xsd:element name="dashPattern" type="gpxd:dashPattern_type">
     46                <xsd:annotation>
     47                        <xsd:documentation>
     48                                The dash pattern of the line, see gpxd:dashPattern_type. Should always be relative to the width.
     49                        </xsd:documentation>
     50                </xsd:annotation>
     51        </xsd:element>
     52       
     53        <!-- Types -->
     54
     55        <xsd:simpleType name="hexColor_type">
     56                <xsd:annotation>
     57                        <xsd:documentation>
     58                                The hexColor_type must be a # followed by a 6 or 8-digit hex representation of the color (with or without the alpha value).                     
     59                        </xsd:documentation>
     60                </xsd:annotation>
     61                <xsd:restriction base="xsd:string">
     62                        <xsd:pattern value="\#([a-fA-F0-9]{6}|[a-fA-F0-9]{8})" />
     63                        <xsd:whiteSpace value="collapse" />
     64                </xsd:restriction>
     65        </xsd:simpleType>
     66
     67        <xsd:simpleType name="opacity_type">
     68                <xsd:annotation>
     69                        <xsd:documentation>
     70                                The opacity_type must be a decimal value between 0 and 1.
     71                        </xsd:documentation>
     72                </xsd:annotation>
     73                <xsd:restriction base="xsd:decimal">
     74                        <xsd:minInclusive value="0" />
     75                        <xsd:maxInclusive value="1" />
     76                </xsd:restriction>
     77        </xsd:simpleType>
     78       
     79        <xsd:simpleType name="dashPattern_type">
     80                <xsd:annotation>
     81                        <xsd:documentation>
     82                                The dashPattern_type can be
     83                                        - a representation of the pattern as y-n-y-n-... with y being the relative length of the line that is
     84                                          visible and n being the relative length of the line that is hidden to create a dashed / dotted line.
     85                                          Has to have an even number of segments (at least two) and can contain multi-digit numbers.
     86                                        - one of the following predefined values:
     87                                          none, dash-long, dash-medium, dash-short, dot-sparse, dot-normal, dot-dense, dash-dot, dash-dot-dot
     88                        </xsd:documentation>
     89                </xsd:annotation>
     90                <xsd:restriction base="xsd:string"> <!-- use string based pattern instead of enum because both pattern and enums are allowed -->
     91                        <xsd:pattern value="\d+\-\d+(\-\d+\-\d+)*" /> <!-- pattern, see documentation above -->
     92                        <xsd:pattern value="none" />            <!-- 1-0, default value/line -->
     93                        <xsd:pattern value="dash-long" />       <!-- 6-2 -->
     94                        <xsd:pattern value="dash-medium" />     <!-- 4-4 -->
     95                        <xsd:pattern value="dash-short" />      <!-- 2-6 -->
     96                        <xsd:pattern value="dot-sparse" />      <!-- 1-4 -->
     97                        <xsd:pattern value="dot-normal" />      <!-- 1-2 -->
     98                        <xsd:pattern value="dot-dense" />       <!-- 1-1 -->
     99                        <xsd:pattern value="dash-dot" />        <!-- 4-2-1-2 -->
     100                        <xsd:pattern value="dash-dot-dot" /><!-- 4-2-1-2-1-2 -->                       
     101                </xsd:restriction>
     102        </xsd:simpleType>
     103       
     104</schema>
     105 No newline at end of file
  • src/org/openstreetmap/josm/data/gpx/Extensions.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
    4 import java.util.LinkedHashMap;
     4import java.util.TreeMap;
    55
     6import org.apache.commons.jcs.access.exception.InvalidArgumentException;
     7
    68/**
    7  * Data class for extensions in a GPX-File.
     9 * Data class for extensions of a specific type in a GPX-File (keys are case-insensitive).
    810 */
    9 public class Extensions extends LinkedHashMap<String, String> {
     11public final class Extensions extends TreeMap<String, String> {
    1012
    1113    private static final long serialVersionUID = 1L;
     14    private String type;
    1215
    1316    /**
    1417     * Constructs a new {@code Extensions}.
     18     * @param extensionType type of the extension, can be any <code>GpxConstants.EXTENSIONS_*</code>
    1519     */
    16     public Extensions() {
    17         super();
     20    public Extensions(String extensionType) {
     21        super(String.CASE_INSENSITIVE_ORDER);
     22        if (!extensionType.startsWith(GpxConstants.EXTENSIONS_PREFIX))
     23            throw new InvalidExtensionException();
     24        type = extensionType;
    1825    }
     26
     27    /**
     28     * Returns the prefix for the XML namespace.
     29     * @return the prefix including the <code>:</code>
     30     */
     31    public String getPrefix() {
     32        return type.substring(type.lastIndexOf(".") + 1) + ":";
     33    }
     34
     35    /**
     36     * @return type of the extension
     37     */
     38    public String getType() {
     39        return type;
     40    }
     41
     42    /**
     43     * InvalidExtensionException is thrown if the Extension value does not match the expected format
     44     * @see GpxConstants#EXTENSIONS_PREFIX
     45     */
     46    public static class InvalidExtensionException extends InvalidArgumentException {
     47        /**
     48         * Constructs a new {@link InvalidExtensionException} with a default error message
     49         */
     50        public InvalidExtensionException() {
     51            super("The extensionType must start with the extensions prefix.");
     52        }
     53        /**
     54         * Constructs a new {@link InvalidExtensionException} with the given error message
     55         * @param message
     56         */
     57        public InvalidExtensionException(String message) {
     58            super(message);
     59        }
     60    }
     61
    1962}
  • src/org/openstreetmap/josm/data/gpx/GpxConstants.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
     4import java.awt.Color;
    45import java.util.Arrays;
    56import java.util.Collection;
    67import java.util.Collections;
    78import java.util.List;
     9import java.util.Map;
     10import java.util.TreeMap;
    811
    912import org.openstreetmap.josm.data.Bounds;
    1013import org.openstreetmap.josm.spi.preferences.Config;
     
    9295     * @see GpxData#getMetaBounds()
    9396     */
    9497    String META_BOUNDS = META_PREFIX + "bounds";
     98
    9599    /**
    96      * A constant for the metadata hash map: the extension data. This is a {@link Extensions} object
    97      * @see GpxData#addExtension(String, String)
     100     * Prefix used for all extension values.
     101     * Note that all extension values <b>must</b> end with the according XML namespace prefix (i.e <code>.josm</code> or <code>.gpxd</code>)
     102     */
     103    String EXTENSIONS_PREFIX = "extensions.";
     104
     105    /**
     106     * The JOSM extension data (josm:*). This is a {@link Extensions} object
     107     * @see GpxData#addExtensionKey(String, String, String)
    98108     * @see GpxData#get(String)
    99109     */
    100     String META_EXTENSIONS = META_PREFIX + "extensions";
     110    String EXTENSIONS_JOSM = EXTENSIONS_PREFIX + "josm";
    101111
    102112    /**
    103      * A namespace for josm GPX extensions
     113     * The GPX drawing extension data (gpxd:*). This is a {@link Extensions} object
    104114     */
    105     String JOSM_EXTENSIONS_NAMESPACE_URI = Config.getUrls().getXMLBase() + "/gpx-extensions-1.0";
     115    String EXTENSIONS_DRAWING = EXTENSIONS_PREFIX + "gpxd";
    106116
     117    /**
     118     * The Garmin GPX extension data (gpxx:*). This is a {@link Extensions} object
     119     */
     120    String EXTENSIONS_GARMIN = EXTENSIONS_PREFIX + "gpxx";
     121
     122    /**
     123     * Namespace for JOSM GPX extensions
     124     */
     125    String XML_URI_EXTENSIONS_JOSM = Config.getUrls().getXMLBase() + "/gpx-extensions-1.0";
     126    /**
     127     * Location of the XSD schema for JOSM GPX extensions
     128     */
     129    String XML_XSD_EXTENSIONS_JOSM = Config.getUrls().getXMLBase() + "/gpx-extensions-1.0.xsd";
     130
     131    /**
     132     * Namespace for GPX drawing extensions
     133     */
     134    String XML_URI_EXTENSIONS_DRAWING = Config.getUrls().getXMLBase() + "/gpx-drawing-extensions-1.0";
     135    /**
     136     * Location of the XSD schema for GPX drawing extensions
     137     */
     138    String XML_XSD_EXTENSIONS_DRAWING = Config.getUrls().getXMLBase() + "/gpx-drawing-extensions-1.0.xsd";
     139
     140    /**
     141     * Namespace for Garmin GPX extensions
     142     */
     143    String XML_URI_EXTENSIONS_GARMIN = "http://www.garmin.com/xmlschemas/GpxExtensions/v3";
     144    /**
     145     * Location of the XSD schema for GPX drawing extensions
     146     */
     147    String XML_XSD_EXTENSIONS_GARMIN = "http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd";
     148
    107149    /** Elevation (in meters) of the point. */
    108150    String PT_ELE = "ele";
    109151
     
    151193     */
    152194    List<String> WPT_KEYS = Collections.unmodifiableList(Arrays.asList(PT_ELE, PT_TIME, PT_MAGVAR, PT_GEOIDHEIGHT,
    153195            GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, PT_SYM, PT_TYPE,
    154             PT_FIX, PT_SAT, PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID, META_EXTENSIONS));
     196            PT_FIX, PT_SAT, PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID,
     197            EXTENSIONS_JOSM, EXTENSIONS_DRAWING, EXTENSIONS_GARMIN));
    155198
    156199    /**
    157200     * Ordered list of all possible route and track keys.
    158201     */
    159202    List<String> RTE_TRK_KEYS = Collections.unmodifiableList(Arrays.asList(
    160             GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, "number", PT_TYPE, META_EXTENSIONS));
     203            GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, "number", PT_TYPE,
     204            EXTENSIONS_JOSM, EXTENSIONS_DRAWING, EXTENSIONS_GARMIN));
    161205
    162206    /**
     207     * Possible extension namespaces
     208     */
     209    List<String> SUPPORTED_EXTENSION_NAPMESPACES = Collections.unmodifiableList(Arrays.asList(
     210            "josm", "gpxx", "gpxd"));
     211
     212    /**
     213     * Map with all supported Garmin colors
     214     */
     215    Map<String, Color> GARMIN_COLORS = Collections.unmodifiableMap(
     216            new TreeMap<String, Color>(String.CASE_INSENSITIVE_ORDER) {{
     217                put("Black", Color.BLACK);
     218                put("DarkRed", new Color(139, 0, 0));
     219                put("DarkGreen", new Color(0, 100, 0));
     220                put("DarkYellow", new Color(255, 170, 0));
     221                put("DarkBlue", new Color(0, 0, 139));
     222                put("DarkMagenta", new Color(139, 0, 139));
     223                put("DarkCyan", new Color(0, 139, 139));
     224                put("LightGray", Color.LIGHT_GRAY);
     225                put("DarkGray", Color.DARK_GRAY);
     226                put("Red", Color.RED);
     227                put("Green", Color.GREEN);
     228                put("Yellow", Color.YELLOW);
     229                put("Blue", Color.BLUE);
     230                put("Magenta", Color.MAGENTA);
     231                put("Cyan", Color.CYAN);
     232                put("White", Color.WHITE);
     233                put("Transparent", new Color(0, 0, 0, 255));
     234            }});
     235
     236    /**
    163237     * Possible fix values.
    164238     */
    165239    Collection<String> FIX_VALUES = Collections.unmodifiableList(Arrays.asList("none", "2d", "3d", "dgps", "pps"));
  • src/org/openstreetmap/josm/data/gpx/GpxData.java

     
    6565     * Addidionaly waypoints for this file.
    6666     */
    6767    private final ArrayList<WayPoint> privateWaypoints = new ArrayList<>();
    68     private final GpxTrackChangeListener proxy = e -> fireInvalidate();
     68    private final GpxTrackChangeListener proxy = e -> {fireInvalidate(); modified = true;};
     69    private boolean modified = false;
    6970
    7071    /**
    7172     * Tracks. Access is discouraged, use {@link #getTracks()} to read.
     
    881882        private Line next;
    882883        private final boolean[] trackVisibility;
    883884        private Map<String, Object> trackAttributes;
     885        private GpxTrack curTrack;
    884886
    885887        /**
    886888         * Constructs a new {@code LinesIterator}.
     
    914916        private Line getNext() {
    915917            if (itTracks != null) {
    916918                if (itTrackSegments != null && itTrackSegments.hasNext()) {
    917                     return new Line(itTrackSegments.next(), trackAttributes);
     919                    return new Line(itTrackSegments.next(), trackAttributes, curTrack.getColor());
    918920                } else {
    919921                    while (itTracks.hasNext()) {
    920                         GpxTrack nxtTrack = itTracks.next();
    921                         trackAttributes = nxtTrack.getAttributes();
     922                        curTrack = itTracks.next();
     923                        trackAttributes = curTrack.getAttributes();
    922924                        idxTracks++;
    923925                        if (trackVisibility != null && !trackVisibility[idxTracks])
    924926                            continue;
    925                         itTrackSegments = nxtTrack.getSegments().iterator();
     927                        itTrackSegments = curTrack.getSegments().iterator();
    926928                        if (itTrackSegments.hasNext()) {
    927                             return new Line(itTrackSegments.next(), trackAttributes);
     929                            return new Line(itTrackSegments.next(), trackAttributes, curTrack.getColor());
    928930                        }
    929931                    }
    930932                    // if we get here, all the Tracks are finished; Continue with Routes
     
    10601062            return source;
    10611063        }
    10621064    }
     1065
     1066    /**
     1067     * @return whether anything (i.e. colors) have been modified
     1068     */
     1069    public boolean isModified() {
     1070        return modified;
     1071    }
    10631072}
  • src/org/openstreetmap/josm/data/gpx/GpxTrack.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
     4import java.awt.Color;
    45import java.util.Collection;
    56import java.util.Map;
    67
     
    78import org.openstreetmap.josm.data.Bounds;
    89
    910/**
    10  * Read-only gpx track. Implementations doesn't have to be immutable, but should always be thread safe.
     11 * Gpx track. Implementations doesn't have to be immutable, but should always be thread safe.
    1112 * @since 444
    1213 */
    1314public interface GpxTrack extends IWithAttributes {
     
    3738    double length();
    3839
    3940    /**
     41     * Gets the color of this track.
     42     * @return The color, <code>null</code> if not set or not supported by the implementation.
     43     * @since xxx
     44     */
     45    default Color getColor() {
     46        return null;
     47    }
     48
     49    /**
     50     * Sets the color of this track. Not necessarily supported by all implementations.
     51     * @param color
     52     * @since xxx
     53     */
     54    default void setColor(Color color) {
     55        return;
     56    }
     57
     58    /**
    4059     * Add a listener that listens to changes in the GPX track.
    4160     * @param l The listener
    4261     */
  • src/org/openstreetmap/josm/data/gpx/IWithAttributes.java

     
    22package org.openstreetmap.josm.data.gpx;
    33
    44import java.util.Collection;
     5import java.util.Map;
    56
    67/**
    78 * Object with attributes (in the context of GPX data).
     
    5051    void put(String key, Object value);
    5152
    5253    /**
    53      * Add a key / value pair that is not part of the GPX schema as an extension.
    54      *
     54     * Add a key / value pair that is not part of the GPX schema as an extension and the extension attribute if not already present.
     55     * @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>
    5556     * @param key the key
    5657     * @param value the value
     58     * @see GpxConstants#EXTENSIONS_PREFIX
    5759     */
    58     void addExtension(String key, String value);
     60    void addExtensionKey(String extensionType, String key, String value);
    5961
     62    /**
     63     * Add key / value pairs that are not part of the GPX schema as extensions.
     64     * @param extensionMap the <code>Map&lt;String, String&gt;</code> with the key being the qualified name including prefix of the namespace
     65     * @see GpxConstants#EXTENSIONS_PREFIX
     66     * @see Extensions
     67     */
     68    void addExtensions(Map<String, String> extensionMap);
     69
     70    /**
     71     * Removes a key from the extension if present and the extension if empty
     72     * @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>
     73     * @param key the key, not case-sensitive
     74     * @see GpxConstants#EXTENSIONS_PREFIX
     75     */
     76    void removeExtensionKey(String extensionType, String key);
     77
    6078}
  • src/org/openstreetmap/josm/data/gpx/ImmutableGpxTrack.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
     4import java.awt.Color;
    45import java.util.ArrayList;
    56import java.util.Collection;
    67import java.util.Collections;
     
    910import java.util.Map;
    1011
    1112import org.openstreetmap.josm.data.Bounds;
     13import org.openstreetmap.josm.tools.ListenerList;
     14import org.openstreetmap.josm.tools.Logging;
    1215
    1316/**
    1417 * Immutable GPX track.
     18 * Note that the color attributes are not immutable and may be modified by the user.
    1519 * @since 2907
    1620 */
    1721public class ImmutableGpxTrack extends WithAttributes implements GpxTrack {
     
    1923    private final List<GpxTrackSegment> segments;
    2024    private final double length;
    2125    private final Bounds bounds;
     26    private Color colorCache;
     27    private final ListenerList<GpxTrackChangeListener> listeners = ListenerList.create();
    2228
    2329    /**
    24      * Constructs a new {@code ImmutableGpxTrack}.
     30     * Constructs a new {@code ImmutableGpxTrack} and adds the extensions.
    2531     * @param trackSegs track segments
    2632     * @param attributes track attributes
     33     * @param extensionMap map of extensions
    2734     */
    28     public ImmutableGpxTrack(Collection<Collection<WayPoint>> trackSegs, Map<String, Object> attributes) {
     35    public ImmutableGpxTrack(Collection<Collection<WayPoint>> trackSegs, Map<String, Object> attributes, Map<String, String> extensionMap) {
    2936        List<GpxTrackSegment> newSegments = new ArrayList<>();
    3037        for (Collection<WayPoint> trackSeg: trackSegs) {
    3138            if (trackSeg != null && !trackSeg.isEmpty()) {
     
    3239                newSegments.add(new ImmutableGpxTrackSegment(trackSeg));
    3340            }
    3441        }
    35         this.attr = Collections.unmodifiableMap(new HashMap<>(attributes));
     42        this.attr = new HashMap<>(attributes);
     43        if (extensionMap != null) {
     44            addExtensions(extensionMap);
     45        }
    3646        this.segments = Collections.unmodifiableList(newSegments);
    3747        this.length = calculateLength();
    3848        this.bounds = calculateBounds();
     
    3949    }
    4050
    4151    /**
     52     * Constructs a new {@code ImmutableGpxTrack}.
     53     * @param trackSegs track segments
     54     * @param attributes track attributes
     55     */
     56    public ImmutableGpxTrack(Collection<Collection<WayPoint>> trackSegs, Map<String, Object> attributes) {
     57        this(trackSegs, attributes, null);
     58    }
     59
     60    /**
    4261     * Constructs a new {@code ImmutableGpxTrack} from {@code GpxTrackSegment} objects.
    4362     * @param segments The segments to build the track from.  Input is not deep-copied,
    4463     *                 which means the caller may reuse the same segments to build
     
    4867     * @since 13210
    4968     */
    5069    public ImmutableGpxTrack(List<GpxTrackSegment> segments, Map<String, Object> attributes) {
    51         this.attr = Collections.unmodifiableMap(new HashMap<>(attributes));
     70        this.attr = new HashMap<>(attributes);
    5271        this.segments = Collections.unmodifiableList(segments);
    5372        this.length = calculateLength();
    5473        this.bounds = calculateBounds();
     
    7998    }
    8099
    81100    @Override
     101    public void setColor(Color color) {
     102        setColorAttr(color);
     103        colorCache = color;
     104    }
     105
     106    private void setColorAttr(Color color) {
     107        removeExtensionKey(GpxConstants.EXTENSIONS_GARMIN, "displaycolor");
     108        if (color != null) {
     109            addExtensionKey(EXTENSIONS_DRAWING, "color", String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()));
     110        } else {
     111            removeExtensionKey(EXTENSIONS_DRAWING, "color");
     112        }
     113    }
     114
     115    @Override
     116    public Color getColor() {
     117        if (colorCache == null) {
     118            colorCache = getColorFromAttr(attr);
     119        }
     120        return colorCache;
     121    }
     122
     123
     124    private Color getColorFromAttr(Map<String, Object> attr) {
     125        Extensions gpxd = (Extensions) attr.get(GpxConstants.EXTENSIONS_DRAWING);
     126        if (gpxd != null) {
     127            String cs = gpxd.get("color");
     128            try {
     129                return Color.decode(cs);
     130            } catch (NumberFormatException ex) {
     131                Logging.warn("Could not read gpxd color: " + cs);
     132            }
     133        } else {
     134            Extensions gpxx = (Extensions) attr.get(GpxConstants.EXTENSIONS_GARMIN);
     135            if (gpxx != null) {
     136                String cs = gpxx.get("displaycolor");
     137                if (cs != null) {
     138                    Color cc = GpxConstants.GARMIN_COLORS.get(cs);
     139                    if (cc != null) {
     140                        return cc;
     141                    }
     142                }
     143                Logging.warn("Could not read garmin color: " + cs);
     144            }
     145        }
     146        return null;
     147    }
     148
     149    @Override
     150    public void addExtensionKey(String extensionType, String key, String value) {
     151        colorCache = null;
     152        super.addExtensionKey(extensionType, key, value);
     153        listeners.fireEvent(l -> l.gpxDataChanged(new GpxTrackChangeEvent(this)));
     154    }
     155
     156    @Override
     157    public void removeExtensionKey(String extensionType, String key) {
     158        colorCache = null;
     159        super.removeExtensionKey(extensionType, key);
     160        listeners.fireEvent(l -> l.gpxDataChanged(new GpxTrackChangeEvent(this)));
     161    }
     162
     163    @Override
    82164    public Map<String, Object> getAttributes() {
    83165        return attr;
    84166    }
     
    119201            return false;
    120202        return true;
    121203    }
     204
     205    @Override
     206    public void addListener(GpxTrackChangeListener l) {
     207        listeners.addListener(l);
     208    }
     209
     210    @Override
     211    public void removeListener(GpxTrackChangeListener l) {
     212        listeners.removeListener(l);
     213    }
     214
    122215}
  • src/org/openstreetmap/josm/data/gpx/Line.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
     4import java.awt.Color;
    45import java.util.Collection;
    56import java.util.Iterator;
    67import java.util.Map;
     
    1314public class Line implements Collection<WayPoint> {
    1415    private final Collection<WayPoint> waypoints;
    1516    private final boolean unordered;
     17    private final Color color;
    1618
    1719    /**
    1820     * Constructs a new {@code Line}.
    1921     * @param waypoints collection of waypoints
    2022     * @param attributes track/route attributes
     23     * @param color color of the track
     24     *
    2125     */
    22     public Line(Collection<WayPoint> waypoints, Map<String, Object> attributes) {
     26    public Line(Collection<WayPoint> waypoints, Map<String, Object> attributes, Color color) {
     27        this.color = color;
    2328        this.waypoints = Objects.requireNonNull(waypoints);
    2429        unordered = attributes.isEmpty() && waypoints.stream().allMatch(x -> x.get(GpxConstants.PT_TIME) == null);
    2530    }
     
    2934     * @param trackSegment track segment
    3035     * @param trackAttributes track attributes
    3136     */
    32     public Line(GpxTrackSegment trackSegment, Map<String, Object> trackAttributes) {
    33         this(trackSegment.getWayPoints(), trackAttributes);
     37    public Line(GpxTrackSegment trackSegment, Map<String, Object> trackAttributes, Color color) {
     38        this(trackSegment.getWayPoints(), trackAttributes, color);
    3439    }
    3540
    3641    /**
     
    3843     * @param route route
    3944     */
    4045    public Line(GpxRoute route) {
    41         this(route.routePoints, route.attr);
     46        this(route.routePoints, route.attr, null);
    4247    }
    4348
    4449    /**
     
    4954        return unordered;
    5055    }
    5156
     57    /**
     58     * Returns the track/route color
     59     * @return the color
     60     */
     61    public Color getColor() {
     62        return color;
     63    }
     64
    5265    @Override
    5366    public int size() {
    5467        return waypoints.size();
  • src/org/openstreetmap/josm/data/gpx/WithAttributes.java

     
    44import java.util.Collection;
    55import java.util.HashMap;
    66import java.util.Map;
     7import java.util.Map.Entry;
    78
    89/**
    910 * Default implementation for IWithAttributes.
     
    6465
    6566    /**
    6667     * Put a key / value pair as a new attribute.
    67      *
    6868     * Overrides key / value pair with the same key (if present).
    6969     *
    7070     * @param key the key
     
    7575        attr.put(key, value);
    7676    }
    7777
    78     /**
    79      * Add a key / value pair that is not part of the GPX schema as an extension.
    80      *
    81      * @param key the key
    82      * @param value the value
    83      */
    8478    @Override
    85     public void addExtension(String key, String value) {
    86         if (!attr.containsKey(META_EXTENSIONS)) {
    87             attr.put(META_EXTENSIONS, new Extensions());
     79    public void addExtensionKey(String extensionType, String key, String value) {
     80        Extensions ext;
     81        if (!attr.containsKey(extensionType)) {
     82            ext = new Extensions(extensionType);
     83            attr.put(extensionType, ext);
     84        } else {
     85            ext = (Extensions) attr.get(extensionType);
    8886        }
    89         Extensions ext = (Extensions) attr.get(META_EXTENSIONS);
    9087        ext.put(key, value);
    9188    }
    9289
    9390    @Override
     91    public void addExtensions(Map<String, String> extensionMap) {
     92        for (Entry<String, String> e : extensionMap.entrySet()) {
     93            String k = e.getKey();
     94            int dot = k.indexOf(":");
     95            if (dot != -1) {
     96                addExtensionKey(GpxConstants.EXTENSIONS_PREFIX + k.substring(0, dot), k.substring(dot + 1), e.getValue());
     97            }
     98        }
     99    }
     100
     101    @Override
     102    public void removeExtensionKey(String extensionType, String key) {
     103        Extensions ext = (Extensions) attr.get(extensionType);
     104        if (ext != null) {
     105            ext.remove(key);
     106            if (ext.isEmpty()) {
     107                attr.remove(extensionType);
     108            }
     109        }
     110    }
     111
     112    @Override
    94113    public int hashCode() {
    95114        return 31 + ((attr == null) ? 0 : attr.hashCode());
    96115    }
     
    111130            return false;
    112131        return true;
    113132    }
     133
    114134}
  • src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java

     
    1818import java.util.Arrays;
    1919import java.util.Collections;
    2020import java.util.List;
    21 import java.util.Objects;
    2221import java.util.concurrent.CopyOnWriteArrayList;
    2322
    2423import javax.swing.AbstractAction;
     
    4443import org.openstreetmap.josm.actions.MergeLayerAction;
    4544import org.openstreetmap.josm.data.coor.EastNorth;
    4645import org.openstreetmap.josm.data.imagery.OffsetBookmark;
    47 import org.openstreetmap.josm.data.preferences.AbstractProperty;
    4846import org.openstreetmap.josm.gui.MainApplication;
    4947import org.openstreetmap.josm.gui.MapFrame;
    5048import org.openstreetmap.josm.gui.MapView;
     
    601599                label.setFont(label.getFont().deriveFont(Font.BOLD));
    602600            }
    603601            if (Config.getPref().getBoolean("dialog.layer.colorname", true)) {
    604                 AbstractProperty<Color> prop = layer.getColorProperty();
    605                 Color c = prop == null ? null : prop.get();
    606                 if (c == null || model.getLayers().stream()
    607                         .map(Layer::getColorProperty)
    608                         .filter(Objects::nonNull)
    609                         .map(AbstractProperty::get)
    610                         .noneMatch(oc -> oc != null && !oc.equals(c))) {
    611                     /* not more than one color, don't use coloring */
     602                Color c = layer.getColor();
     603                if (c != null) {
     604                    label.setForeground(c);
     605                } else {
    612606                    label.setForeground(UIManager.getColor(isSelected ? "Table.selectionForeground" : "Table.foreground"));
    613                 } else {
    614                     label.setForeground(c);
    615607                }
    616608            }
    617609            label.setIcon(layer.getIcon());
  • src/org/openstreetmap/josm/gui/dialogs/layer/LayerVisibilityAction.java

     
    593593                    List<Layer> layers = layerSupplier.get();
    594594                    for (Layer l : layers) {
    595595                        if (l instanceof GpxLayer) {
    596                             l.getColorProperty().put(color);
     596                            l.setColor(color);
    597597                        }
    598598                    }
    599599                    highlightColor(color);
     
    602602            add(colorPanel, GBC.std().weight(1, 1).fill().insets(5));
    603603            panels.put(color, colorPanel);
    604604
    605             List<Color> colors = layerSupplier.get().stream().map(l -> l.getColorProperty().get()).distinct().collect(Collectors.toList());
     605            List<Color> colors = layerSupplier.get().stream().map(l -> l.getColor()).distinct().collect(Collectors.toList());
    606606            if (colors.size() == 1) {
    607607                highlightColor(colors.get(0));
    608608            }
     
    611611        @Override
    612612        public void updateLayers(List<Layer> layers, boolean allVisible, boolean allHidden) {
    613613            List<Color> colors = layers.stream().filter(l -> l instanceof GpxLayer)
    614                     .map(l -> ((GpxLayer) l).getColorProperty().get())
     614                    .map(l -> ((GpxLayer) l).getColor())
    615615                    .distinct()
    616616                    .collect(Collectors.toList());
    617617            if (colors.size() == 1) {
  • src/org/openstreetmap/josm/gui/io/importexport/GpxExporter.java

     
    2626
    2727import org.openstreetmap.josm.data.gpx.GpxConstants;
    2828import org.openstreetmap.josm.data.gpx.GpxData;
     29import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    2930import org.openstreetmap.josm.gui.ExtendedDialog;
    3031import org.openstreetmap.josm.gui.MainApplication;
    3132import org.openstreetmap.josm.gui.layer.GpxLayer;
     
    148149        p.add(new JLabel(tr("Keywords")), GBC.eol());
    149150        JosmTextField keywords = new JosmTextField();
    150151        keywords.setText(gpxData.getString(META_KEYWORDS));
    151         p.add(keywords, GBC.eop().fill(GBC.HORIZONTAL));
     152        p.add(keywords, GBC.eol().fill(GBC.HORIZONTAL));
    152153
     154        boolean sel = Config.getPref().getBoolean("gpx.export.colors", true);
     155        JCheckBox colors = new JCheckBox(tr("Save track colors in GPX file"), sel);
     156        p.add(colors, GBC.eol().fill(GBC.HORIZONTAL));
     157        JCheckBox garmin = new JCheckBox(tr("Use Garmin compatible GPX extensions"),
     158                Config.getPref().getBoolean("gpx.export.colors.garmin", false));
     159        garmin.setEnabled(sel);
     160        p.add(garmin, GBC.eop().fill(GBC.HORIZONTAL).insets(20, 0, 0, 0));
     161
     162
    153163        ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(),
    154164                tr("Export options"),
    155165                tr("Export and Save"), tr("Cancel"))
     
    156166            .setButtonIcons("exportgpx", "cancel")
    157167            .setContent(p);
    158168
     169        colors.addActionListener(l -> {
     170            garmin.setEnabled(colors.isSelected());
     171        });
     172
     173        garmin.addActionListener(l -> {
     174            if (garmin.isSelected() &&
     175                    !ConditionalOptionPaneUtil.showConfirmationDialog(
     176                            "gpx_color_garmin",
     177                            ed,
     178                            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>")),
     179                            tr("Information"),
     180                            JOptionPane.OK_CANCEL_OPTION,
     181                            JOptionPane.INFORMATION_MESSAGE,
     182                            JOptionPane.OK_OPTION)) {
     183                garmin.setSelected(false);
     184            }
     185        });
     186
    159187        if (ed.showDialog().getValue() != 1) {
    160188            setCanceled(true);
    161189            return;
     
    169197        if (!copyright.getText().isEmpty()) {
    170198            Config.getPref().put("lastCopyright", copyright.getText());
    171199        }
     200        Config.getPref().putBoolean("gpx.export.colors", colors.isSelected());
     201        Config.getPref().putBoolean("gpx.export.colors.garmin", garmin.isSelected());
     202        String colorFormat = null;
     203        if (colors.isSelected()) {
     204            colorFormat = garmin.isSelected()
     205                    ? GpxConstants.EXTENSIONS_GARMIN
     206                    : GpxConstants.EXTENSIONS_DRAWING;
     207        }
    172208
    173209        if (layer instanceof OsmDataLayer) {
    174210            gpxData = ((OsmDataLayer) layer).toGpxData();
     
    206242        }
    207243
    208244        try (OutputStream fo = Compression.getCompressedFileOutputStream(file)) {
    209             new GpxWriter(fo).write(gpxData);
     245            GpxWriter w = new GpxWriter(fo);
     246            w.write(gpxData, colorFormat);
     247            w.close();
    210248            fo.flush();
    211249        } catch (IOException | InvalidPathException ex) {
    212250            Logging.error(ex);
  • src/org/openstreetmap/josm/gui/layer/CustomizeColor.java

     
    1818import javax.swing.JMenuItem;
    1919import javax.swing.JOptionPane;
    2020
    21 import org.openstreetmap.josm.data.preferences.AbstractProperty;
    2221import org.openstreetmap.josm.gui.MainApplication;
    2322import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
    2423import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
    2524import org.openstreetmap.josm.gui.layer.Layer.MultiLayerAction;
    26 import org.openstreetmap.josm.tools.CheckParameterUtil;
    2725import org.openstreetmap.josm.tools.ImageProvider;
    2826
    2927/**
     
    3331 * of a certain {@link GpxLayer} or {@link org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer}.
    3432 */
    3533public class CustomizeColor extends AbstractAction implements LayerAction, MultiLayerAction {
    36     private final transient List<AbstractProperty<Color>> colors;
     34    private final transient List<Layer> colorLayers;
    3735
    3836    /**
    3937     * Constructs a new {@code CustomizeColor} for a given list of layers.
     
    4240    public CustomizeColor(List<Layer> l) {
    4341        super(tr("Customize Color"));
    4442        new ImageProvider("colorchooser").getResource().attachImageIcon(this, true);
    45         colors = l.stream().map(Layer::getColorProperty).collect(Collectors.toList());
    46         CheckParameterUtil.ensureThat(colors.stream().allMatch(Objects::nonNull), "All layers must have colors.");
     43        colorLayers = l.stream().filter(Objects::nonNull).filter(Layer::hasColor).collect(Collectors.toList());
    4744        putValue("help", ht("/Action/LayerCustomizeColor"));
    4845    }
    4946
     
    5754
    5855    @Override
    5956    public boolean supportLayers(List<Layer> layers) {
    60         return layers.stream().allMatch(l -> l.getColorProperty() != null);
     57        return layers.stream().allMatch(l -> l.hasColor());
    6158    }
    6259
    6360    @Override
     
    7269
    7370    @Override
    7471    public void actionPerformed(ActionEvent e) {
    75         Color cl = colors.stream().map(AbstractProperty::get).filter(Objects::nonNull).findAny().orElse(Color.GRAY);
     72        Color cl = colorLayers.stream().filter(Objects::nonNull).map(Layer::getColor).filter(Objects::nonNull).findAny().orElse(Color.GRAY);
    7673        JColorChooser c = new JColorChooser(cl);
    7774        Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
    7875        int answer = JOptionPane.showOptionDialog(
     
    8784        );
    8885        switch (answer) {
    8986        case 0:
    90             colors.stream().forEach(prop -> prop.put(c.getColor()));
     87            colorLayers.stream().forEach(l -> l.setColor(c.getColor()));
    9188            break;
    9289        case 1:
    9390            return;
    9491        case 2:
    95             colors.stream().forEach(prop -> prop.put(null));
     92            colorLayers.stream().forEach(l -> l.setColor(null));
    9693            break;
    9794        }
    9895        // TODO: Make the layer dialog listen to property change events so that this is not needed any more.
  • src/org/openstreetmap/josm/gui/layer/GpxLayer.java

     
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55import static org.openstreetmap.josm.tools.I18n.trn;
    66
     7import java.awt.Color;
    78import java.awt.Dimension;
    89import java.awt.Graphics2D;
    910import java.awt.event.ActionEvent;
     
    3132import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener;
    3233import org.openstreetmap.josm.data.gpx.GpxTrack;
    3334import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    34 import org.openstreetmap.josm.data.preferences.NamedColorProperty;
    3535import org.openstreetmap.josm.data.projection.Projection;
    3636import org.openstreetmap.josm.gui.MapView;
    3737import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
     
    5353/**
    5454 * A layer that displays data from a Gpx file / the OSM gpx downloads.
    5555 */
    56 public class GpxLayer extends Layer implements ExpertModeChangeListener {
     56public class GpxLayer extends AbstractModifiableLayer implements ExpertModeChangeListener {
    5757
    5858    /** GPX data */
    5959    public GpxData data;
     
    107107    }
    108108
    109109    @Override
    110     protected NamedColorProperty getBaseColorProperty() {
    111         return GpxDrawHelper.DEFAULT_COLOR;
     110    public Color getColor() {
     111        Color[] c = data.getTracks().stream().map(t -> t.getColor()).distinct().toArray(Color[]::new);
     112        return c.length == 1 ? c[0] : null; //only return if exactly one distinct color present
    112113    }
    113114
     115    @Override
     116    public void setColor(Color color) {
     117        for (GpxTrack trk : data.getTracks()) {
     118            trk.setColor(color);
     119        }
     120    }
     121
     122    @Override
     123    public boolean hasColor() {
     124        return true;
     125    }
     126
    114127    /**
    115128     * Returns a human readable string that shows the timespan of the given track
    116129     * @param trk The GPX track for which timespan is displayed
     
    473486    public void expertChanged(boolean isExpert) {
    474487        this.isExpertMode = isExpert;
    475488    }
     489
     490    @Override
     491    public boolean isModified() {
     492        return data.isModified();
     493    }
     494
     495    @Override
     496    public boolean requiresSaveToFile() {
     497        return isModified() && isLocalFile();
     498    }
    476499}
  • src/org/openstreetmap/josm/gui/layer/Layer.java

     
    2525import org.openstreetmap.josm.actions.SaveAsAction;
    2626import org.openstreetmap.josm.data.ProjectionBounds;
    2727import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    28 import org.openstreetmap.josm.data.preferences.AbstractProperty;
    29 import org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeListener;
    30 import org.openstreetmap.josm.data.preferences.NamedColorProperty;
    3128import org.openstreetmap.josm.data.projection.Projection;
    3229import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
    3330import org.openstreetmap.josm.gui.MainApplication;
     
    164161     */
    165162    private File associatedFile;
    166163
    167     private final ValueChangeListener<Object> invalidateListener = change -> invalidate();
    168164    private boolean isDestroyed;
    169165
    170166    /**
     
    198194    public abstract Icon getIcon();
    199195
    200196    /**
    201      * Gets the color property to use for this layer.
    202      * @return The color property.
    203      * @since 10824
     197     * @return whether the layer has / can handle colors.
    204198     */
    205     public AbstractProperty<Color> getColorProperty() {
    206         NamedColorProperty base = getBaseColorProperty();
    207         if (base != null) {
    208             return base.getChildColor(NamedColorProperty.COLOR_CATEGORY_LAYER, getName(), base.getName());
    209         } else {
    210             return null;
    211         }
     199    public boolean hasColor() {
     200        return false;
    212201    }
    213202
    214203    /**
    215      * Gets the color property that stores the default color for this layer.
    216      * @return The property or <code>null</code> if this layer is not colored.
    217      * @since 10824
     204     * Return the current color of the layer
     205     * @return null when not present or not supported
    218206     */
    219     protected NamedColorProperty getBaseColorProperty() {
     207    public Color getColor() {
    220208        return null;
    221209    }
    222210
    223     private void addColorPropertyListener() {
    224         AbstractProperty<Color> colorProperty = getColorProperty();
    225         if (colorProperty != null) {
    226             colorProperty.addListener(invalidateListener);
    227         }
     211    /**
     212     * Sets the color for this layer. Nothing happens if not supported by the layer
     213     * @param color the color to be set
     214     */
     215    public void setColor(Color color) {
    228216    }
    229217
    230     private void removeColorPropertyListener() {
    231         AbstractProperty<Color> colorProperty = getColorProperty();
    232         if (colorProperty != null) {
    233             colorProperty.removeListener(invalidateListener);
    234         }
    235     }
    236 
    237218    /**
    238219     * @return A small tooltip hint about some statistics for this layer.
    239220     */
     
    302283        }
    303284        isDestroyed = true;
    304285        // Override in subclasses if needed
    305         removeColorPropertyListener();
    306286    }
    307287
    308288    /**
     
    339319     * @param name the name. If null, the name is set to the empty string.
    340320     */
    341321    public void setName(String name) {
    342         if (this.name != null) {
    343             removeColorPropertyListener();
    344         }
    345322        String oldValue = this.name;
    346323        this.name = Optional.ofNullable(name).orElse("");
    347324        if (!this.name.equals(oldValue)) {
    348325            propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
    349326        }
    350 
    351         // re-add listener
    352         addColorPropertyListener();
    353327        invalidate();
    354328    }
    355329
  • src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

     
    3131import java.util.LinkedHashMap;
    3232import java.util.List;
    3333import java.util.Map;
     34import java.util.Map.Entry;
    3435import java.util.Set;
    3536import java.util.concurrent.CopyOnWriteArrayList;
    3637import java.util.concurrent.atomic.AtomicBoolean;
     
    747748            }
    748749            Collection<Collection<WayPoint>> trk = new ArrayList<>();
    749750            Map<String, Object> trkAttr = new HashMap<>();
    750 
    751             String name = w.get("name");
    752             if (name != null) {
    753                 trkAttr.put("name", name);
     751            Map<String, String> trkExts = new HashMap<>();
     752            for (Entry<String, String> e : w.getKeys().entrySet()) {
     753                String k = e.getKey();
     754                String v = e.getValue();
     755                if (GpxConstants.RTE_TRK_KEYS.contains(e.getKey())) {
     756                    trkAttr.put(k, v);
     757                } else if (GpxConstants.SUPPORTED_EXTENSION_NAPMESPACES.stream().anyMatch(
     758                        s -> k.startsWith(s + ":"))) {
     759                    trkExts.put(k, v);
     760                }
    754761            }
    755762
    756763            List<WayPoint> trkseg = null;
     
    769776                trkseg.add(nodeToWayPoint(n));
    770777            }
    771778
    772             gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr));
     779            gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr, trkExts));
    773780        });
    774781    }
    775782
  • src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java

     
    44import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
    55import static org.openstreetmap.josm.tools.I18n.tr;
    66
     7import java.awt.Color;
    78import java.awt.Component;
    89import java.awt.Dimension;
    910import java.awt.GridBagLayout;
     
    1213import java.awt.event.MouseEvent;
    1314import java.awt.event.MouseListener;
    1415import java.io.Serializable;
     16import java.util.ArrayList;
    1517import java.util.Arrays;
    1618import java.util.Comparator;
     19import java.util.List;
    1720import java.util.Map;
     21import java.util.Objects;
    1822import java.util.Optional;
    1923
    2024import javax.swing.AbstractAction;
     25import javax.swing.JColorChooser;
    2126import javax.swing.JComponent;
    2227import javax.swing.JLabel;
     28import javax.swing.JOptionPane;
    2329import javax.swing.JPanel;
    2430import javax.swing.JScrollPane;
    2531import javax.swing.JTable;
    2632import javax.swing.JToggleButton;
    2733import javax.swing.ListSelectionModel;
     34import javax.swing.event.TableModelEvent;
    2835import javax.swing.table.DefaultTableModel;
    2936import javax.swing.table.TableCellRenderer;
    3037import javax.swing.table.TableRowSorter;
    3138
     39import org.apache.commons.jcs.access.exception.InvalidArgumentException;
    3240import org.openstreetmap.josm.data.SystemOfMeasurement;
    3341import org.openstreetmap.josm.data.gpx.GpxConstants;
    3442import org.openstreetmap.josm.data.gpx.GpxTrack;
     
    5563     * @param layer The associated GPX layer
    5664     */
    5765    public ChooseTrackVisibilityAction(final GpxLayer layer) {
    58         super(tr("Choose visible tracks"));
     66        super(tr("Choose track visibility and colors"));
    5967        new ImageProvider("dialogs/filter").getResource().attachImageIcon(this, true);
    6068        this.layer = layer;
    6169        putValue("help", ht("/Action/ChooseTrackVisibility"));
     
    116124            String time = GpxLayer.getTimespanForTrack(trk);
    117125            TrackLength length = new TrackLength(trk.length());
    118126            String url = (String) Optional.ofNullable(attr.get("url")).orElse("");
    119             tracks[i] = new Object[]{name, desc, time, length, url};
     127            tracks[i] = new Object[]{name, desc, time, length, url, trk};
    120128            i++;
    121129        }
    122130        return tracks;
    123131    }
    124132
     133    private void showColorDialog(List<GpxTrack> tracks) {
     134        Color cl = tracks.stream().filter(Objects::nonNull)
     135                .map(GpxTrack::getColor).filter(Objects::nonNull)
     136                .findAny().orElse(GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get());
     137        JColorChooser c = new JColorChooser(cl);
     138        Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
     139        int answer = JOptionPane.showOptionDialog(
     140                MainApplication.getMainFrame(),
     141                c,
     142                tr("Choose a color"),
     143                JOptionPane.OK_CANCEL_OPTION,
     144                JOptionPane.PLAIN_MESSAGE,
     145                null,
     146                options,
     147                options[0]
     148        );
     149        switch (answer) {
     150        case 0:
     151            tracks.stream().forEach(t -> t.setColor(c.getColor()));
     152            break;
     153        case 1:
     154            return;
     155        case 2:
     156            tracks.stream().forEach(t -> t.setColor(null));
     157            break;
     158        }
     159        table.repaint();
     160    }
     161
    125162    /**
    126163     * Builds an non-editable table whose 5th column will open a browser when double clicked.
    127164     * The table will fill its parent.
     
    138175                if (c instanceof JComponent) {
    139176                    JComponent jc = (JComponent) c;
    140177                    jc.setToolTipText(getValueAt(row, col).toString());
     178                    if (content.length > row
     179                            && content[row].length > 5
     180                            && content[row][5] instanceof GpxTrack) {
     181                        Color color = ((GpxTrack) content[row][5]).getColor();
     182                        if (color != null) {
     183                            double brightness = Math.sqrt(Math.pow(color.getRed(), 2) * .241
     184                                    + Math.pow(color.getGreen(), 2) * .691
     185                                    + Math.pow(color.getBlue(), 2) * .068);
     186                            if (brightness > 250) {
     187                                color = color.darker();
     188                            }
     189                            if (isRowSelected(row)) {
     190                                jc.setBackground(color);
     191                                if (brightness <= 130) {
     192                                    jc.setForeground(Color.WHITE);
     193                                } else {
     194                                    jc.setForeground(Color.BLACK);
     195                                }
     196                            } else {
     197                                if (brightness > 200) {
     198                                    color = color.darker(); //brightness >250 is darkened twice on purpose
     199                                }
     200                                jc.setForeground(color);
     201                                jc.setBackground(Color.WHITE);
     202                            }
     203                        }
     204                    }
    141205                }
    142206                return c;
    143207            }
     
    144208
    145209            @Override
    146210            public boolean isCellEditable(int rowIndex, int colIndex) {
    147                 return false;
     211                return colIndex <= 1;
    148212            }
     213
     214            @Override
     215            public void tableChanged(TableModelEvent e) {
     216                super.tableChanged(e);
     217                int col = e.getColumn();
     218                int row = e.getFirstRow();
     219                if (row >= 0 && row < content.length && col >= 0 && col <= 1) {
     220                    Object t = content[row][5];
     221                    String val = (String) getValueAt(row, col);
     222                    if (t != null && t instanceof GpxTrack) {
     223                        GpxTrack trk = (GpxTrack) t;
     224                        if (col == 0) {
     225                            trk.put("name", val);
     226                        } else {
     227                            trk.put("desc", val);
     228                        }
     229                    } else {
     230                        throw new InvalidArgumentException();
     231                    }
     232                }
     233            }
    149234        };
    150235        // define how to sort row
    151236        TableRowSorter<DefaultTableModel> rowSorter = new TableRowSorter<>();
     
    249334
    250335        msg.add(new JLabel(tr("<html>Select all tracks that you want to be displayed. " +
    251336                "You can drag select a range of tracks or use CTRL+Click to select specific ones. " +
    252                 "The map is updated live in the background. Open the URLs by double clicking them.</html>")),
     337                "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>")),
    253338                GBC.eop().fill(GBC.HORIZONTAL));
    254339        // build table
    255340        final boolean[] trackVisibilityBackup = layer.trackVisibility.clone();
    256         table = buildTable(buildTableContents());
     341        Object[][] content = buildTableContents();
     342        table = buildTable(content);
    257343        selectVisibleTracksInTable();
    258344        listenToSelectionChanges();
    259345        // make the table scrollable
     
    262348
    263349        int v = 1;
    264350        // build dialog
    265         ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), tr("Set track visibility for {0}", layer.getName()),
    266                 tr("Show all"), tr("Show selected only"), tr("Cancel"));
    267         ed.setButtonIcons("eye", "dialogs/filter", "cancel");
     351        ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(),
     352                tr("Set track visibility for {0}", layer.getName()),
     353                tr("Set color for selected tracks..."), tr("Show all"), tr("Show selected only"), tr("Cancel")) {
     354            @Override
     355            protected void buttonAction(int buttonIndex, ActionEvent evt) {
     356                if (buttonIndex == 0) {
     357                    List<GpxTrack> trks = new ArrayList<>();
     358                    for (int i : table.getSelectedRows()) {
     359                        Object trk = content[i][5];
     360                        if (trk != null && trk instanceof GpxTrack) {
     361                            trks.add((GpxTrack) trk);
     362                        }
     363                    }
     364                    showColorDialog(trks);
     365                } else {
     366                    super.buttonAction(buttonIndex, evt);
     367                }
     368            }
     369        };
     370        ed.setButtonIcons("colorchooser", "eye", "dialogs/filter", "cancel");
    268371        ed.setContent(msg, false);
    269372        ed.setDefaultButton(2);
    270373        ed.setCancelButton(3);
     
    275378        dateFilter.saveInPrefs();
    276379        v = ed.getValue();
    277380        // cancel for unknown buttons and copy back original settings
    278         if (v != 1 && v != 2) {
     381        if (v != 2 && v != 3) {
    279382            layer.trackVisibility = Arrays.copyOf(trackVisibilityBackup, layer.trackVisibility.length);
    280383            MainApplication.getMap().repaint();
    281384            return;
    282385        }
    283         // set visibility (1 = show all, 2 = filter). If no tracks are selected
     386        // set visibility (2 = show all, 3 = filter). If no tracks are selected
    284387        // set all of them visible and...
    285388        ListSelectionModel s = table.getSelectionModel();
    286         final boolean all = v == 1 || s.isSelectionEmpty();
     389        final boolean all = v == 2 || s.isSelectionEmpty();
    287390        for (int i = 0; i < layer.trackVisibility.length; i++) {
    288391            layer.trackVisibility[table.convertRowIndexToModel(i)] = all || s.isSelectedIndex(i);
    289392        }
     
    290393        // layer has been changed
    291394        layer.invalidate();
    292395        // ...sync with layer visibility instead to avoid having two ways to hide everything
    293         layer.setVisible(v == 1 || !s.isSelectionEmpty());
     396        layer.setVisible(v == 2 || !s.isSelectionEmpty());
    294397    }
    295398}
  • src/org/openstreetmap/josm/gui/layer/gpx/ConvertFromGpxLayerAction.java

     
    77import java.awt.event.ActionEvent;
    88import java.awt.event.ActionListener;
    99import java.util.ArrayList;
    10 import java.util.Collection;
    1110import java.util.Date;
    1211import java.util.List;
     12import java.util.Map;
    1313import java.util.Map.Entry;
    1414
    1515import javax.swing.BorderFactory;
     
    1919import javax.swing.JPanel;
    2020import javax.swing.JRadioButton;
    2121
     22import org.openstreetmap.josm.data.gpx.Extensions;
    2223import org.openstreetmap.josm.data.gpx.GpxConstants;
    2324import org.openstreetmap.josm.data.gpx.GpxTrack;
    2425import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
     
    2526import org.openstreetmap.josm.data.gpx.WayPoint;
    2627import org.openstreetmap.josm.data.osm.DataSet;
    2728import org.openstreetmap.josm.data.osm.Node;
     29import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2830import org.openstreetmap.josm.data.osm.Way;
    2931import org.openstreetmap.josm.gui.ExtendedDialog;
    3032import org.openstreetmap.josm.gui.MainApplication;
     
    6466                List<Node> nodes = new ArrayList<>();
    6567                for (WayPoint p : segment.getWayPoints()) {
    6668                    Node n = new Node(p.getCoor());
    67                     for (Entry<String, Object> entry : p.attr.entrySet()) {
    68                         String key = entry.getKey();
    69                         Object obj = p.get(key);
    70                         if (check && !keys.contains(key) && (obj instanceof String || obj instanceof Date)) {
    71                             keys.add(key);
    72                         }
    73                         if (obj instanceof String) {
    74                             String str = (String) obj;
    75                             if (!none) {
    76                                 // only convert when required
    77                                 n.put(key, str);
    78                             }
    79                         } else if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) {
    80                             // timestamps should always be converted
    81                             Date date = (Date) obj;
    82                             if (!none) { //... but the tag will only be set when required
    83                                 n.put(key, DateUtils.fromDate(date));
    84                             }
    85                             n.setTimestamp(date);
    86                         }
    87                     }
     69                    addAttributes(p.attr, n, keys, check, none);
    8870                    ds.addPrimitive(n);
    8971                    nodes.add(n);
    9072                }
    9173                Way w = new Way();
    9274                w.setNodes(nodes);
     75                addAttributes(trk.getAttributes(), w, keys, check, none);
    9376                ds.addPrimitive(w);
    9477            }
    9578        }
     
    125108        return ds;
    126109    }
    127110
     111    private static void addAttributes(Map<String, Object> attr, OsmPrimitive n, List<String> keys, boolean check, boolean none) {
     112        for (Entry<String, Object> entry : attr.entrySet()) {
     113            String key = entry.getKey();
     114            Object obj = entry.getValue();
     115            if (!none) {
     116                // only convert when required
     117                if (check && !keys.contains(key) && (obj instanceof String || obj instanceof Date)) {
     118                    keys.add(key);
     119                }
     120                if (obj instanceof String) {
     121                    n.put(key, (String) obj);
     122                } else if (obj instanceof Extensions) {
     123                    Extensions ext = (Extensions) obj;
     124                    String pre = ext.getPrefix();
     125                    for (Entry<String, String> e : ext.entrySet()) {
     126                        String k = pre + e.getKey();
     127                        if (check && !keys.contains(k)) {
     128                            keys.add(k);
     129                        }
     130                        n.put(k, e.getValue());
     131                    }
     132                }
     133            }
     134            if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) {
     135                // timestamps should always be converted
     136                Date date = (Date) obj;
     137                if (!none) { //... but the tag will only be set when required
     138                    n.put(key, DateUtils.fromDate(date));
     139                }
     140                n.setTimestamp(date);
     141            }
     142        }
     143    }
     144
    128145    /**
    129146     * Filters the tags of the given {@link DataSet}
    130147     * @param ds The {@link DataSet}
     
    133150     * @since 14103
    134151     */
    135152    public DataSet filterDataSet(DataSet ds, List<String> listPos) {
    136         Collection<Node> nodes = ds.getNodes();
    137         for (Node n : nodes) {
    138             for (String key : n.keySet()) {
     153        for (OsmPrimitive p : ds.getPrimitives(p -> p instanceof Node || p instanceof Way)) {
     154            for (String key : p.keySet()) {
    139155                if (listPos == null || !listPos.contains(key)) {
    140                     n.put(key, null);
     156                    p.put(key, null);
    141157                }
    142158            }
    143159        }
  • src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java

     
    6868     * The color that is used for drawing GPX points.
    6969     * @since 10824
    7070     */
    71     public static final NamedColorProperty DEFAULT_COLOR = new NamedColorProperty(marktr("gps point"), Color.magenta);
     71    public static final NamedColorProperty DEFAULT_COLOR_PROPERTY = new NamedColorProperty(marktr("gps point"), Color.magenta);
    7272
    7373    private final GpxData data;
    7474    private final GpxLayer layer;
     
    258258    }
    259259
    260260    /**
    261      * Get the default color for gps tracks for specified layer
    262      * @param layerName name of the GpxLayer
    263      * @param ignoreCustom do not use preferences
    264      * @return the color or null if the color is not constant
    265      */
    266     public Color getColor(String layerName, boolean ignoreCustom) {
    267         if (ignoreCustom || getColorMode(layerName) == ColorMode.NONE) {
    268             return DEFAULT_COLOR.getChildColor(
    269                     NamedColorProperty.COLOR_CATEGORY_LAYER,
    270                     layerName,
    271                     DEFAULT_COLOR.getName()).get();
    272         } else {
    273             return null;
    274         }
    275     }
    276 
    277     /**
    278261     * Read coloring mode for specified layer from preferences
    279262     * @param layerName name of the GpxLayer
    280263     * @return coloring mode
     
    289272        return ColorMode.NONE;
    290273    }
    291274
    292     /** Reads generic color from preferences (usually gray)
    293      * @return the color
    294      **/
    295     public static Color getGenericColor() {
    296         return DEFAULT_COLOR.get();
    297     }
    298 
    299275    /**
    300276     * Read all drawing-related settings from preferences
    301277     * @param layerName layer name used to access its specific preferences
     
    337313
    338314        // shrink to range
    339315        heatMapDrawGain = Utils.clamp(heatMapDrawGain, -10, 10);
    340 
    341         neutralColor = getColor(layerName, true);
     316        neutralColor = DEFAULT_COLOR_PROPERTY.get();
    342317        velocityScale.setNoDataColor(neutralColor);
    343318        dateScale.setNoDataColor(neutralColor);
    344319        hdopScale.setNoDataColor(neutralColor);
     
    577552            }
    578553            for (WayPoint trkPnt : segment) {
    579554                LatLon c = trkPnt.getCoor();
    580                 trkPnt.customColoring = neutralColor;
     555                trkPnt.customColoring = segment.getColor();
    581556                if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
    582557                    continue;
    583558                }
     
    624599                    }
    625600                } else { // make sure we reset outdated data
    626601                    trkPnt.drawLine = false;
    627                     color = neutralColor;
     602                    color = segment.getColor();
    628603                }
    629604                if (color != null) {
    630605                    trkPnt.customColoring = color;
  • src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java

     
    103103        GpxLink link = new GpxLink(audioUrl.toString());
    104104        link.type = "audio";
    105105        wpt.put(GpxConstants.META_LINKS, Collections.singleton(link));
    106         wpt.addExtension("offset", Double.toString(offset));
    107         wpt.addExtension("sync-offset", Double.toString(syncOffset));
     106        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "offset", Double.toString(offset));
     107        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "sync-offset", Double.toString(syncOffset));
    108108        return wpt;
    109109    }
    110110}
  • src/org/openstreetmap/josm/gui/layer/markerlayer/DefaultMarkerProducers.java

     
    4545            return Collections.singleton(marker);
    4646        } else if (Utils.hasExtension(urlStr, "wav", "mp3", "aac", "aif", "aiff")) {
    4747            final AudioMarker audioMarker = new AudioMarker(wpt.getCoor(), wpt, url, parentLayer, time, offset);
    48             Extensions exts = (Extensions) wpt.get(GpxConstants.META_EXTENSIONS);
    49             if (exts != null && exts.containsKey("offset")) {
     48            Extensions josmExts = (Extensions) wpt.get(GpxConstants.EXTENSIONS_JOSM);
     49            if (josmExts != null && josmExts.containsKey("offset")) {
    5050                try {
    51                     audioMarker.syncOffset = Double.parseDouble(exts.get("sync-offset"));
     51                    audioMarker.syncOffset = Double.parseDouble(josmExts.get("sync-offset"));
    5252                } catch (NumberFormatException nfe) {
    5353                    Logging.warn(nfe);
    5454                }
  • src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java

     
    247247        WayPoint wpt = new WayPoint(getCoor());
    248248        wpt.setTimeInMillis((long) (time * 1000));
    249249        if (text != null) {
    250             wpt.addExtension("text", text);
     250            wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "text", text);
    251251        } else if (dataProvider != null) {
    252252            for (String key : dataProvider.getTemplateKeys()) {
    253253                Object value = dataProvider.getTemplateValue(key, false);
  • src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java

     
    7777    public AudioMarker syncAudioMarker;
    7878
    7979    private static final Color DEFAULT_COLOR = Color.magenta;
    80     private static final NamedColorProperty COLOR_PROPERTY = new NamedColorProperty(marktr("gps marker"), DEFAULT_COLOR);
     80    /**
     81     * The color that is used for drawing markers.
     82     */
     83    public static final NamedColorProperty DEFAULT_COLOR_PROPERTY = new NamedColorProperty(marktr("gps marker"), DEFAULT_COLOR);
    8184
    8285    /**
    8386     * Constructs a new {@code MarkerLayer}.
     
    123126            // audio file) calculate the offset relative to the first marker of
    124127            // that group. This way the user can jump to the corresponding
    125128            // playback positions in a long audio track.
    126             Extensions exts = (Extensions) wpt.get(GpxConstants.META_EXTENSIONS);
    127             if (exts != null && exts.containsKey("offset")) {
     129            Extensions josmExts = (Extensions) wpt.get(GpxConstants.EXTENSIONS_JOSM);
     130            if (josmExts != null && josmExts.containsKey("offset")) {
    128131                try {
    129                     offset = Double.valueOf(exts.get("offset"));
     132                    offset = Double.valueOf(josmExts.get("offset"));
    130133                } catch (NumberFormatException nfe) {
    131134                    Logging.warn(nfe);
    132135                }
     
    173176    }
    174177
    175178    @Override
    176     protected NamedColorProperty getBaseColorProperty() {
    177         return COLOR_PROPERTY;
    178     }
    179 
    180     /* for preferences */
    181     public static Color getGenericColor() {
    182         return COLOR_PROPERTY.get();
    183     }
    184 
    185     @Override
    186179    public void paint(Graphics2D g, MapView mv, Bounds box) {
    187180        boolean showTextOrIcon = isTextOrIconShown();
    188         g.setColor(getColorProperty().get());
    189 
     181        g.setColor(DEFAULT_COLOR_PROPERTY.get());
    190182        if (mousePressed) {
    191183            boolean mousePressedTmp = mousePressed;
    192184            Point mousePos = mv.getMousePosition(); // Get mouse position only when necessary (it's the slowest part of marker layer painting)
  • src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java

     
    390390        PaintColors.values();
    391391        ConflictColors.getColors();
    392392        Severity.getColors();
    393         MarkerLayer.getGenericColor();
    394         GpxDrawHelper.getGenericColor();
     393        MarkerLayer.DEFAULT_COLOR_PROPERTY.get();
     394        GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get();
    395395        OsmDataLayer.getOutsideColor();
    396396        MapScaler.getColor();
    397397        MapStatus.getColors();
  • src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java

     
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55import static org.openstreetmap.josm.tools.I18n.trc;
    66
    7 import java.awt.Color;
    87import java.awt.Component;
    98import java.awt.Dimension;
    109import java.awt.GridBagLayout;
     
    2423
    2524import org.openstreetmap.josm.actions.ExpertToggleAction;
    2625import org.openstreetmap.josm.data.PreferencesUtils;
    27 import org.openstreetmap.josm.data.preferences.NamedColorProperty;
    2826import org.openstreetmap.josm.gui.MainApplication;
    2927import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper;
    3028import org.openstreetmap.josm.gui.layer.markerlayer.Marker;
     
    326324            if (null != dim) {
    327325                // get image size of environment
    328326                final int iconSize = (int) dim.getHeight();
    329                 final Color color;
    330                 // ask the GPX draw for the correct color of that layer ( if there is one )
    331                 if (null != layerName) {
    332                     color = GpxDrawHelper.DEFAULT_COLOR.getChildColor(
    333                             NamedColorProperty.COLOR_CATEGORY_LAYER, layerName, GpxDrawHelper.DEFAULT_COLOR.getName()).get();
    334                 } else {
    335                     color = GpxDrawHelper.DEFAULT_COLOR.getDefaultValue();
    336                 }
    337                 colorTypeHeatIconLabel.setIcon(GpxDrawHelper.getColorMapImageIcon(color, colorTypeHeatMapTune.getSelectedIndex(), iconSize));
     327                colorTypeHeatIconLabel.setIcon(GpxDrawHelper.getColorMapImageIcon(
     328                        GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get(),
     329                        colorTypeHeatMapTune.getSelectedIndex(),
     330                        iconSize));
    338331            }
    339332        });
    340333
  • src/org/openstreetmap/josm/io/GpxReader.java

     
    7676        private State currentState = State.INIT;
    7777
    7878        private GpxLink currentLink;
    79         private Extensions currentExtensions;
     79        private Map<String, String> currentExtensionMap = new HashMap<>();
    8080        private Stack<State> states;
    8181        private final Stack<String> elements = new Stack<>();
    8282
     
    159159                case "extensions":
    160160                    states.push(currentState);
    161161                    currentState = State.EXT;
    162                     currentExtensions = new Extensions();
    163162                    break;
    164163                case "gpx":
    165164                    if (atts.getValue("creator") != null && atts.getValue("creator").startsWith("Nokia Sports Tracker")) {
     
    178177                case "extensions":
    179178                    states.push(currentState);
    180179                    currentState = State.EXT;
    181                     currentExtensions = new Extensions();
    182180                    break;
    183181                case "copyright":
    184182                    states.push(currentState);
     
    228226                case "extensions":
    229227                    states.push(currentState);
    230228                    currentState = State.EXT;
    231                     currentExtensions = new Extensions();
    232229                    break;
    233230                default: // Do nothing
    234231                }
     
    250247                case "extensions":
    251248                    states.push(currentState);
    252249                    currentState = State.EXT;
    253                     currentExtensions = new Extensions();
    254250                    break;
    255251                default: // Do nothing
    256252                }
     
    270266                case "extensions":
    271267                    states.push(currentState);
    272268                    currentState = State.EXT;
    273                     currentExtensions = new Extensions();
    274269                    break;
    275270                default: // Do nothing
    276271                }
     
    349344                    if ((currentState == State.METADATA && "metadata".equals(localName)) ||
    350345                        (currentState == State.GPX && "gpx".equals(localName))) {
    351346                        convertUrlToLink(data.attr);
    352                         if (currentExtensions != null && !currentExtensions.isEmpty()) {
    353                             data.put(META_EXTENSIONS, currentExtensions);
    354                         }
     347                        data.addExtensions(currentExtensionMap);
    355348                        currentState = states.pop();
    356349                    }
    357350                    break;
     
    464457                case "wpt":
    465458                    currentState = states.pop();
    466459                    convertUrlToLink(currentWayPoint.attr);
    467                     if (currentExtensions != null && !currentExtensions.isEmpty()) {
    468                         currentWayPoint.put(META_EXTENSIONS, currentExtensions);
    469                     }
     460                    currentWayPoint.addExtensions(currentExtensionMap);
    470461                    data.waypoints.add(currentWayPoint);
     462                    currentExtensionMap = new HashMap<>();
    471463                    break;
    472464                default: // Do nothing
    473465                }
     
    483475                case "trk":
    484476                    currentState = states.pop();
    485477                    convertUrlToLink(currentTrackAttr);
    486                     data.addTrack(new ImmutableGpxTrack(currentTrack, currentTrackAttr));
     478                    ImmutableGpxTrack trk = new ImmutableGpxTrack(currentTrack, currentTrackAttr, currentExtensionMap);
     479                    data.addTrack(trk);
     480                    currentExtensionMap = new HashMap<>();
    487481                    break;
    488482                case "name":
    489483                case "cmt":
     
    499493                }
    500494                break;
    501495            case EXT:
     496                String acc;
    502497                if ("extensions".equals(localName)) {
    503498                    currentState = states.pop();
    504                 } else if (JOSM_EXTENSIONS_NAMESPACE_URI.equals(namespaceURI)) {
    505                     // only interested in extensions written by JOSM
    506                     currentExtensions.put(localName, accumulator.toString());
     499                } else if ((acc = accumulator.toString().trim()).length() > 0) {
     500                    currentExtensionMap.put(qName, acc);
    507501                }
    508502                break;
    509503            default:
     
    519513                default: // Do nothing
    520514                }
    521515            }
     516            accumulator.setLength(0);
    522517        }
    523518
    524519        @Override
     
    525520        public void endDocument() throws SAXException {
    526521            if (!states.empty())
    527522                throw new SAXException(tr("Parse error: invalid document structure for GPX document."));
    528             Extensions metaExt = (Extensions) data.get(META_EXTENSIONS);
    529             if (metaExt != null && "true".equals(metaExt.get("from-server"))) {
     523            Extensions josmMetaExt = (Extensions) data.get(EXTENSIONS_JOSM);
     524            if (josmMetaExt != null && "true".equals(josmMetaExt.get("from-server"))) {
    530525                data.fromServer = true;
    531526            }
    532527            gpxData = data;
  • src/org/openstreetmap/josm/io/GpxWriter.java

     
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.awt.Color;
    67import java.io.BufferedWriter;
    78import java.io.OutputStream;
    89import java.io.OutputStreamWriter;
    910import java.io.PrintWriter;
    1011import java.nio.charset.StandardCharsets;
     12import java.util.AbstractMap.SimpleEntry;
     13import java.util.ArrayList;
    1114import java.util.Collection;
    1215import java.util.Date;
     16import java.util.HashMap;
    1317import java.util.List;
    1418import java.util.Map;
    1519import java.util.Map.Entry;
     
    5963    private static final int ROUTE_POINT = 1;
    6064    private static final int TRACK_POINT = 2;
    6165
     66    private HashMap<String, Entry<String, String>> extlinks = new HashMap<>();
     67
    6268    /**
    6369     * Writes the given GPX data.
    6470     * @param data The data to write
    6571     */
    6672    public void write(GpxData data) {
     73        write(data, EXTENSIONS_DRAWING);
     74    }
     75
     76    /**
     77     * Writes the given GPX data.
     78     *
     79     * @param data The data to write
     80     * @param colorFormat determines if colors are saved and which extension is to be used, can be
     81     * {@link GpxConstants#EXTENSIONS_GARMIN}, {@link GpxConstants#EXTENSIONS_DRAWING} or <code>null</code>
     82     */
     83    public void write(GpxData data, String colorFormat) {
    6784        this.data = data;
    6885        // We write JOSM specific meta information into gpx 'extensions' elements.
    6986        // In particular it is noted whether the gpx data is from the OSM server
     
    7188        // and some extra synchronization info for export of AudioMarkers.
    7289        // It is checked in advance, if any extensions are used, so we know whether
    7390        // a namespace declaration is necessary.
    74         boolean hasExtensions = data.fromServer;
    75         if (!hasExtensions) {
     91
     92        boolean hasJosmExtension = data.fromServer,
     93                hasGpxxExtension = false,
     94                hasGpxdExtension = false,
     95                searchGpxx = EXTENSIONS_GARMIN.equals(colorFormat),
     96                searchGpxd = EXTENSIONS_DRAWING.equals(colorFormat);
     97
     98        if (!hasJosmExtension) {
    7699            for (WayPoint wpt : data.waypoints) {
    77                 Extensions extensions = (Extensions) wpt.get(META_EXTENSIONS);
     100                Extensions extensions = (Extensions) wpt.get(EXTENSIONS_JOSM);
    78101                if (extensions != null && !extensions.isEmpty()) {
    79                     hasExtensions = true;
     102                    hasJosmExtension = true;
    80103                    break;
    81104                }
    82105            }
    83106        }
    84107
     108        if (searchGpxx || searchGpxd) {
     109
     110            HashMap<Color, String> closestColorCache = new HashMap<>();
     111
     112            for (GpxTrack trk : data.getTracks()) {
     113                Extensions gpxx = (Extensions) trk.get(EXTENSIONS_GARMIN),
     114                           gpxd = (Extensions) trk.get(EXTENSIONS_DRAWING);
     115                String gpxxC = null, gpxdC = null;
     116
     117                if (gpxd != null) {
     118                    gpxdC = gpxd.get("color");
     119                }
     120                if (gpxx != null) {
     121                    gpxxC = gpxx.get("displaycolor");
     122                }
     123
     124                if (searchGpxd && gpxdC == null && gpxx != null && gpxxC != null) {
     125                    //Convert GPXX to GPXD
     126                    Color c = GARMIN_COLORS.get(gpxxC);
     127                    if (c != null) {
     128                        //Remove GPXX
     129                        trk.removeExtensionKey(EXTENSIONS_GARMIN, "displaycolor");
     130                        //Put GPXD
     131                        trk.setColor(c);
     132                        hasGpxdExtension = true;
     133                    } else {
     134                        Logging.warn("Could not read garmin color: " + gpxxC);
     135                    }
     136                } else if (searchGpxx && gpxxC == null && gpxd != null && gpxdC != null) {
     137                    //Convert GPXD to GPXX
     138                    try {
     139                        Color c = Color.decode(gpxdC);
     140                        //Remove GPXD
     141                        trk.removeExtensionKey(EXTENSIONS_DRAWING, "color");
     142                        //Put GPXX
     143                        String colorString = null;
     144                        double closestDiff = -1;
     145
     146                        if (closestColorCache.containsKey(c)) {
     147                            colorString = closestColorCache.get(c);
     148                        } else {
     149                            //find closest garmin color
     150                            for (Entry<String, Color> e : GARMIN_COLORS.entrySet()) {
     151                                double diff = colorDist(e.getValue(), c);
     152                                if (closestDiff < 0 || diff < closestDiff) {
     153                                    colorString = e.getKey();
     154                                    closestDiff = diff;
     155                                    if (closestDiff == 0) break;
     156                                }
     157                            }
     158                            closestColorCache.put(c, colorString);
     159                        }
     160                        trk.addExtensionKey(EXTENSIONS_GARMIN, "DisplayColor", colorString);
     161                        hasGpxxExtension = true;
     162                    } catch (NumberFormatException ex) {
     163                        Logging.warn("Could not read gpxd color: " + gpxdC);
     164                    }
     165                }
     166                //Must be checked again because of conversion above
     167                if (!hasGpxdExtension && gpxd != null && !gpxd.isEmpty()) {
     168                    hasGpxdExtension = true;
     169                }
     170                if (!hasGpxxExtension && gpxx != null && !gpxx.isEmpty()) {
     171                    hasGpxxExtension = true;
     172                }
     173            }
     174        }
     175
    85176        out.println("<?xml version='1.0' encoding='UTF-8'?>");
    86177        out.println("<gpx version=\"1.1\" creator=\"JOSM GPX export\" xmlns=\"http://www.topografix.com/GPX/1/1\"");
    87         out.println((hasExtensions ? String.format("    xmlns:josm=\"%s\"%n", JOSM_EXTENSIONS_NAMESPACE_URI) : "") +
    88                     "    xmlns:xsi=\""+XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI+"\"");
    89         out.println("    xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">");
     178
     179        String schemaLocations = "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd";
     180
     181        if (hasJosmExtension) {
     182            extlinks.put(GpxConstants.EXTENSIONS_JOSM, new SimpleEntry<>(GpxConstants.XML_URI_EXTENSIONS_JOSM, GpxConstants.XML_XSD_EXTENSIONS_JOSM));
     183        }
     184        if (hasGpxdExtension) {
     185            extlinks.put(GpxConstants.EXTENSIONS_DRAWING, new SimpleEntry<>(GpxConstants.XML_URI_EXTENSIONS_DRAWING, GpxConstants.XML_XSD_EXTENSIONS_DRAWING));
     186        }
     187        if (hasGpxxExtension) {
     188            extlinks.put(GpxConstants.EXTENSIONS_GARMIN, new SimpleEntry<>(GpxConstants.XML_URI_EXTENSIONS_GARMIN, GpxConstants.XML_XSD_EXTENSIONS_GARMIN));
     189        }
     190
     191        for (Entry<String, Entry<String, String>> e : extlinks.entrySet()) {
     192            String k = e.getKey();
     193            Entry<String, String> v = e.getValue();
     194            if (!k.startsWith(EXTENSIONS_PREFIX)) {
     195                throw new Extensions.InvalidExtensionException();
     196            }
     197            out.println(String.format("    xmlns:%s=\"%s\"", k.substring(k.lastIndexOf(".") + 1), v.getKey()));
     198            schemaLocations += " " + v.getKey() + " " + v.getValue();
     199        }
     200
     201        out.println("    xmlns:xsi=\""+XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI+"\"");
     202        out.println(String.format("    xsi:schemaLocation=\"%s\">", schemaLocations));
    90203        indent = "  ";
    91204        writeMetaData();
    92205        writeWayPoints();
     
    97210    }
    98211
    99212    private void writeAttr(IWithAttributes obj, List<String> keys) {
     213        List<Extensions> allExtensions = new ArrayList<>();
    100214        for (String key : keys) {
    101215            if (META_LINKS.equals(key)) {
    102216                Collection<GpxLink> lValue = obj.<GpxLink>getCollection(key);
     
    105219                        gpxLink(link);
    106220                    }
    107221                }
    108             } else if (META_EXTENSIONS.equals(key)) {
    109                 Extensions extensions = (Extensions) obj.get(key);
    110                 if (extensions != null) {
    111                     gpxExtensions(extensions);
     222            } else if (key != null && key.startsWith(EXTENSIONS_PREFIX)) {
     223                Extensions ex = (Extensions) obj.get(key);
     224                if (ex != null && !ex.isEmpty()) {
     225                    //ex.setType(key);
     226                    allExtensions.add(ex);
    112227                }
    113228            } else {
    114229                String value = obj.getString(key);
     
    126241                }
    127242            }
    128243        }
     244        gpxExtensions(allExtensions);
    129245    }
    130246
     247    double colorDist(Color c1, Color c2) {
     248        // Simple Euclidean distance between two colors
     249        return Math.sqrt(Math.pow(c1.getRed() - c2.getRed(), 2)
     250                + Math.pow(c1.getGreen() - c2.getGreen(), 2)
     251                + Math.pow(c1.getBlue() - c2.getBlue(), 2));
     252    }
     253
     254
    131255    private void writeMetaData() {
    132256        Map<String, Object> attr = data.attr;
    133257        openln("metadata");
     
    318442        }
    319443    }
    320444
    321     private void gpxExtensions(Extensions extensions) {
    322         if (extensions != null && !extensions.isEmpty()) {
     445    private void gpxExtensions(List<Extensions> allExtensions) {
     446        if (!allExtensions.isEmpty()) {
    323447            openln("extensions");
    324             for (Entry<String, String> e : extensions.entrySet()) {
    325                 simpleTag("josm:" + e.getKey(), e.getValue());
     448            for (Extensions extensions : allExtensions) {
     449                if (extlinks.containsKey(extensions.getType())) {
     450                    //making sure links were actually added / filter if no colors are to be saved at all.
     451                    //TODO: probably makes more sense to do the conversion here instead of in the beginning
     452                    //note that the colors will actually change during the conversion to GPXX
     453                    boolean garmin = EXTENSIONS_GARMIN.equals(extensions.getType());
     454                    if (garmin) { //allow nested Garmin TrackExtension. Not ideal but does the job.
     455                        openln("gpxx:TrackExtension");
     456                    }
     457                    for (Entry<String, String> e : extensions.entrySet()) {
     458                        simpleTag(extensions.getPrefix() + e.getKey(), e.getValue());
     459                    }
     460                    if (garmin) {
     461                        closeln("gpxx:TrackExtension");
     462                    }
     463                }
    326464            }
    327465            closeln("extensions");
    328466        }
  • test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java

     
    473473    public void testEqualsContract() {
    474474        TestUtils.assumeWorkingEqualsVerifier();
    475475        EqualsVerifier.forClass(GpxData.class).usingGetClass()
    476             .withIgnoredFields("attr", "creator", "fromServer", "storageFile", "listeners", "tracks", "routes", "waypoints", "proxy", "segSpans")
     476            .withIgnoredFields("attr", "creator", "fromServer", "storageFile", "listeners", "tracks", "routes", "waypoints", "proxy", "segSpans", "modified")
    477477            .withPrefabValues(WayPoint.class, new WayPoint(LatLon.NORTH_POLE), new WayPoint(LatLon.SOUTH_POLE))
    478478            .withPrefabValues(ListenerList.class, ListenerList.create(), ListenerList.create())
    479479            .verify();
  • test/unit/org/openstreetmap/josm/data/gpx/ImmutableGpxTrackTest.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
     4import static org.junit.Assert.assertEquals;
     5import static org.junit.Assert.assertNull;
     6
     7import java.awt.Color;
     8import java.util.ArrayList;
     9import java.util.HashMap;
     10import java.util.Map;
     11
    412import org.junit.Rule;
    513import org.junit.Test;
    614import org.openstreetmap.josm.TestUtils;
    715import org.openstreetmap.josm.testutils.JOSMTestRules;
     16import org.openstreetmap.josm.tools.ListenerList;
    817
    918import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    1019import nl.jqno.equalsverifier.EqualsVerifier;
     
    2332    public JOSMTestRules test = new JOSMTestRules();
    2433
    2534    /**
     35     * Tests weather the track can read and write colors.
     36     */
     37    @Test
     38    public void testColors() {
     39        GpxTrack trk = new ImmutableGpxTrack(new ArrayList<GpxTrackSegment>(), new HashMap<>());
     40        trk.addExtensions(Map.of("gpxd:color", "#FF0000"));
     41        assertEquals(trk.getColor(), Color.RED);
     42        trk.addExtensionKey(GpxConstants.EXTENSIONS_DRAWING, "color", "#00FF00");
     43        assertEquals(trk.getColor(), Color.GREEN);
     44        trk.removeExtensionKey(GpxConstants.EXTENSIONS_DRAWING, "color");
     45        assertNull(trk.getColor());
     46        trk.addExtensionKey(GpxConstants.EXTENSIONS_GARMIN, "DisplayColor", "Blue");
     47        assertEquals(trk.getColor(), Color.BLUE);
     48        trk.setColor(null);
     49        assertNull(trk.getColor());
     50        trk.addExtensions(Map.of("gpxx:DisplayColor", "Cyan"));
     51        assertEquals(trk.getColor(), Color.CYAN);
     52        trk.setColor(Color.YELLOW);
     53        assertEquals(trk.getColor(), Color.YELLOW);
     54    }
     55
     56    /**
    2657     * Unit test of methods {@link ImmutableGpxTrack#equals} and {@link ImmutableGpxTrack#hashCode}.
    2758     */
    2859    @Test
     
    3061        TestUtils.assumeWorkingEqualsVerifier();
    3162        EqualsVerifier.forClass(ImmutableGpxTrack.class).usingGetClass()
    3263            .suppress(Warning.NONFINAL_FIELDS)
    33             .withIgnoredFields("bounds", "length")
     64            .withPrefabValues(ListenerList.class, ListenerList.create(), ListenerList.create())
     65            .withIgnoredFields("bounds", "length", "colorCache", "listeners")
    3466            .verify();
    3567    }
    3668}
  • test/unit/org/openstreetmap/josm/gui/layer/GpxLayerTest.java

     
    33
    44import static org.junit.Assert.assertEquals;
    55import static org.junit.Assert.assertFalse;
     6import static org.junit.Assert.assertNull;
    67import static org.junit.Assert.assertTrue;
    78
    89import java.awt.Color;
     
    1011import java.util.ArrayList;
    1112import java.util.Collection;
    1213import java.util.HashMap;
     14import java.util.Map;
    1315import java.util.TimeZone;
    1416
    1517import javax.swing.JScrollPane;
     
    1820import org.junit.Test;
    1921import org.openstreetmap.josm.TestUtils;
    2022import org.openstreetmap.josm.data.gpx.GpxData;
     23import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
    2124import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
    2225import org.openstreetmap.josm.data.gpx.WayPoint;
    2326import org.openstreetmap.josm.data.osm.DataSet;
     
    7376    @Test
    7477    public void testGpxLayer() throws Exception {
    7578        GpxLayer layer = new GpxLayer(new GpxData(), "foo", false);
     79        ImmutableGpxTrack trk = new ImmutableGpxTrack(new ArrayList<GpxTrackSegment>(), new HashMap<>());
     80        trk.addExtensions(Map.of("gpxd:color", "#FF0000"));
     81        layer.data.addTrack(trk);
     82
    7683        assertEquals("foo", layer.getName());
    7784        assertFalse(layer.isLocalFile());
    78         assertEquals(Color.MAGENTA, layer.getColorProperty().get());
    79         assertEquals("<html>0 tracks (0 segments), 0 routes, 0 waypoints<br>Length: < 0.01 m<br></html>", layer.getToolTipText());
     85        assertEquals(layer.getColor(), Color.RED);
     86        assertEquals("<html>1 track (0 segments), 0 routes, 0 waypoints<br>Length: < 0.01 m<br></html>", layer.getToolTipText());
    8087
    8188        GpxLayer layer2 = new GpxLayer(new GpxData(), "bar", true);
    8289        assertEquals("bar", layer2.getName());
    8390        assertTrue(layer2.isLocalFile());
    84         assertEquals(Color.MAGENTA, layer2.getColorProperty().get());
     91        assertNull(layer2.getColor());
    8592        assertEquals("<html>0 tracks (0 segments), 0 routes, 0 waypoints<br>Length: < 0.01 m<br></html>", layer2.getToolTipText());
    8693
    8794        assertTrue(layer.checkSaveConditions());
  • test/unit/org/openstreetmap/josm/gui/layer/LayerTest.java

     
    77import static org.junit.Assert.assertNull;
    88import static org.junit.Assert.assertTrue;
    99
    10 import java.awt.Color;
    1110import java.io.File;
    1211
    1312import org.junit.Before;
    1413import org.junit.Rule;
    1514import org.junit.Test;
    16 import org.openstreetmap.josm.data.preferences.AbstractProperty;
    17 import org.openstreetmap.josm.data.preferences.NamedColorProperty;
    1815import org.openstreetmap.josm.data.projection.ProjectionRegistry;
    1916import org.openstreetmap.josm.testutils.JOSMTestRules;
    2017
     
    4340    }
    4441
    4542    /**
    46      * Test {@link Layer#getColorProperty()}
    47      */
    48     @Test
    49     public void testGetColorProperty() {
    50         assertEquals(null, testLayer.getColorProperty());
    51 
    52         AbstractProperty<Color> color = new LayerManagerTest.TestLayer() {
    53             @Override
    54             protected NamedColorProperty getBaseColorProperty() {
    55                 return new NamedColorProperty("x", Color.BLACK);
    56             }
    57         }.getColorProperty();
    58 
    59         assertEquals(Color.BLACK, color.get());
    60         assertEquals(Color.BLACK, color.getDefaultValue());
    61         assertEquals("clr.layer.Test Layer.x", color.getKey());
    62     }
    63 
    64     /**
    6543     * Test of {@link Layer#isInfoResizable}
    6644     */
    6745    @Test
     
    9775        testLayer.setName("Test Layer2");
    9876        assertEquals("Test Layer2", testLayer.getName());
    9977
    100         testLayer = new LayerManagerTest.TestLayer() {
    101             @Override
    102             public AbstractProperty<Color> getColorProperty() {
    103                 return new NamedColorProperty("test", Color.RED);
    104             }
    105         };
     78        testLayer = new LayerManagerTest.TestLayer();
    10679
    10780        testLayer.setName("Test Layer2");
    10881        testLayer.setName(null);
  • test/unit/org/openstreetmap/josm/gui/layer/MarkerLayerTest.java

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.layer;
    3 
    4 import static org.junit.Assert.assertNotNull;
    5 import static org.junit.Assert.assertNull;
    6 
    7 import org.junit.Rule;
    8 import org.junit.Test;
    9 import org.openstreetmap.josm.data.gpx.GpxData;
    10 import org.openstreetmap.josm.data.osm.DataSet;
    11 import org.openstreetmap.josm.gui.MainApplication;
    12 import org.openstreetmap.josm.gui.MapFrame;
    13 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
    14 import org.openstreetmap.josm.testutils.JOSMTestRules;
    15 
    16 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    17 
    18 /**
    19  * Unit tests of {@link MarkerLayer} class.
    20  */
    21 public class MarkerLayerTest {
    22 
    23     /**
    24      * For creating layers
    25      */
    26     @Rule
    27     @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    28     public JOSMTestRules test = new JOSMTestRules().main().projection();
    29 
    30     /**
    31      * Unit test of {@code Main.map.mapView.playHeadMarker}.
    32      */
    33     @Test
    34     public void testPlayHeadMarker() {
    35         try {
    36             MainApplication.getLayerManager().addLayer(new OsmDataLayer(new DataSet(), "", null));
    37             MapFrame map = MainApplication.getMap();
    38             MarkerLayer layer = new MarkerLayer(new GpxData(), null, null, null);
    39             assertNull(map.mapView.playHeadMarker);
    40             MainApplication.getLayerManager().addLayer(layer);
    41             assertNotNull(map.mapView.playHeadMarker);
    42             MainApplication.getLayerManager().removeLayer(layer);
    43         } finally {
    44             if (MainApplication.isDisplayingMapView()) {
    45                 MainApplication.getMap().mapView.playHeadMarker = null;
    46             }
    47         }
    48     }
    49 }
  • test/unit/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarkerTest.java

    Property changes on: test/unit/org/openstreetmap/josm/gui/layer/MarkerLayerTest.java
    ___________________________________________________________________
    Deleted: svn:eol-style
    ## -1 +0,0 ##
    -native
    \ No newline at end of property
     
    4545        assertEquals("2", marker.getText());
    4646        WayPoint wpt = marker.convertToWayPoint();
    4747        assertEquals(LatLon.ZERO, wpt.getCoor());
    48         Extensions ext = (Extensions) wpt.get(GpxConstants.META_EXTENSIONS);
     48        Extensions ext = (Extensions) wpt.get(GpxConstants.EXTENSIONS_JOSM);
    4949        assertEquals("2.0", ext.get("offset"));
    5050    }
    5151}
  • test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java

     
    33
    44import static org.junit.Assert.assertEquals;
    55import static org.junit.Assert.assertNotNull;
     6import static org.junit.Assert.assertNull;
    67import static org.junit.Assert.assertTrue;
    78
    8 import java.awt.Color;
    99import java.util.Arrays;
    1010
    1111import org.junit.Before;
     
    1616import org.openstreetmap.josm.data.gpx.GpxData;
    1717import org.openstreetmap.josm.data.gpx.GpxLink;
    1818import org.openstreetmap.josm.data.gpx.WayPoint;
     19import org.openstreetmap.josm.data.osm.DataSet;
    1920import org.openstreetmap.josm.gui.MainApplication;
     21import org.openstreetmap.josm.gui.MapFrame;
     22import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2023import org.openstreetmap.josm.spi.preferences.Config;
    2124import org.openstreetmap.josm.testutils.JOSMTestRules;
    2225
     
    2831public class MarkerLayerTest {
    2932
    3033    /**
    31      * Setup tests
     34     * For creating layers
    3235     */
    3336    @Rule
    3437    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     
    4750     */
    4851    @Test
    4952    public void testMarkerLayer() {
    50         assertEquals(Color.magenta, MarkerLayer.getGenericColor());
     53        //assertEquals(Color.magenta, MarkerLayer.getGenericColor());
    5154        MarkerLayer layer = new MarkerLayer(new GpxData(), "foo", null, null);
    5255        MainApplication.getLayerManager().addLayer(layer);
    5356
    5457        assertEquals("foo", layer.getName());
    55         assertEquals(Color.magenta, layer.getColorProperty().get());
     58        assertNull(layer.getColor());
    5659        assertNotNull(layer.getIcon());
    5760        assertEquals("0 markers", layer.getToolTipText());
    5861        assertEquals("<html>foo consists of 0 markers</html>", layer.getInfoComponent());
     
    6164        GpxData gpx = new GpxData();
    6265        WayPoint wpt = new WayPoint(LatLon.ZERO);
    6366        wpt.attr.put(GpxConstants.META_LINKS, Arrays.asList(new GpxLink("https://josm.openstreetmap.de")));
    64         wpt.addExtension("offset", "1.0");
     67        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "offset", "1.0");
    6568        gpx.waypoints.add(wpt);
    6669        wpt = new WayPoint(LatLon.ZERO);
    67         wpt.addExtension("offset", "NaN");
     70        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "offset", "NaN");
    6871        gpx.waypoints.add(wpt);
    6972        layer = new MarkerLayer(gpx, "bar", null, null);
    7073
    7174        assertEquals("bar", layer.getName());
    72         assertEquals(Color.magenta, layer.getColorProperty().get());
     75        assertNull(layer.getColor());
    7376        assertNotNull(layer.getIcon());
    7477        assertEquals("3 markers", layer.getToolTipText());
    7578        assertEquals("<html>bar consists of 3 markers</html>", layer.getInfoComponent());
    7679        assertTrue(layer.getMenuEntries().length > 10);
    7780    }
     81
     82    /**
     83     * Unit test of {@code Main.map.mapView.playHeadMarker}.
     84     */
     85    @Test
     86    public void testPlayHeadMarker() {
     87        try {
     88            MainApplication.getLayerManager().addLayer(new OsmDataLayer(new DataSet(), "", null));
     89            MapFrame map = MainApplication.getMap();
     90            MarkerLayer layer = new MarkerLayer(new GpxData(), null, null, null);
     91            assertNull(map.mapView.playHeadMarker);
     92            MainApplication.getLayerManager().addLayer(layer);
     93            assertNotNull(map.mapView.playHeadMarker);
     94            MainApplication.getLayerManager().removeLayer(layer);
     95        } finally {
     96            if (MainApplication.isDisplayingMapView()) {
     97                MainApplication.getMap().mapView.playHeadMarker = null;
     98            }
     99        }
     100    }
    78101}
  • test/unit/org/openstreetmap/josm/io/GpxWriterTest.java

    Property changes on: test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java
    ___________________________________________________________________
    Deleted: svn:eol-style
    ## -1 +0,0 ##
    -native
    \ No newline at end of property
     
    8787                "    <vdop>0.9</vdop>%n" +
    8888                "    <pdop>1.2</pdop>%n");
    8989    }
     90
     91    /**
     92     * Tests if extensions are handled correctly when reading and writing.
     93     * Source file contains 4 tracks
     94     *  - 1x gpxx colors (garmin)
     95     *  - 1x gpxd colors
     96     *  - 2x no colors
     97     * one of the tracks without colors is assigned a new color
     98     * Then the layer is exported twice
     99     *  - once using gpxx extensions and
     100     *  - once using gpxd extensions
     101     */
     102    @Test
     103    public void testExtensions() {
     104        //TODO
     105    }
     106
    90107}