### Eclipse Workspace Patch 1.0
#P josm-2-21932
Index: src/org/openstreetmap/josm/io/GpxReader.java
===================================================================
--- src/org/openstreetmap/josm/io/GpxReader.java	(revision 18599)
+++ src/org/openstreetmap/josm/io/GpxReader.java	(working copy)
@@ -304,6 +304,8 @@
             case EXT:
                 if (states.lastElement() == State.TRK) {
                     currentTrackExtensionCollection.openChild(namespaceURI, qName, atts);
+                } else if (states.lastElement() == State.WPT) {
+                    currentWayPoint.getExtensions().openChild(namespaceURI, qName, atts);
                 } else {
                     currentExtensionCollection.openChild(namespaceURI, qName, atts);
                 }
@@ -548,6 +550,8 @@
                     String acc = accumulator.toString().trim();
                     if (states.lastElement() == State.TRK) {
                         currentTrackExtensionCollection.closeChild(qName, acc); //a segment inside the track can have an extension too
+                    } else if (states.lastElement() == State.WPT) {
+                        currentWayPoint.getExtensions().closeChild(qName, acc);
                     } else {
                         currentExtensionCollection.closeChild(qName, acc);
                     }
Index: resources/data/gpx-nmea-extensions-1.0.xsd
===================================================================
--- resources/data/gpx-nmea-extensions-1.0.xsd	(nonexistent)
+++ resources/data/gpx-nmea-extensions-1.0.xsd	(working copy)
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schema targetNamespace="https://josm.openstreetmap.de/gpx-nmea-extensions-1.0"
+    elementFormDefault="qualified"
+    xmlns="http://www.w3.org/2001/XMLSchema"
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:nmea="https://josm.openstreetmap.de/gpx-nmea-extensions-1.0"
+    xsi:schemaLocation="https://josm.openstreetmap.de/gpx-nmea-extensions-1.0 https://josm.openstreetmap.de/gpx-nmea-extensions-1.0.xsd">
+
+    <xsd:annotation>
+        <xsd:documentation>
+            This schema defines NMEA extensions for the GPX 1.1 schema (http://www.topografix.com/GPX/1/1/gpx.xsd).
+            Elements in this schema should be used as child elements of the "extensions" element defined by the GPX schema.
+        </xsd:documentation>
+    </xsd:annotation>
+    
+    <!-- Elements -->
+  
+    <xsd:element name="speed" type="xsd:decimal">
+        <xsd:annotation>
+            <xsd:documentation>
+                The speed at the given waypoint in km/h.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+    
+    <xsd:element name="course" type="xsd:decimal">
+        <xsd:annotation>
+            <xsd:documentation>
+                The course in radians.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+</schema>
\ No newline at end of file
Index: src/org/openstreetmap/josm/io/nmea/NmeaReader.java
===================================================================
--- src/org/openstreetmap/josm/io/nmea/NmeaReader.java	(revision 18599)
+++ src/org/openstreetmap/josm/io/nmea/NmeaReader.java	(working copy)
@@ -23,6 +23,7 @@
 import org.openstreetmap.josm.data.gpx.GpxConstants;
 import org.openstreetmap.josm.data.gpx.GpxData;
 import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.IWithAttributes;
 import org.openstreetmap.josm.data.gpx.WayPoint;
 import org.openstreetmap.josm.io.IGpxReader;
 import org.openstreetmap.josm.io.IllegalDataException;
@@ -439,7 +440,7 @@
                     accu = e[VTG.COURSE.position];
                     if (!accu.isEmpty() && currentwp != null) {
                         Double.parseDouble(accu);
-                        currentwp.put("course", accu);
+                        putExt(currentwp, "course", accu);
                     }
                 }
                 // SPEED
