Ignore:
Timestamp:
2019-11-02T15:11:34+01:00 (5 years ago)
Author:
Don-vip
Message:

fix #16796 - Rework of GPX track colors / layer preferences (patch by Bjoeni)

Location:
trunk/src/org/openstreetmap/josm/io
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/io/GpxReader.java

    r14456 r15496  
    1919import org.openstreetmap.josm.data.Bounds;
    2020import org.openstreetmap.josm.data.coor.LatLon;
    21 import org.openstreetmap.josm.data.gpx.Extensions;
    2221import org.openstreetmap.josm.data.gpx.GpxConstants;
    2322import org.openstreetmap.josm.data.gpx.GpxData;
     23import org.openstreetmap.josm.data.gpx.GpxData.XMLNamespace;
     24import org.openstreetmap.josm.data.gpx.GpxExtensionCollection;
    2425import org.openstreetmap.josm.data.gpx.GpxLink;
    2526import org.openstreetmap.josm.data.gpx.GpxRoute;
    26 import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
     27import org.openstreetmap.josm.data.gpx.GpxTrack;
     28import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
     29import org.openstreetmap.josm.data.gpx.IGpxTrackSegment;
    2730import org.openstreetmap.josm.data.gpx.WayPoint;
    2831import org.openstreetmap.josm.tools.Logging;
     
    6871
    6972        private GpxData data;
    70         private Collection<Collection<WayPoint>> currentTrack;
     73        private Collection<IGpxTrackSegment> currentTrack;
    7174        private Map<String, Object> currentTrackAttr;
    7275        private Collection<WayPoint> currentTrackSeg;
     
    7780
    7881        private GpxLink currentLink;
    79         private Extensions currentExtensions;
     82        private GpxExtensionCollection currentExtensionCollection;
     83        private GpxExtensionCollection currentTrackExtensionCollection;
    8084        private Stack<State> states;
    8185        private final Stack<String> elements = new Stack<>();
     
    8993            accumulator = new StringBuilder();
    9094            states = new Stack<>();
    91             data = new GpxData();
     95            data = new GpxData(true);
     96            currentExtensionCollection = new GpxExtensionCollection();
     97            currentTrackExtensionCollection = new GpxExtensionCollection();
     98        }
     99
     100        @Override
     101        public void startPrefixMapping(String prefix, String uri) throws SAXException {
     102            data.getNamespaces().add(new XMLNamespace(prefix, uri));
    92103        }
    93104
     
    134145                    version = "1.1";
    135146                }
     147                String schemaLocation = atts.getValue(GpxConstants.XML_URI_XSD, "schemaLocation");
     148                if (schemaLocation != null) {
     149                    String[] schemaLocations = schemaLocation.split(" ");
     150                    for (int i = 0; i < schemaLocations.length - 1; i += 2) {
     151                        final String schemaURI = schemaLocations[i];
     152                        final String schemaXSD = schemaLocations[i + 1];
     153                        data.getNamespaces().stream().filter(xml -> xml.getURI().equals(schemaURI)).forEach(xml -> {
     154                            xml.setLocation(schemaXSD);
     155                        });
     156                    }
     157                }
    136158                break;
    137159            case GPX:
     
    160182                    states.push(currentState);
    161183                    currentState = State.EXT;
    162                     currentExtensions = new Extensions();
    163184                    break;
    164185                case "gpx":
     
    179200                    states.push(currentState);
    180201                    currentState = State.EXT;
    181                     currentExtensions = new Extensions();
    182202                    break;
    183203                case "copyright":
     
    229249                    states.push(currentState);
    230250                    currentState = State.EXT;
    231                     currentExtensions = new Extensions();
    232251                    break;
    233252                default: // Do nothing
     
    235254                break;
    236255            case TRKSEG:
    237                 if ("trkpt".equals(localName)) {
     256                switch (localName) {
     257                case "trkpt":
    238258                    states.push(currentState);
    239259                    currentState = State.WPT;
    240260                    currentWayPoint = new WayPoint(parseLatLon(atts));
     261                    break;
     262                case "extensions":
     263                    states.push(currentState);
     264                    currentState = State.EXT;
     265                    break;
    241266                }
    242267                break;
     
    251276                    states.push(currentState);
    252277                    currentState = State.EXT;
    253                     currentExtensions = new Extensions();
    254278                    break;
    255279                default: // Do nothing
     
    271295                    states.push(currentState);
    272296                    currentState = State.EXT;
    273                     currentExtensions = new Extensions();
    274                     break;
    275                 default: // Do nothing
     297                    break;
     298                default: // Do nothing
     299                }
     300                break;
     301            case EXT:
     302                if (states.lastElement() == State.TRK) {
     303                    currentTrackExtensionCollection.openChild(namespaceURI, qName, atts);
     304                } else {
     305                    currentExtensionCollection.openChild(namespaceURI, qName, atts);
    276306                }
    277307                break;
     
    350380                        (currentState == State.GPX && "gpx".equals(localName))) {
    351381                        convertUrlToLink(data.attr);
    352                         if (currentExtensions != null && !currentExtensions.isEmpty()) {
    353                             data.put(META_EXTENSIONS, currentExtensions);
    354                         }
     382                        data.getExtensions().addAll(currentExtensionCollection);
     383                        currentExtensionCollection.clear();
    355384                        currentState = states.pop();
    356385                    }
     
    360389                    break;
    361390                default:
    362                     //TODO: parse extensions
    363391                }
    364392                break;
     
    465493                    currentState = states.pop();
    466494                    convertUrlToLink(currentWayPoint.attr);
    467                     if (currentExtensions != null && !currentExtensions.isEmpty()) {
    468                         currentWayPoint.put(META_EXTENSIONS, currentExtensions);
    469                     }
     495                    currentWayPoint.getExtensions().addAll(currentExtensionCollection);
    470496                    data.waypoints.add(currentWayPoint);
     497                    currentExtensionCollection.clear();
    471498                    break;
    472499                default: // Do nothing
     
    476503                if ("trkseg".equals(localName)) {
    477504                    currentState = states.pop();
    478                     currentTrack.add(currentTrackSeg);
     505                    if (!currentTrackSeg.isEmpty()) {
     506                        GpxTrackSegment seg = new GpxTrackSegment(currentTrackSeg);
     507                        seg.getExtensions().addAll(currentExtensionCollection);
     508                        currentTrack.add(seg);
     509                    }
     510                    currentExtensionCollection.clear();
    479511                }
    480512                break;
     
    484516                    currentState = states.pop();
    485517                    convertUrlToLink(currentTrackAttr);
    486                     data.addTrack(new ImmutableGpxTrack(currentTrack, currentTrackAttr));
     518                    GpxTrack trk = new GpxTrack(new ArrayList<>(currentTrack), currentTrackAttr);
     519                    trk.getExtensions().addAll(currentTrackExtensionCollection);
     520                    data.addTrack(trk);
     521                    currentTrackExtensionCollection.clear();
    487522                    break;
    488523                case "name":
     
    502537                if ("extensions".equals(localName)) {
    503538                    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());
     539                } else if (currentExtensionCollection != null) {
     540                    String acc = accumulator.toString().trim();
     541                    if (states.lastElement() == State.TRK) {
     542                        currentTrackExtensionCollection.closeChild(qName, acc); //a segment inside the track can have an extension too
     543                    } else {
     544                        currentExtensionCollection.closeChild(qName, acc);
     545                    }
    507546                }
    508547                break;
     
    520559                }
    521560            }
     561            accumulator.setLength(0);
    522562        }
    523563
     
    526566            if (!states.empty())
    527567                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"))) {
    530                 data.fromServer = true;
    531             }
     568
     569            data.getExtensions().stream("josm", "from-server").findAny().ifPresent(ext -> {
     570                data.fromServer = "true".equals(ext.getValue());
     571            });
     572
     573            data.getExtensions().stream("josm", "layerPreferences").forEach(prefs -> {
     574                prefs.getExtensions().stream("josm", "entry").forEach(prefEntry -> {
     575                    Object key = prefEntry.get("key");
     576                    Object val = prefEntry.get("value");
     577                    if (key != null && val != null) {
     578                        data.getLayerPrefs().put(key.toString(), val.toString());
     579                    }
     580                });
     581            });
     582            data.endUpdate();
    532583            gpxData = data;
    533584        }
  • trunk/src/org/openstreetmap/josm/io/GpxWriter.java

    r14459 r15496  
    99import java.io.PrintWriter;
    1010import java.nio.charset.StandardCharsets;
     11import java.util.ArrayList;
    1112import java.util.Collection;
    1213import java.util.Date;
    1314import java.util.List;
    1415import java.util.Map;
    15 import java.util.Map.Entry;
     16import java.util.Objects;
     17import java.util.stream.Collectors;
    1618
    1719import javax.xml.XMLConstants;
     
    1921import org.openstreetmap.josm.data.Bounds;
    2022import org.openstreetmap.josm.data.coor.LatLon;
    21 import org.openstreetmap.josm.data.gpx.Extensions;
    2223import org.openstreetmap.josm.data.gpx.GpxConstants;
    2324import org.openstreetmap.josm.data.gpx.GpxData;
     25import org.openstreetmap.josm.data.gpx.GpxData.XMLNamespace;
     26import org.openstreetmap.josm.data.gpx.GpxExtension;
     27import org.openstreetmap.josm.data.gpx.GpxExtensionCollection;
    2428import org.openstreetmap.josm.data.gpx.GpxLink;
    2529import org.openstreetmap.josm.data.gpx.GpxRoute;
    2630import org.openstreetmap.josm.data.gpx.GpxTrack;
    27 import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
     31import org.openstreetmap.josm.data.gpx.IGpxTrackSegment;
    2832import org.openstreetmap.josm.data.gpx.IWithAttributes;
    2933import org.openstreetmap.josm.data.gpx.WayPoint;
     
    5559    private GpxData data;
    5660    private String indent = "";
     61    private List<String> validprefixes;
    5762
    5863    private static final int WAY_POINT = 0;
     
    6570     */
    6671    public void write(GpxData data) {
     72        write(data, ColorFormat.GPXD, true);
     73    }
     74
     75    /**
     76     * Writes the given GPX data.
     77     *
     78     * @param data The data to write
     79     * @param colorFormat determines if colors are saved and which extension is to be used
     80     * @param savePrefs whether layer specific preferences are saved
     81     */
     82    public void write(GpxData data, ColorFormat colorFormat, boolean savePrefs) {
    6783        this.data = data;
    68         // We write JOSM specific meta information into gpx 'extensions' elements.
    69         // In particular it is noted whether the gpx data is from the OSM server
    70         // (so the rendering of clouds of anonymous TrackPoints can be improved)
    71         // and some extra synchronization info for export of AudioMarkers.
    72         // It is checked in advance, if any extensions are used, so we know whether
    73         // a namespace declaration is necessary.
    74         boolean hasExtensions = data.fromServer;
    75         if (!hasExtensions) {
    76             for (WayPoint wpt : data.waypoints) {
    77                 Extensions extensions = (Extensions) wpt.get(META_EXTENSIONS);
    78                 if (extensions != null && !extensions.isEmpty()) {
    79                     hasExtensions = true;
    80                     break;
    81                 }
    82             }
    83         }
     84
     85        //Prepare extensions for writing
     86        data.beginUpdate();
     87        data.getTracks().forEach(trk -> trk.convertColor(colorFormat));
     88        data.getExtensions().removeAllWithPrefix("josm");
     89        if (data.fromServer) {
     90            data.getExtensions().add("josm", "from-server", "true");
     91        }
     92        if (savePrefs && !data.getLayerPrefs().isEmpty()) {
     93            GpxExtensionCollection layerExts = data.getExtensions().add("josm", "layerPreferences").getExtensions();
     94            data.getLayerPrefs().entrySet()
     95            .stream()
     96            .sorted((e1, e2) -> e1.getKey().compareTo(e2.getKey()))
     97            .forEach(entry -> {
     98                GpxExtension e = layerExts.add("josm", "entry");
     99                e.put("key", entry.getKey());
     100                e.put("value", entry.getValue());
     101            });
     102        }
     103        data.endUpdate();
     104
     105        Collection<IWithAttributes> all = new ArrayList<>();
     106
     107        all.add(data);
     108        all.addAll(data.getWaypoints());
     109        all.addAll(data.getRoutes());
     110        all.addAll(data.getTracks());
     111        all.addAll(data.getTrackSegmentsStream().collect(Collectors.toList()));
     112
     113        List<XMLNamespace> namespaces = all
     114                .stream()
     115                .flatMap(w -> w.getExtensions().getPrefixesStream())
     116                .distinct()
     117                .map(p -> data.getNamespaces()
     118                        .stream()
     119                        .filter(s -> s.getPrefix().equals(p))
     120                        .findAny()
     121                        .orElse(GpxExtension.findNamespace(p)))
     122                .filter(Objects::nonNull)
     123                .collect(Collectors.toList());
     124
     125        validprefixes = namespaces.stream().map(n -> n.getPrefix()).collect(Collectors.toList());
    84126
    85127        out.println("<?xml version='1.0' encoding='UTF-8'?>");
    86128        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\">");
     129
     130        String schemaLocations = "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd";
     131
     132        for (XMLNamespace n : namespaces) {
     133            if (n.getURI() != null && n.getPrefix() != null && !n.getPrefix().isEmpty()) {
     134                out.println(String.format("    xmlns:%s=\"%s\"", n.getPrefix(), n.getURI()));
     135                if (n.getLocation() != null) {
     136                    schemaLocations += " " + n.getURI() + " " + n.getLocation();
     137                }
     138            }
     139        }
     140
     141        out.println("    xmlns:xsi=\""+XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI+"\"");
     142        out.println(String.format("    xsi:schemaLocation=\"%s\">", schemaLocations));
    90143        indent = "  ";
    91144        writeMetaData();
     
    105158                        gpxLink(link);
    106159                    }
    107                 }
    108             } else if (META_EXTENSIONS.equals(key)) {
    109                 Extensions extensions = (Extensions) obj.get(key);
    110                 if (extensions != null) {
    111                     gpxExtensions(extensions);
    112160                }
    113161            } else {
     
    148196                String[] tmp = data.getString(META_AUTHOR_EMAIL).split("@");
    149197                if (tmp.length == 2) {
    150                     inline("email", "id=\"" + tmp[0] + "\" domain=\""+tmp[1]+'\"');
     198                    inline("email", "id=\"" + encode(tmp[0]) + "\" domain=\"" + encode(tmp[1]) +'\"');
    151199                }
    152200            }
     
    159207        if (attr.containsKey(META_COPYRIGHT_LICENSE)
    160208                || attr.containsKey(META_COPYRIGHT_YEAR)) {
    161             openAtt("copyright", "author=\""+ data.get(META_COPYRIGHT_AUTHOR) +'\"');
     209            openln("copyright", "author=\""+ encode(data.get(META_COPYRIGHT_AUTHOR).toString()) +'\"');
    162210            if (attr.containsKey(META_COPYRIGHT_YEAR)) {
    163211                simpleTag("year", (String) data.get(META_COPYRIGHT_YEAR));
     
    188236        }
    189237
    190         if (data.fromServer) {
    191             openln("extensions");
    192             simpleTag("josm:from-server", "true");
    193             closeln("extensions");
    194         }
    195 
     238        gpxExtensions(data.getExtensions());
    196239        closeln("metadata");
    197240    }
     
    207250            openln("rte");
    208251            writeAttr(rte, RTE_TRK_KEYS);
     252            gpxExtensions(rte.getExtensions());
    209253            for (WayPoint pnt : rte.routePoints) {
    210254                wayPoint(pnt, ROUTE_POINT);
     
    218262            openln("trk");
    219263            writeAttr(trk, RTE_TRK_KEYS);
    220             for (GpxTrackSegment seg : trk.getSegments()) {
     264            gpxExtensions(trk.getExtensions());
     265            for (IGpxTrackSegment seg : trk.getSegments()) {
    221266                openln("trkseg");
     267                gpxExtensions(seg.getExtensions());
    222268                for (WayPoint pnt : seg.getWayPoints()) {
    223269                    wayPoint(pnt, TRACK_POINT);
     
    234280    }
    235281
     282    private void openln(String tag, String attributes) {
     283        open(tag, attributes);
     284        out.println();
     285    }
     286
    236287    private void open(String tag) {
    237288        out.print(indent + '<' + tag + '>');
     
    239290    }
    240291
    241     private void openAtt(String tag, String attributes) {
    242         out.println(indent + '<' + tag + ' ' + attributes + '>');
     292    private void open(String tag, String attributes) {
     293        out.print(indent + '<' + tag + (attributes.isEmpty() ? "" : ' ') + attributes + '>');
    243294        indent += "  ";
    244295    }
    245296
    246297    private void inline(String tag, String attributes) {
    247         out.println(indent + '<' + tag + ' ' + attributes + "/>");
     298        out.println(indent + '<' + tag + (attributes.isEmpty() ? "" : ' ') + attributes + "/>");
    248299    }
    249300
     
    273324    }
    274325
     326    private void simpleTag(String tag, String content, String attributes) {
     327        if (content != null && !content.isEmpty()) {
     328            open(tag, attributes);
     329            out.print(encode(content));
     330            out.println("</" + tag + '>');
     331            indent = indent.substring(2);
     332        }
     333    }
     334
    275335    /**
    276336     * output link
     
    279339    private void gpxLink(GpxLink link) {
    280340        if (link != null) {
    281             openAtt("link", "href=\"" + link.uri + '\"');
     341            openln("link", "href=\"" + encode(link.uri) + '\"');
    282342            simpleTag("text", link.text);
    283343            simpleTag("type", link.type);
     
    309369            LatLon c = pnt.getCoor();
    310370            String coordAttr = "lat=\"" + c.lat() + "\" lon=\"" + c.lon() + '\"';
    311             if (pnt.attr.isEmpty()) {
     371            if (pnt.attr.isEmpty() && pnt.getExtensions().isEmpty()) {
    312372                inline(type, coordAttr);
    313373            } else {
    314                 openAtt(type, coordAttr);
     374                openln(type, coordAttr);
    315375                writeAttr(pnt, WPT_KEYS);
     376                gpxExtensions(pnt.getExtensions());
    316377                closeln(type);
    317378            }
     
    319380    }
    320381
    321     private void gpxExtensions(Extensions extensions) {
    322         if (extensions != null && !extensions.isEmpty()) {
     382    private void gpxExtensions(GpxExtensionCollection allExtensions) {
     383        if (allExtensions.isVisible()) {
    323384            openln("extensions");
    324             for (Entry<String, String> e : extensions.entrySet()) {
    325                 simpleTag("josm:" + e.getKey(), e.getValue());
    326             }
     385            writeExtension(allExtensions);
    327386            closeln("extensions");
    328387        }
    329388    }
     389
     390    private void writeExtension(List<GpxExtension> extensions) {
     391        for (GpxExtension e : extensions) {
     392            if (validprefixes.contains(e.getPrefix()) && e.isVisible()) {
     393                // this might lead to loss of an unknown extension *after* the file was saved as .osm,
     394                // but otherwise the file is invalid and can't even be parsed by SAX anymore
     395                String k = (e.getPrefix().isEmpty() ? "" : e.getPrefix() + ":") + e.getKey();
     396                String attr = String.join(" ", e.getAttributes().entrySet().stream().map(a -> encode(a.getKey()) + "=\"" + encode(a.getValue().toString()) + "\"").sorted().collect(Collectors.toList()));
     397                if (e.getValue() == null && e.getExtensions().isEmpty()) {
     398                    inline(k, attr);
     399                } else if (e.getExtensions().isEmpty()) {
     400                    simpleTag(k, e.getValue(), attr);
     401                } else {
     402                    openln(k, attr);
     403                    if (e.getValue() != null) {
     404                        out.print(encode(e.getValue()));
     405                    }
     406                    writeExtension(e.getExtensions());
     407                    closeln(k);
     408                }
     409            }
     410        }
     411    }
    330412}
  • trunk/src/org/openstreetmap/josm/io/nmea/NmeaReader.java

    r15247 r15496  
    2121import org.openstreetmap.josm.data.gpx.GpxConstants;
    2222import org.openstreetmap.josm.data.gpx.GpxData;
    23 import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
     23import org.openstreetmap.josm.data.gpx.GpxTrack;
    2424import org.openstreetmap.josm.data.gpx.WayPoint;
    2525import org.openstreetmap.josm.io.IGpxReader;
     
    265265            }
    266266            currentTrack.add(ps.waypoints);
    267             data.tracks.add(new ImmutableGpxTrack(currentTrack, Collections.<String, Object>emptyMap()));
     267            data.tracks.add(new GpxTrack(currentTrack, Collections.<String, Object>emptyMap()));
    268268
    269269        } catch (IllegalDataException e) {
  • trunk/src/org/openstreetmap/josm/io/rtklib/RtkLibPosReader.java

    r15343 r15496  
    1919import org.openstreetmap.josm.data.gpx.GpxConstants;
    2020import org.openstreetmap.josm.data.gpx.GpxData;
    21 import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
     21import org.openstreetmap.josm.data.gpx.GpxTrack;
    2222import org.openstreetmap.josm.data.gpx.WayPoint;
    2323import org.openstreetmap.josm.io.IGpxReader;
     
    115115        }
    116116        currentTrack.add(waypoints);
    117         data.tracks.add(new ImmutableGpxTrack(currentTrack, Collections.<String, Object>emptyMap()));
     117        data.tracks.add(new GpxTrack(currentTrack, Collections.<String, Object>emptyMap()));
    118118        return true;
    119119    }
  • trunk/src/org/openstreetmap/josm/io/session/GenericSessionExporter.java

    r15404 r15496  
    8282        @Override
    8383        public void actionPerformed(ActionEvent e) {
    84             SaveAction.getInstance().doSave(layer);
     84            SaveAction.getInstance().doSave(layer, true);
    8585            updateEnabledState();
    8686        }
Note: See TracChangeset for help on using the changeset viewer.