Ticket #16796: GpxColors-v0.13.diff

File GpxColors-v0.13.diff, 103.6 KB (added by Bjoeni, 6 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;
     
    9598     * @see GpxData#getMetaBounds()
    9699     */
    97100    String META_BOUNDS = META_PREFIX + "bounds";
     101
    98102    /**
    99      * A constant for the metadata hash map: the extension data. This is a {@link Extensions} object
    100      * @see GpxData#addExtension(String, String)
     103     * Prefix used for all extension values.
     104     * Note that all extension values <b>must</b> end with the according XML namespace prefix (i.e <code>.josm</code> or <code>.gpxd</code>)
     105     */
     106    String EXTENSIONS_PREFIX = "extensions.";
     107
     108    /**
     109     * The JOSM extension data (josm:*). This is a {@link Extensions} object
     110     * @see GpxData#addExtensionKey(String, String, String)
    101111     * @see GpxData#get(String)
    102112     */
    103     String META_EXTENSIONS = META_PREFIX + "extensions";
     113    String EXTENSIONS_JOSM = EXTENSIONS_PREFIX + "josm";
    104114
    105115    /**
    106      * A namespace for josm GPX extensions
     116     * The GPX drawing extension data (gpxd:*). This is a {@link Extensions} object
    107117     */
    108     String JOSM_EXTENSIONS_NAMESPACE_URI = Config.getUrls().getXMLBase() + "/gpx-extensions-1.0";
     118    String EXTENSIONS_DRAWING = EXTENSIONS_PREFIX + "gpxd";
    109119
     120    /**
     121     * The Garmin GPX extension data (gpxx:*). This is a {@link Extensions} object
     122     */
     123    String EXTENSIONS_GARMIN = EXTENSIONS_PREFIX + "gpxx";
     124
     125    /**
     126     * Namespace for JOSM GPX extensions
     127     */
     128    String XML_URI_EXTENSIONS_JOSM = Config.getUrls().getXMLBase() + "/gpx-extensions-1.0";
     129    /**
     130     * Location of the XSD schema for JOSM GPX extensions
     131     */
     132    String XML_XSD_EXTENSIONS_JOSM = Config.getUrls().getXMLBase() + "/gpx-extensions-1.0.xsd";
     133
     134    /**
     135     * Namespace for GPX drawing extensions
     136     */
     137    String XML_URI_EXTENSIONS_DRAWING = Config.getUrls().getXMLBase() + "/gpx-drawing-extensions-1.0";
     138    /**
     139     * Location of the XSD schema for GPX drawing extensions
     140     */
     141    String XML_XSD_EXTENSIONS_DRAWING = Config.getUrls().getXMLBase() + "/gpx-drawing-extensions-1.0.xsd";
     142
     143    /**
     144     * Namespace for Garmin GPX extensions
     145     */
     146    String XML_URI_EXTENSIONS_GARMIN = "http://www.garmin.com/xmlschemas/GpxExtensions/v3";
     147    /**
     148     * Location of the XSD schema for GPX drawing extensions
     149     */
     150    String XML_XSD_EXTENSIONS_GARMIN = "http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd";
     151
    110152    /** Elevation (in meters) of the point. */
    111153    String PT_ELE = "ele";
    112154
     
    154196     */
    155197    List<String> WPT_KEYS = Collections.unmodifiableList(Arrays.asList(PT_ELE, PT_TIME, PT_MAGVAR, PT_GEOIDHEIGHT,
    156198            GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, PT_SYM, PT_TYPE,
    157             PT_FIX, PT_SAT, PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID, META_EXTENSIONS));
     199            PT_FIX, PT_SAT, PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID,
     200            EXTENSIONS_JOSM, EXTENSIONS_DRAWING, EXTENSIONS_GARMIN));
    158201
    159202    /**
    160203     * Ordered list of all possible route and track keys.
    161204     */
    162205    List<String> RTE_TRK_KEYS = Collections.unmodifiableList(Arrays.asList(
    163             GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, "number", PT_TYPE, META_EXTENSIONS));
     206            GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, "number", PT_TYPE,
     207            EXTENSIONS_JOSM, EXTENSIONS_DRAWING, EXTENSIONS_GARMIN));
    164208
    165209    /**
     210     * Possible extension namespaces
     211     */
     212    List<String> SUPPORTED_EXTENSION_NAPMESPACES = Collections.unmodifiableList(Arrays.asList(
     213            "josm", "gpxx", "gpxd"));
     214
     215    /**
     216     * Map with all supported Garmin colors
     217     */
     218    Map<String, Color> GARMIN_COLORS = Collections.unmodifiableMap(
     219            new TreeMap<String, Color>(String.CASE_INSENSITIVE_ORDER) {{
     220                put("Black", Color.BLACK);
     221                put("DarkRed", new Color(139, 0, 0));
     222                put("DarkGreen", new Color(0, 100, 0));
     223                put("DarkYellow", new Color(255, 170, 0));
     224                put("DarkBlue", new Color(0, 0, 139));
     225                put("DarkMagenta", new Color(139, 0, 139));
     226                put("DarkCyan", new Color(0, 139, 139));
     227                put("LightGray", Color.LIGHT_GRAY);
     228                put("DarkGray", Color.DARK_GRAY);
     229                put("Red", Color.RED);
     230                put("Green", Color.GREEN);
     231                put("Yellow", Color.YELLOW);
     232                put("Blue", Color.BLUE);
     233                put("Magenta", Color.MAGENTA);
     234                put("Cyan", Color.CYAN);
     235                put("White", Color.WHITE);
     236                put("Transparent", new Color(0, 0, 0, 255));
     237            }});
     238           
     239    /**
    166240     * Possible fix values. NMEA 0183 Version 4.00
    167241     */
    168242    Collection<String> FIX_VALUES = Collections.unmodifiableList(
  • 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.
     
    888889        private Line next;
    889890        private final boolean[] trackVisibility;
    890891        private Map<String, Object> trackAttributes;
     892        private GpxTrack curTrack;
    891893
    892894        /**
    893895         * Constructs a new {@code LinesIterator}.
     
    921923        private Line getNext() {
    922924            if (itTracks != null) {
    923925                if (itTrackSegments != null && itTrackSegments.hasNext()) {
    924                     return new Line(itTrackSegments.next(), trackAttributes);
     926                    return new Line(itTrackSegments.next(), trackAttributes, curTrack.getColor());
    925927                } else {
    926928                    while (itTracks.hasNext()) {
    927                         GpxTrack nxtTrack = itTracks.next();
    928                         trackAttributes = nxtTrack.getAttributes();
     929                        curTrack = itTracks.next();
     930                        trackAttributes = curTrack.getAttributes();
    929931                        idxTracks++;
    930932                        if (trackVisibility != null && !trackVisibility[idxTracks])
    931933                            continue;
    932                         itTrackSegments = nxtTrack.getSegments().iterator();
     934                        itTrackSegments = curTrack.getSegments().iterator();
    933935                        if (itTrackSegments.hasNext()) {
    934                             return new Line(itTrackSegments.next(), trackAttributes);
     936                            return new Line(itTrackSegments.next(), trackAttributes, curTrack.getColor());
    935937                        }
    936938                    }
    937939                    // if we get here, all the Tracks are finished; Continue with Routes
     
    10671069            return source;
    10681070        }
    10691071    }
     1072
     1073    /**
     1074     * @return whether anything (i.e. colors) have been modified
     1075     */
     1076    public boolean isModified() {
     1077        return modified;
     1078    }
    10701079}
  • 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

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.data.gpx;
    3 
    4 import java.util.Collection;
    5 import java.util.Iterator;
    6 import java.util.Map;
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.gpx;
     3
     4import java.awt.Color;
     5import java.util.Collection;
     6import java.util.Iterator;
     7import java.util.Map;
    78import java.util.Objects;
    89
    910/**
     
    1011 * Line represents a linear collection of GPX waypoints with the ordered/unordered distinction.
    1112 * @since 14451
    1213 */
    13 public class Line implements Collection<WayPoint> {
    14     private final Collection<WayPoint> waypoints;
    15     private final boolean unordered;
     14public class Line implements Collection<WayPoint> {
     15    private final Collection<WayPoint> waypoints;
     16    private final boolean unordered;
     17    private final Color color;
     18
     19    /**
     20     * Constructs a new {@code Line}.
     21     * @param waypoints collection of waypoints
     22     * @param attributes track/route attributes
     23     * @param color color of the track
     24     *
     25     */
     26    public Line(Collection<WayPoint> waypoints, Map<String, Object> attributes, Color color) {
     27        this.color = color;
     28        this.waypoints = Objects.requireNonNull(waypoints);
     29        unordered = attributes.isEmpty() && waypoints.stream().allMatch(x -> x.get(GpxConstants.PT_TIME) == null);
     30    }
    1631
    1732    /**
    1833     * Constructs a new {@code Line}.
    19      * @param waypoints collection of waypoints
    20      * @param attributes track/route attributes
    21      */
    22     public Line(Collection<WayPoint> waypoints, Map<String, Object> attributes) {
    23         this.waypoints = Objects.requireNonNull(waypoints);
    24         unordered = attributes.isEmpty() && waypoints.stream().allMatch(x -> x.get(GpxConstants.PT_TIME) == null);
    25     }
    26 
    27     /**
     34     * @param trackSegment track segment
     35     * @param trackAttributes track attributes
     36     * @param color color of the track
     37     */
     38    public Line(GpxTrackSegment trackSegment, Map<String, Object> trackAttributes, Color color) {
     39        this(trackSegment.getWayPoints(), trackAttributes, color);
     40    }
     41
     42    /**
    2843     * Constructs a new {@code Line}.
    29      * @param trackSegment track segment
    30      * @param trackAttributes track attributes
    31      */
    32     public Line(GpxTrackSegment trackSegment, Map<String, Object> trackAttributes) {
    33         this(trackSegment.getWayPoints(), trackAttributes);
    34     }
    35 
    36     /**
    37      * Constructs a new {@code Line}.
    38      * @param route route
    39      */
    40     public Line(GpxRoute route) {
    41         this(route.routePoints, route.attr);
    42     }
    43 
    44     /**
     44     * @param route route
     45     */
     46    public Line(GpxRoute route) {
     47        this(route.routePoints, route.attr, null);
     48    }
     49
     50    /**
    4551     * Determines if waypoints are ordered.
    4652     * @return {@code true} if waypoints are ordered
    4753     */
    4854    public boolean isUnordered() {
    49         return unordered;
     55        return unordered;
     56    }
     57
     58    /**
     59     * Returns the track/route color
     60     * @return the color
     61     */
     62    public Color getColor() {
     63        return color;
     64    }
     65
     66    @Override
     67    public int size() {
     68        return waypoints.size();
    5069    }
    5170
    5271    @Override
    53     public int size() {
    54         return waypoints.size();
    55     }
    56 
    57     @Override
    5872    public boolean isEmpty() {
    5973        return waypoints.isEmpty();
    6074    }
  • 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

     
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.awt.Color;
    76import java.awt.Component;
    87import java.awt.Dimension;
    98import java.awt.Font;
     
    1716import java.util.ArrayList;
    1817import java.util.Arrays;
    1918import java.util.List;
    20 import java.util.Objects;
     19import java.util.Optional;
    2120import java.util.concurrent.CopyOnWriteArrayList;
    2221
    2322import javax.swing.AbstractAction;
     
    4140import org.openstreetmap.josm.actions.MergeLayerAction;
    4241import org.openstreetmap.josm.data.coor.EastNorth;
    4342import org.openstreetmap.josm.data.imagery.OffsetBookmark;
    44 import org.openstreetmap.josm.data.preferences.AbstractProperty;
    4543import org.openstreetmap.josm.gui.MainApplication;
    4644import org.openstreetmap.josm.gui.MapFrame;
    4745import org.openstreetmap.josm.gui.MapView;
     
    583581                label.setFont(label.getFont().deriveFont(Font.BOLD));
    584582            }
    585583            if (Config.getPref().getBoolean("dialog.layer.colorname", true)) {
    586                 AbstractProperty<Color> prop = layer.getColorProperty();
    587                 Color c = prop == null ? null : prop.get();
    588                 if (c == null || model.getLayers().stream()
    589                         .map(Layer::getColorProperty)
    590                         .filter(Objects::nonNull)
    591                         .map(AbstractProperty::get)
    592                         .noneMatch(oc -> oc != null && !oc.equals(c))) {
    593                     /* not more than one color, don't use coloring */
    594                     label.setForeground(UIManager.getColor(isSelected ? "Table.selectionForeground" : "Table.foreground"));
    595                 } else {
    596                     label.setForeground(c);
    597                 }
     584                label.setForeground(Optional
     585                        .ofNullable(layer.getColor())
     586                        .orElse(UIManager.getColor(isSelected ? "Table.selectionForeground" : "Table.foreground")));
    598587            }
    599588            label.setIcon(layer.getIcon());
    600589            label.setToolTipText(layer.getToolTipText());
  • 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

     
    2525
    2626import org.openstreetmap.josm.data.gpx.GpxConstants;
    2727import org.openstreetmap.josm.data.gpx.GpxData;
     28import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    2829import org.openstreetmap.josm.gui.ExtendedDialog;
    2930import org.openstreetmap.josm.gui.MainApplication;
    3031import org.openstreetmap.josm.gui.layer.GpxLayer;
     
    146147        p.add(new JLabel(tr("Keywords")), GBC.eol());
    147148        JosmTextField keywords = new JosmTextField();
    148149        keywords.setText(gpxData.getString(META_KEYWORDS));
    149         p.add(keywords, GBC.eop().fill(GBC.HORIZONTAL));
     150        p.add(keywords, GBC.eol().fill(GBC.HORIZONTAL));
    150151
     152        boolean sel = Config.getPref().getBoolean("gpx.export.colors", true);
     153        JCheckBox colors = new JCheckBox(tr("Save track colors in GPX file"), sel);
     154        p.add(colors, GBC.eol().fill(GBC.HORIZONTAL));
     155        JCheckBox garmin = new JCheckBox(tr("Use Garmin compatible GPX extensions"),
     156                Config.getPref().getBoolean("gpx.export.colors.garmin", false));
     157        garmin.setEnabled(sel);
     158        p.add(garmin, GBC.eop().fill(GBC.HORIZONTAL).insets(20, 0, 0, 0));
     159
     160
    151161        ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(),
    152162                tr("Export options"),
    153163                tr("Export and Save"), tr("Cancel"))
     
    154164            .setButtonIcons("exportgpx", "cancel")
    155165            .setContent(p);
    156166
     167        colors.addActionListener(l -> {
     168            garmin.setEnabled(colors.isSelected());
     169        });
     170
     171        garmin.addActionListener(l -> {
     172            if (garmin.isSelected() &&
     173                    !ConditionalOptionPaneUtil.showConfirmationDialog(
     174                            "gpx_color_garmin",
     175                            ed,
     176                            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>")),
     177                            tr("Information"),
     178                            JOptionPane.OK_CANCEL_OPTION,
     179                            JOptionPane.INFORMATION_MESSAGE,
     180                            JOptionPane.OK_OPTION)) {
     181                garmin.setSelected(false);
     182            }
     183        });
     184
    157185        if (ed.showDialog().getValue() != 1) {
    158186            setCanceled(true);
    159187            return;
     
    167195        if (!copyright.getText().isEmpty()) {
    168196            Config.getPref().put("lastCopyright", copyright.getText());
    169197        }
     198        Config.getPref().putBoolean("gpx.export.colors", colors.isSelected());
     199        Config.getPref().putBoolean("gpx.export.colors.garmin", garmin.isSelected());
     200        String colorFormat = null;
     201        if (colors.isSelected()) {
     202            colorFormat = garmin.isSelected()
     203                    ? GpxConstants.EXTENSIONS_GARMIN
     204                    : GpxConstants.EXTENSIONS_DRAWING;
     205        }
    170206
    171207        if (layer instanceof OsmDataLayer) {
    172208            gpxData = ((OsmDataLayer) layer).toGpxData();
     
    203239            gpxData.put(META_KEYWORDS, keywords.getText());
    204240        }
    205241
    206         try (OutputStream fo = Compression.getCompressedFileOutputStream(file); GpxWriter writer = new GpxWriter(fo)) {
    207             writer.write(gpxData);
     242        try (OutputStream fo = Compression.getCompressedFileOutputStream(file)) {
     243            GpxWriter w = new GpxWriter(fo);
     244            w.write(gpxData, colorFormat);
     245            w.close();
     246            fo.flush();
     247        } catch (IOException | InvalidPathException ex) {
     248            Logging.error(ex);
    208249        }
    209250    }
    210251
  • 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;
     
    5454/**
    5555 * A layer that displays data from a Gpx file / the OSM gpx downloads.
    5656 */
    57 public class GpxLayer extends Layer implements ExpertModeChangeListener {
     57public class GpxLayer extends AbstractModifiableLayer implements ExpertModeChangeListener {
    5858
    5959    /** GPX data */
    6060    public GpxData data;
     
    108108    }
    109109
    110110    @Override
    111     protected NamedColorProperty getBaseColorProperty() {
    112         return GpxDrawHelper.DEFAULT_COLOR;
     111    public Color getColor() {
     112        Color[] c = data.getTracks().stream().map(t -> t.getColor()).distinct().toArray(Color[]::new);
     113        return c.length == 1 ? c[0] : null; //only return if exactly one distinct color present
    113114    }
    114115
     116    @Override
     117    public void setColor(Color color) {
     118        for (GpxTrack trk : data.getTracks()) {
     119            trk.setColor(color);
     120        }
     121    }
     122
     123    @Override
     124    public boolean hasColor() {
     125        return true;
     126    }
     127
    115128    /**
    116129     * Returns a human readable string that shows the timespan of the given track
    117130     * @param trk The GPX track for which timespan is displayed
     
    478491    public void expertChanged(boolean isExpert) {
    479492        this.isExpertMode = isExpert;
    480493    }
     494   
     495        @Override
     496    public boolean isModified() {
     497        return data.isModified();
     498    }
    481499
    482500    @Override
     501    public boolean requiresSaveToFile() {
     502        return isModified() && isLocalFile();
     503    }
     504
     505    @Override
    483506    public String getChangesetSourceTag() {
    484507        // no i18n for international values
    485508        return "survey";
  • 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.Optional;
    3536import java.util.Set;
    3637import java.util.concurrent.CopyOnWriteArrayList;
     
    780781            Collection<Collection<WayPoint>> trk = new ArrayList<>();
    781782            Map<String, Object> trkAttr = new HashMap<>();
    782783
    783             String name = gpxVal(w, "name");
    784             if (name != null) {
    785                 trkAttr.put("name", name);
     784            Map<String, String> trkExts = new HashMap<>();
     785            for (Entry<String, String> e : w.getKeys().entrySet()) {
     786                //String k = e.getKey().replaceFirst("/^" + GpxConstants.GPX_PREFIX + "/", "");
     787                String k = e.getKey().startsWith(GpxConstants.GPX_PREFIX) ? e.getKey().substring(GpxConstants.GPX_PREFIX.length()) : e.getKey();
     788                String v = e.getValue();
     789                if (GpxConstants.RTE_TRK_KEYS.contains(k)) {
     790                    trkAttr.put(k, v);
     791                } else if (GpxConstants.SUPPORTED_EXTENSION_NAPMESPACES.stream().anyMatch(
     792                        s -> k.startsWith(s + ":"))) {
     793                    trkExts.put(k, v);
     794                }
    786795            }
    787 
    788796            List<WayPoint> trkseg = null;
    789797            for (Node n : w.getNodes()) {
    790798                if (!n.isUsable()) {
     
    801809                trkseg.add(nodeToWayPoint(n));
    802810            }
    803811
    804             gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr));
     812            gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr, trkExts));
    805813        });
    806814    }
    807815
  • 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

     
    1010import java.util.Collection;
    1111import java.util.Date;
    1212import java.util.List;
     13import java.util.Map;
    1314import java.util.Map.Entry;
    1415
    1516import javax.swing.BorderFactory;
     
    1920import javax.swing.JPanel;
    2021import javax.swing.JRadioButton;
    2122
     23import org.openstreetmap.josm.data.gpx.Extensions;
    2224import org.openstreetmap.josm.data.gpx.GpxConstants;
    2325import org.openstreetmap.josm.data.gpx.GpxTrack;
    2426import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
     
    2527import org.openstreetmap.josm.data.gpx.WayPoint;
    2628import org.openstreetmap.josm.data.osm.DataSet;
    2729import org.openstreetmap.josm.data.osm.Node;
     30import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2831import org.openstreetmap.josm.data.osm.Way;
    2932import org.openstreetmap.josm.gui.ExtendedDialog;
    3033import org.openstreetmap.josm.gui.MainApplication;
     
    6467                List<Node> nodes = new ArrayList<>();
    6568                for (WayPoint p : segment.getWayPoints()) {
    6669                    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 Number || obj instanceof Date)) {
    71                             keys.add(key);
    72                         }
    73                         if (!none && (obj instanceof String || obj instanceof Number)) {
    74                             // only convert when required
    75                             n.put(GpxConstants.GPX_PREFIX + key, obj.toString());
    76                         } else if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) {
    77                             // timestamps should always be converted
    78                             Date date = (Date) obj;
    79                             if (!none) { //... but the tag will only be set when required
    80                                 n.put(GpxConstants.GPX_PREFIX + key, DateUtils.fromDate(date));
    81                             }
    82                             n.setTimestamp(date);
    83                         }
    84                     }
     70                    addAttributes(p.attr, n, keys, check, none);
    8571                    ds.addPrimitive(n);
    8672                    nodes.add(n);
    8773                }
    8874                Way w = new Way();
    8975                w.setNodes(nodes);
     76                addAttributes(trk.getAttributes(), w, keys, check, none);
    9077                ds.addPrimitive(w);
    9178            }
    9279        }
     
    122109        return ds;
    123110    }
    124111
     112    private static void addAttributes(Map<String, Object> attr, OsmPrimitive n, List<String> keys, boolean check, boolean none) {
     113        for (Entry<String, Object> entry : attr.entrySet()) {
     114            String key = entry.getKey();
     115            Object obj = entry.getValue();
     116            if (check && !keys.contains(key) && (obj instanceof String || obj instanceof Number || obj instanceof Date)) {
     117                keys.add(key);
     118            }
     119            if (!none && (obj instanceof String || obj instanceof Number)) {
     120                // only convert when required
     121                n.put(GpxConstants.GPX_PREFIX + key, obj.toString());
     122            } else if (!none && 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            } else if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) {
     133                // timestamps should always be converted
     134                Date date = (Date) obj;
     135                if (!none) { //... but the tag will only be set when required
     136                    n.put(GpxConstants.GPX_PREFIX + key, DateUtils.fromDate(date));
     137                }
     138                n.setTimestamp(date);
     139            }
     140        }
     141    }
     142
    125143    /**
    126144     * Filters the tags of the given {@link DataSet}
    127145     * @param ds The {@link DataSet}
     
    131149     */
    132150    public DataSet filterDataSet(DataSet ds, List<String> listPos) {
    133151        Collection<Node> nodes = ds.getNodes();
    134         for (Node n : nodes) {
    135             for (String key : n.keySet()) {
     152        for (OsmPrimitive p : ds.getPrimitives(p -> p instanceof Node || p instanceof Way)) {
     153            for (String key : p.keySet()) {
    136154                if (listPos == null || !listPos.contains(key.substring(GpxConstants.GPX_PREFIX.length()))) {
    137                     n.put(key, null);
     155                   p.put(key, null);
    138156                }
    139157            }
    140158        }
  • 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;
     
    273273    }
    274274
    275275    /**
    276      * Get the default color for gps tracks for specified layer
    277      * @param layerName name of the GpxLayer
    278      * @param ignoreCustom do not use preferences
    279      * @return the color or null if the color is not constant
    280      */
    281     public Color getColor(String layerName, boolean ignoreCustom) {
    282         if (ignoreCustom || getColorMode(layerName) == ColorMode.NONE) {
    283             return DEFAULT_COLOR.getChildColor(
    284                     NamedColorProperty.COLOR_CATEGORY_LAYER,
    285                     layerName,
    286                     DEFAULT_COLOR.getName()).get();
    287         } else {
    288             return null;
    289         }
    290     }
    291 
    292     /**
    293276     * Read coloring mode for specified layer from preferences
    294277     * @param layerName name of the GpxLayer
    295278     * @return coloring mode
     
    304287        return ColorMode.NONE;
    305288    }
    306289
    307     /** Reads generic color from preferences (usually gray)
    308      * @return the color
    309      **/
    310     public static Color getGenericColor() {
    311         return DEFAULT_COLOR.get();
    312     }
    313 
    314290    /**
    315291     * Read all drawing-related settings from preferences
    316292     * @param layerName layer name used to access its specific preferences
     
    352328
    353329        // shrink to range
    354330        heatMapDrawGain = Utils.clamp(heatMapDrawGain, -10, 10);
    355 
    356         neutralColor = getColor(layerName, true);
     331        neutralColor = DEFAULT_COLOR_PROPERTY.get();
    357332        velocityScale.setNoDataColor(neutralColor);
    358333        dateScale.setNoDataColor(neutralColor);
    359334        hdopScale.setNoDataColor(neutralColor);
     
    594569            }
    595570            for (WayPoint trkPnt : segment) {
    596571                LatLon c = trkPnt.getCoor();
    597                 trkPnt.customColoring = neutralColor;
     572                trkPnt.customColoring = segment.getColor();
    598573                if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
    599574                    continue;
    600575                }
     
    642617                    }
    643618                } else { // make sure we reset outdated data
    644619                    trkPnt.drawLine = false;
    645                     color = neutralColor;
     620                    color = segment.getColor();
    646621                }
    647622                if (color != null) {
    648623                    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

     
    257257        WayPoint wpt = new WayPoint(getCoor());
    258258        wpt.setTimeInMillis((long) (time * 1000));
    259259        if (text != null) {
    260             wpt.addExtension("text", text);
     260            wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "text", text);
    261261        } else if (dataProvider != null) {
    262262            for (String key : dataProvider.getTemplateKeys()) {
    263263                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;
     
    331329            if (null != dim) {
    332330                // get image size of environment
    333331                final int iconSize = (int) dim.getHeight();
    334                 final Color color;
    335                 // ask the GPX draw for the correct color of that layer ( if there is one )
    336                 if (null != layerName) {
    337                     color = GpxDrawHelper.DEFAULT_COLOR.getChildColor(
    338                             NamedColorProperty.COLOR_CATEGORY_LAYER, layerName, GpxDrawHelper.DEFAULT_COLOR.getName()).get();
    339                 } else {
    340                     color = GpxDrawHelper.DEFAULT_COLOR.getDefaultValue();
    341                 }
    342                 colorTypeHeatIconLabel.setIcon(GpxDrawHelper.getColorMapImageIcon(color, colorTypeHeatMapTune.getSelectedIndex(), iconSize));
     332                colorTypeHeatIconLabel.setIcon(GpxDrawHelper.getColorMapImageIcon(
     333                        GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get(),
     334                        colorTypeHeatMapTune.getSelectedIndex(),
     335                        iconSize));
    343336            }
    344337        });
    345338
  • 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/markerlayer/AudioMarkerTest.java

     
    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

     
    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}