@@ -448,7 +449,7 @@
                     accu = e[VTG.SPEED_KMH.position];
                     if (!accu.isEmpty() && currentwp != null) {
                         double speed = Double.parseDouble(accu);
-                        currentwp.put("speed", Double.toString(speed)); // speed in km/h
+                        putExt(currentwp, "speed", Double.toString(speed)); // speed in km/h
                     }
                 }
             } else if (isSentence(e[0], Sentence.GSA)) {
@@ -494,16 +495,16 @@
                 currentwp.setInstant(instant);
                 // speed
                 accu = e[RMC.SPEED.position];
-                if (!accu.isEmpty() && !currentwp.attr.containsKey("speed")) {
+                if (!accu.isEmpty() && !hasExt(currentwp, "speed")) {
                     double speed = Double.parseDouble(accu);
                     speed *= 0.514444444 * 3.6; // to km/h
-                    currentwp.put("speed", Double.toString(speed));
+                    putExt(currentwp, "speed", Double.toString(speed));
                 }
                 // course
                 accu = e[RMC.COURSE.position];
-                if (!accu.isEmpty() && !currentwp.attr.containsKey("course")) {
+                if (!accu.isEmpty() && !hasExt(currentwp, "course")) {
                     Double.parseDouble(accu);
-                    currentwp.put("course", accu);
+                    putExt(currentwp, "course", accu);
                 }
 
                 // TODO fix?
@@ -562,6 +563,16 @@
         }
     }
 
+    private void putExt(IWithAttributes wp, String flag, String field) {
+        if (wp != null && field != null) {
+            wp.getExtensions().addOrUpdate("nmea", flag, field);
+        }
+    }
+
+    private boolean hasExt(IWithAttributes wp, String flag) {
+        return wp.hasExtensions() && wp.getExtensions().get("nmea", flag) != null;
+    }
+
     private static LatLon parseLatLon(String ns, String ew, String dlat, String dlon) {
         String widthNorth = dlat.trim();
         String lengthEast = dlon.trim();
Index: test/unit/org/openstreetmap/josm/io/rtklib/RtkLibPosReaderTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/io/rtklib/RtkLibPosReaderTest.java	(revision 18599)
+++ test/unit/org/openstreetmap/josm/io/rtklib/RtkLibPosReaderTest.java	(working copy)
@@ -61,19 +61,23 @@
         assertEquals(Instant.parse("2019-06-08T08:23:12.600Z"), wayPoints.get(2).getInstant());
 
         assertEquals(new LatLon(46.948881673, -1.484757046), wp0.getCoor());
-        assertEquals(5, wp0.get(GpxConstants.RTKLIB_Q));
+        assertEquals(5, Integer.parseInt(getRtk(wp0, GpxConstants.RTKLIB_Q)));
         assertEquals("92.3955", wp0.get(GpxConstants.PT_ELE));
         assertEquals("2", wp0.get(GpxConstants.PT_SAT));
         assertEquals("1.8191757", wp0.get(GpxConstants.PT_HDOP).toString().trim());
 
-        assertEquals("1.5620", wp0.get(GpxConstants.RTKLIB_SDN));
-        assertEquals("0.9325", wp0.get(GpxConstants.RTKLIB_SDE));
-        assertEquals("0.8167", wp0.get(GpxConstants.RTKLIB_SDU));
-        assertEquals("-0.7246", wp0.get(GpxConstants.RTKLIB_SDNE));
-        assertEquals("0.7583", wp0.get(GpxConstants.RTKLIB_SDEU));
-        assertEquals("0.6573", wp0.get(GpxConstants.RTKLIB_SDUN));
+        assertEquals("1.5620", getRtk(wp0, GpxConstants.RTKLIB_SDN));
+        assertEquals("0.9325", getRtk(wp0, GpxConstants.RTKLIB_SDE));
+        assertEquals("0.8167", getRtk(wp0, GpxConstants.RTKLIB_SDU));
+        assertEquals("-0.7246", getRtk(wp0, GpxConstants.RTKLIB_SDNE));
+        assertEquals("0.7583", getRtk(wp0, GpxConstants.RTKLIB_SDEU));
+        assertEquals("0.6573", getRtk(wp0, GpxConstants.RTKLIB_SDUN));
     }
 
+    private String getRtk(WayPoint wp, String flag) {
+        return wp.getExtensions().get("rtklib", flag).getValue();
+    }
+
     /**
      * Tests reading another RTKLib pos file with different date format.
      * @throws Exception if any error occurs
Index: src/org/openstreetmap/josm/io/GpxWriter.java
===================================================================
--- src/org/openstreetmap/josm/io/GpxWriter.java	(revision 18599)
+++ src/org/openstreetmap/josm/io/GpxWriter.java	(working copy)
@@ -9,13 +9,14 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.time.Instant;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.xml.XMLConstants;
 
@@ -126,27 +127,29 @@
         data.put(META_TIME, (metaTime != null ? metaTime : Instant.now()).toString(), false);
         data.endUpdate();
 
-        Collection<IWithAttributes> all = new ArrayList<>();
-
-        all.add(data);
-        all.addAll(data.getWaypoints());
-        all.addAll(data.getRoutes());
-        all.addAll(data.getTracks());
-        all.addAll(data.getTrackSegmentsStream().collect(Collectors.toList()));
-
-        List<XMLNamespace> namespaces = all
-                .stream()
-                .flatMap(w -> w.getExtensions().getPrefixesStream())
+        // find all XMLNamespaces that occur in the GpxData
+        List<XMLNamespace> namespaces = Stream.of(
+                    Stream.of(data),
+                    data.getWaypoints().stream(),
+                    data.getRoutes().stream(),
+                    data.getTracks().stream(),
+                    data.getTrackSegmentsStream(),
+                    data.getTrackSegmentsStream().flatMap(segment -> segment.getWayPoints().stream()))
+                .flatMap(Function.identity())
+                .map(IWithAttributes::getExtensions)
+                .flatMap(GpxExtensionCollection::getPrefixesStream)
                 .distinct()
-                .map(p -> data.getNamespaces()
+                .map(prefix -> data.getNamespaces()
                         .stream()
-                        .filter(s -> s.getPrefix().equals(p))
+                        .filter(namespace -> namespace.getPrefix().equals(prefix))
                         .findAny()
-                        .orElse(GpxExtension.findNamespace(p)))
+                        .orElse(GpxExtension.findNamespace(prefix)))
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());
 
-        validprefixes = namespaces.stream().map(n -> n.getPrefix()).collect(Collectors.toList());
+        validprefixes = namespaces.stream()
+                .map(XMLNamespace::getPrefix)
+                .collect(Collectors.toList());
 
         data.creator = JOSM_CREATOR_NAME;
         out.println("<?xml version='1.0' encoding='UTF-8'?>");
Index: src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 18599)
+++ src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(working copy)
@@ -922,6 +922,13 @@
         addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_AGEOFDGPSDATA, "gps:ageofdgpsdata");
         addIntegerIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_DGPSID, "gps:dgpsid");
 
+        // Extensions
+        n.getKeys().forEach((key, value) -> {
+            if (key.startsWith(gpxPrefix + "extension")) {
+                wpt.getExtensions().addFlat(key.substring(gpxPrefix.length()).split(":"), value);
+            }
+        });
+
         return wpt;
     }
 
Index: resources/data/gpx-rtklib-extensions-1.0.xsd
===================================================================
--- resources/data/gpx-rtklib-extensions-1.0.xsd	(nonexistent)
+++ resources/data/gpx-rtklib-extensions-1.0.xsd	(working copy)
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schema targetNamespace="https://josm.openstreetmap.de/gpx-rtlib-extensions-1.0"
+    elementFormDefault="qualified"
+    xmlns="http://www.w3.org/2001/XMLSchema"
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:rtklib="https://josm.openstreetmap.de/gpx-rtklib-extensions-1.0"
+    xsi:schemaLocation="https://josm.openstreetmap.de/gpx-rtklib-extensions-1.0 https://josm.openstreetmap.de/gpx-rtklib-extensions-1.0.xsd">
+
+    <xsd:annotation>
+        <xsd:documentation>
+            This schema defines RTKLib extensions for the GPX 1.1 schema (http://www.topografix.com/GPX/1/1/gpx.xsd).
+            Elements in this schema should be used as child elements of the "extensions" element defined by the GPX schema.
+        </xsd:documentation>
+    </xsd:annotation>
+    
+    <!-- Elements -->
+
+    <xsd:element name="Q" type="xsd:positiveInteger">
+        <xsd:annotation>
+            <xsd:documentation>
+                The flag which indicates the solution quality.
+                1 : Fixed, solution by carrier-based relative positioning and the integer ambiguity is properly resolved.
+                2 : Float, solution by carrier-based relative positioning but the integer ambiguity is not resolved.
+                3 : Reserved
+                4 : DGPS, solution by code-based DGPS solutions or single point positioning with SBAS corrections
+                5 : Single, solution by single point positioning
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+
+    <xsd:element name="sdn" type="xsd:decimal">
+        <xsd:annotation>
+            <xsd:documentation>
+                N (north) component of the standard deviations in m.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+
+    <xsd:element name="sde" type="xsd:decimal">
+        <xsd:annotation>
+            <xsd:documentation>
+                E (east) component of the standard deviations in m.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+    
+    <xsd:element name="sdu" type="xsd:decimal">
+        <xsd:annotation>
+            <xsd:documentation>
+                U (up) component of the standard deviations in m.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+    
+    <xsd:element name="sdne" type="xsd:decimal">
+        <xsd:annotation>
+            <xsd:documentation>
+                The absolute value of sdne means square root of the absolute value of NE component of the estimated covariance matrix.
+                The sign represents the sign of the covariance.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+    
+    <xsd:element name="sdeu" type="xsd:decimal">
+        <xsd:annotation>
+            <xsd:documentation>
+                The absolute value of sdeu means square root of the absolute value of EU component of the estimated covariance matrix.
+                The sign represents the sign of the covariance.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+    
+    <xsd:element name="sdun" type="xsd:decimal">
+        <xsd:annotation>
+            <xsd:documentation>
+                The absolute value of sdun means square root of the absolute value of UN component of the estimated covariance matrix.
+                The sign represents the sign of the covariance.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+    
+    <xsd:element name="age" type="xsd:decimal">
+        <xsd:annotation>
+            <xsd:documentation>
+                The time difference between the observation data epochs of the rover receiver and the base station in second.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+    
+    <xsd:element name="ratio" type="xsd:decimal">
+        <xsd:annotation>
+            <xsd:documentation>
+                The ratio factor of "ratio-test" for standard integer ambiguity validation strategy.
+                The value means the ratio of the squared sum of the residuals with the second best integer vector to with the best integer vector.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+    
+</schema>
\ No newline at end of file
Index: src/org/openstreetmap/josm/data/gpx/WithAttributes.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/WithAttributes.java	(revision 18599)
+++ src/org/openstreetmap/josm/data/gpx/WithAttributes.java	(working copy)
@@ -35,7 +35,7 @@
      */
     @Override
     public Object get(String key) {
-        return attr.get(key);
+        return getAttributes().get(key);
     }
 
     /**
@@ -48,7 +48,7 @@
      */
     @Override
     public String getString(String key) {
-        Object value = attr.get(key);
+        Object value = getAttributes().get(key);
         return (value instanceof String) ? (String) value : null;
     }
 
@@ -64,7 +64,7 @@
     @SuppressWarnings("unchecked")
     @Override
     public <T> Collection<T> getCollection(String key) {
-        Object value = attr.get(key);
+        Object value = getAttributes().get(key);
         return (value instanceof Collection) ? (Collection<T>) value : null;
     }
 
@@ -77,7 +77,7 @@
      */
     @Override
     public void put(String key, Object value) {
-        attr.put(key, value);
+        getAttributes().put(key, value);
     }
 
     @Override
@@ -100,7 +100,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(attr, exts);
+        return Objects.hash(getAttributes(), exts);
     }
 
     @Override
@@ -112,10 +112,10 @@
         if (getClass() != obj.getClass())
             return false;
         WithAttributes other = (WithAttributes) obj;
-        if (attr == null) {
-            if (other.attr != null)
+        if (getAttributes() == null) {
+            if (other.getAttributes() != null)
                 return false;
-        } else if (!attr.equals(other.attr))
+        } else if (!getAttributes().equals(other.getAttributes()))
             return false;
         if (exts == null) {
             if (other.exts != null)
Index: test/unit/org/openstreetmap/josm/io/nmea/NmeaReaderTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/io/nmea/NmeaReaderTest.java	(revision 18599)
+++ test/unit/org/openstreetmap/josm/io/nmea/NmeaReaderTest.java	(working copy)
@@ -163,7 +163,7 @@
     }
 
     private static double readSpeed(String nmeaLine) throws IOException, SAXException {
-        return Double.parseDouble(readWayPoint(nmeaLine).getString("speed"));
+        return Double.parseDouble(readWayPoint(nmeaLine).getExtensions().get("nmea", "speed").getValue());
     }
 
     /**
Index: src/org/openstreetmap/josm/data/gpx/GpxConstants.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/GpxConstants.java	(revision 18599)
+++ src/org/openstreetmap/josm/data/gpx/GpxConstants.java	(working copy)
@@ -128,6 +128,24 @@
     String XML_XSD_EXTENSIONS_DRAWING = Config.getUrls().getXMLBase() + "/gpx-drawing-extensions-1.0.xsd";
 
     /**
+     * Namespace for the RTKLib GPX extensions
+     */
+    String XML_URI_EXTENSIONS_RTKLIB = Config.getUrls().getXMLBase() + "/rtklib-extensions-1.0";
+    /**
+     * Location of the XSD schema for the RTKLib GPX extensions
+     */
+    String XML_XSD_EXTENSIONS_RTKLIB = Config.getUrls().getXMLBase() + "/rtklib-extensions-1.0.xsd";
+
+    /**
+     * Namespace for the NMEA GPX extensions
+     */
+    String XML_URI_EXTENSIONS_NMEA = Config.getUrls().getXMLBase() + "/nmea-extensions-1.0";
+    /**
+     * Location of the XSD schema for the NMEA GPX extensions
+     */
+    String XML_XSD_EXTENSIONS_NMEA = Config.getUrls().getXMLBase() + "/nmea-extensions-1.0.xsd";
+
+    /**
      * Namespace for Garmin GPX extensions
      */
     String XML_URI_EXTENSIONS_GARMIN = "http://www.garmin.com/xmlschemas/GpxExtensions/v3";
Index: src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java	(revision 18599)
+++ src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java	(working copy)
@@ -40,6 +40,7 @@
 import org.openstreetmap.josm.data.gpx.GpxData;
 import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeEvent;
 import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener;
+import org.openstreetmap.josm.data.gpx.GpxExtension;
 import org.openstreetmap.josm.data.gpx.Line;
 import org.openstreetmap.josm.data.gpx.WayPoint;
 import org.openstreetmap.josm.data.preferences.NamedColorProperty;
@@ -633,7 +634,16 @@
                 if (colored == ColorMode.HDOP) {
                     color = hdopScale.getColor((Float) trkPnt.get(GpxConstants.PT_HDOP));
                 } else if (colored == ColorMode.QUALITY) {
-                    color = qualityScale.getColor((Integer) trkPnt.get(GpxConstants.RTKLIB_Q));
+                    if (trkPnt.hasExtensions()) {
+                        GpxExtension extQ = trkPnt.getExtensions().get("rtklib", GpxConstants.RTKLIB_Q);
+                        if (extQ != null) {
+                            try {
+                                color = qualityScale.getColor(Integer.parseInt(extQ.getValue()));
+                            } catch (NumberFormatException ex) {
+                                Logging.warn(ex);
+                            }
+                        }
+                    }
                 } else if (colored == ColorMode.FIX) {
                     Object fixval = trkPnt.get(GpxConstants.PT_FIX);
                     if (fixval != null) {
Index: test/unit/org/openstreetmap/josm/data/gpx/GpxExtensionTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/data/gpx/GpxExtensionTest.java	(revision 18599)
+++ test/unit/org/openstreetmap/josm/data/gpx/GpxExtensionTest.java	(working copy)
@@ -38,7 +38,7 @@
         col.add("josm", "from-server", "true");
         EqualsVerifier.forClass(GpxExtension.class).usingGetClass()
         .suppress(Warning.NONFINAL_FIELDS)
-        .withIgnoredFields("qualifiedName", "parent")
+        .withIgnoredFields("parent")
         .withPrefabValues(GpxExtensionCollection.class, new GpxExtensionCollection(), col)
         .verify();
     }
Index: src/org/openstreetmap/josm/io/rtklib/RtkLibPosReader.java
===================================================================
--- src/org/openstreetmap/josm/io/rtklib/RtkLibPosReader.java	(revision 18599)
+++ src/org/openstreetmap/josm/io/rtklib/RtkLibPosReader.java	(working copy)
@@ -15,6 +15,7 @@
 import org.openstreetmap.josm.data.gpx.GpxConstants;
 import org.openstreetmap.josm.data.gpx.GpxData;
 import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.IWithAttributes;
 import org.openstreetmap.josm.data.gpx.WayPoint;
 import org.openstreetmap.josm.io.IGpxReader;
 import org.openstreetmap.josm.tools.Logging;
@@ -77,17 +78,19 @@
                                     Double.parseDouble(fields[IDX_LAT]),
                                     Double.parseDouble(fields[IDX_LON])));
                             currentwp.put(GpxConstants.PT_ELE, fields[IDX_HEIGHT]);
+                            currentwp.put(GpxConstants.PT_SAT, fields[IDX_NS]);
                             currentwp.setInstant(DateUtils.parseInstant(fields[IDX_DATE]+" "+fields[IDX_TIME]));
-                            currentwp.put(GpxConstants.RTKLIB_Q, Integer.parseInt(fields[IDX_Q]));
-                            currentwp.put(GpxConstants.PT_SAT, fields[IDX_NS]);
-                            currentwp.put(GpxConstants.RTKLIB_SDN, fields[IDX_SDN]);
-                            currentwp.put(GpxConstants.RTKLIB_SDE, fields[IDX_SDE]);
-                            currentwp.put(GpxConstants.RTKLIB_SDU, fields[IDX_SDU]);
-                            currentwp.put(GpxConstants.RTKLIB_SDNE, fields[IDX_SDNE]);
-                            currentwp.put(GpxConstants.RTKLIB_SDEU, fields[IDX_SDEU]);
-                            currentwp.put(GpxConstants.RTKLIB_SDUN, fields[IDX_SDUN]);
-                            currentwp.put(GpxConstants.RTKLIB_AGE, fields[IDX_AGE]);
-                            currentwp.put(GpxConstants.RTKLIB_RATIO, fields[IDX_RATIO]);
+
+                            putExt(currentwp, GpxConstants.RTKLIB_Q, fields[IDX_Q]);
+                            putExt(currentwp, GpxConstants.RTKLIB_SDN, fields[IDX_SDN]);
+                            putExt(currentwp, GpxConstants.RTKLIB_SDE, fields[IDX_SDE]);
+                            putExt(currentwp, GpxConstants.RTKLIB_SDU, fields[IDX_SDU]);
+                            putExt(currentwp, GpxConstants.RTKLIB_SDNE, fields[IDX_SDNE]);
+                            putExt(currentwp, GpxConstants.RTKLIB_SDEU, fields[IDX_SDEU]);
+                            putExt(currentwp, GpxConstants.RTKLIB_SDUN, fields[IDX_SDUN]);
+                            putExt(currentwp, GpxConstants.RTKLIB_AGE, fields[IDX_AGE]);
+                            putExt(currentwp, GpxConstants.RTKLIB_RATIO, fields[IDX_RATIO]);
+
                             double sdn = Double.parseDouble(fields[IDX_SDN]);
                             double sde = Double.parseDouble(fields[IDX_SDE]);
                             currentwp.put(GpxConstants.PT_HDOP, (float) Math.sqrt(sdn*sdn + sde*sde));
@@ -105,6 +108,12 @@
         return true;
     }
 
+    private void putExt(IWithAttributes wp, String flag, String field) {
+        if (wp != null && field != null) {
+            wp.getExtensions().add("rtklib", flag, field);
+        }
+    }
+
     @Override
     public GpxData getGpxData() {
         return data;
Index: src/org/openstreetmap/josm/data/gpx/GpxExtension.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/GpxExtension.java	(revision 18599)
+++ src/org/openstreetmap/josm/data/gpx/GpxExtension.java	(working copy)
@@ -1,6 +1,8 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.gpx;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 
@@ -13,7 +15,7 @@
  * @since 15496
  */
 public class GpxExtension extends WithAttributes {
-    private final String qualifiedName, prefix, key;
+    private final String prefix, key;
     private IWithAttributes parent;
     private String value;
     private boolean visible = true;
@@ -28,7 +30,7 @@
         this.prefix = Optional.ofNullable(prefix).orElse("");
         this.key = key;
         this.value = value;
-        this.qualifiedName = (this.prefix.isEmpty() ? "" : this.prefix + ":") + key;
+        this.attr = null;
     }
 
     /**
@@ -40,7 +42,6 @@
      * @param atts the attributes
      */
     public GpxExtension(String namespaceURI, String qName, Attributes atts) {
-        qualifiedName = qName;
         int dot = qName.indexOf(':');
         String p = findPrefix(namespaceURI);
         if (p == null) {
@@ -54,12 +55,12 @@
         }
         key = qName.substring(dot + 1);
         for (int i = 0; i < atts.getLength(); i++) {
-            attr.put(atts.getLocalName(i), atts.getValue(i));
+            getAttributes().put(atts.getLocalName(i), atts.getValue(i));
         }
     }
 
     /**
-     * Finds the default prefix used by JOSM for the given namespaceURI as the document is free specify another one.
+     * Finds the default prefix used by JOSM for the given namespaceURI as the document is free to specify another one.
      * @param namespaceURI namespace URI
      * @return the prefix
      */
@@ -73,6 +74,12 @@
         if (XML_URI_EXTENSIONS_JOSM.equals(namespaceURI))
             return "josm";
 
+        if (XML_URI_EXTENSIONS_RTKLIB.equals(namespaceURI))
+            return "rtklib";
+
+        if (XML_URI_EXTENSIONS_NMEA.equals(namespaceURI))
+            return "nmea";
+
         return null;
     }
 
@@ -89,6 +96,10 @@
             return new XMLNamespace("gpxd", XML_URI_EXTENSIONS_DRAWING, XML_XSD_EXTENSIONS_DRAWING);
         case "josm":
             return new XMLNamespace("josm", XML_URI_EXTENSIONS_JOSM, XML_XSD_EXTENSIONS_JOSM);
+        case "rtklib":
+            return new XMLNamespace("rtklib", XML_URI_EXTENSIONS_RTKLIB, XML_XSD_EXTENSIONS_RTKLIB);
+        case "nmea":
+            return new XMLNamespace("nmea", XML_URI_EXTENSIONS_NMEA, XML_XSD_EXTENSIONS_NMEA);
         }
         return null;
     }
@@ -98,7 +109,7 @@
      * @return the qualified name of the XML element
      */
     public String getQualifiedName() {
-        return qualifiedName;
+        return (this.prefix.isEmpty() ? "" : this.prefix + ":") + key;
     }
 
     /**
@@ -169,7 +180,7 @@
      */
     public void remove() {
         if (parent == null)
-            throw new IllegalStateException("Extension " + qualifiedName + " has no parent, can't remove it.");
+            throw new IllegalStateException("Extension " + getQualifiedName() + " has no parent, can't remove it.");
 
         parent.getExtensions().remove(this);
         if (parent instanceof GpxExtension) {
@@ -232,12 +243,28 @@
      */
     public void setParent(IWithAttributes parent) {
         if (this.parent != null)
-            throw new IllegalStateException("Parent of extension " + qualifiedName + " is already set");
+            throw new IllegalStateException("Parent of extension " + getQualifiedName() + " is already set");
 
         this.parent = parent;
     }
 
+    /**
+     * Returns if the element has attributes without instantiating the HashMap
+     * @return if the element has attributes
+     */
+    public boolean hasAttributes() {
+        return attr != null && !attr.isEmpty();
+    }
+
     @Override
+    public Map<String, Object> getAttributes() {
+        if (attr == null) {
+            attr = new HashMap<>();
+        }
+        return attr;
+    }
+
+    @Override
     public int hashCode() {
         return Objects.hash(prefix, key, value, attr, visible, super.hashCode());
     }
