source: josm/trunk/src/org/openstreetmap/josm/actions/relation/ExportRelationToGpxAction.java @ 14434

Last change on this file since 14434 was 14434, checked in by Don-vip, 3 months ago

fix #16995 - fix timestamp in GPX exports (patch by cmuelle8) + use Java 8 unsigned int API

  • Property svn:eol-style set to native
File size: 9.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions.relation;
3
4import static org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode.FROM_FIRST_MEMBER;
5import static org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode.TO_FILE;
6import static org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode.TO_LAYER;
7import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
8import static org.openstreetmap.josm.tools.I18n.tr;
9
10import java.awt.event.ActionEvent;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.EnumSet;
16import java.util.HashMap;
17import java.util.Iterator;
18import java.util.List;
19import java.util.ListIterator;
20import java.util.Map;
21import java.util.Set;
22import java.util.Stack;
23import java.util.concurrent.TimeUnit;
24
25import org.openstreetmap.josm.actions.GpxExportAction;
26import org.openstreetmap.josm.actions.IPrimitiveAction;
27import org.openstreetmap.josm.data.gpx.GpxData;
28import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
29import org.openstreetmap.josm.data.gpx.WayPoint;
30import org.openstreetmap.josm.data.osm.IPrimitive;
31import org.openstreetmap.josm.data.osm.Node;
32import org.openstreetmap.josm.data.osm.Relation;
33import org.openstreetmap.josm.data.osm.RelationMember;
34import org.openstreetmap.josm.data.osm.Way;
35import org.openstreetmap.josm.gui.MainApplication;
36import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType;
37import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator;
38import org.openstreetmap.josm.gui.layer.GpxLayer;
39import org.openstreetmap.josm.gui.layer.Layer;
40import org.openstreetmap.josm.gui.layer.OsmDataLayer;
41import org.openstreetmap.josm.tools.SubclassFilteredCollection;
42
43/**
44 * Exports the current relation to a single GPX track,
45 * currently for type=route and type=superroute relations only.
46 *
47 * @since 13210
48 */
49public class ExportRelationToGpxAction extends GpxExportAction
50    implements IPrimitiveAction {
51
52    /** Enumeration of export variants */
53    public enum Mode {
54        /** concatenate members from first to last element */
55        FROM_FIRST_MEMBER,
56        /** concatenate members from last to first element */
57        FROM_LAST_MEMBER,
58        /** export to GPX layer and add to LayerManager */
59        TO_LAYER,
60        /** export to GPX file and open FileChooser */
61        TO_FILE
62    }
63
64    /** Mode of this ExportToGpxAction */
65    protected final Set<Mode> mode;
66
67    /** Primitives this action works on */
68    protected Collection<Relation> relations = Collections.<Relation>emptySet();
69
70    /** Construct a new ExportRelationToGpxAction with default mode */
71    public ExportRelationToGpxAction() {
72        this(EnumSet.of(FROM_FIRST_MEMBER, TO_FILE));
73    }
74
75    /**
76     * Constructs a new {@code ExportRelationToGpxAction}
77     *
78     * @param mode which mode to use, see {@code ExportRelationToGpxAction.Mode}
79     */
80    public ExportRelationToGpxAction(Set<Mode> mode) {
81        super(name(mode), mode.contains(TO_FILE) ? "exportgpx" : "dialogs/layerlist", tooltip(mode),
82                null, false, null, false);
83        setHelpId(ht("/Action/ExportRelationToGpx"));
84        this.mode = mode;
85    }
86
87    private static String name(Set<Mode> mode) {
88        if (mode.contains(TO_FILE)) {
89            if (mode.contains(FROM_FIRST_MEMBER)) {
90                return tr("Export GPX file starting from first member");
91            } else {
92                return tr("Export GPX file starting from last member");
93            }
94        } else {
95            if (mode.contains(FROM_FIRST_MEMBER)) {
96                return tr("Convert to GPX layer starting from first member");
97            } else {
98                return tr("Convert to GPX layer starting from last member");
99            }
100        }
101    }
102
103    private static String tooltip(Set<Mode> mode) {
104        if (mode.contains(FROM_FIRST_MEMBER)) {
105            return tr("Flatten this relation to a single gpx track recursively, " +
106                    "starting with the first member, successively continuing to the last.");
107        } else {
108            return tr("Flatten this relation to a single gpx track recursively, " +
109                    "starting with the last member, successively continuing to the first.");
110        }
111    }
112
113    private static final class BidiIterableList {
114        private final List<RelationMember> l;
115
116        private BidiIterableList(List<RelationMember> l) {
117            this.l = l;
118        }
119
120        public Iterator<RelationMember> iterator() {
121            return l.iterator();
122        }
123
124        public Iterator<RelationMember> reverseIterator() {
125            ListIterator<RelationMember> li = l.listIterator(l.size());
126            return new Iterator<RelationMember>() {
127                @Override
128                public boolean hasNext() {
129                    return li.hasPrevious();
130                }
131
132                @Override
133                public RelationMember next() {
134                    return li.previous();
135                }
136
137                @Override
138                public void remove() {
139                    li.remove();
140                }
141            };
142        }
143    }
144
145    @Override
146    protected Layer getLayer() {
147        List<RelationMember> flat = new ArrayList<>();
148
149        List<RelationMember> init = new ArrayList<>();
150        relations.forEach(t -> init.add(new RelationMember("", t)));
151        BidiIterableList l = new BidiIterableList(init);
152
153        Stack<Iterator<RelationMember>> stack = new Stack<>();
154        stack.push(mode.contains(FROM_FIRST_MEMBER) ? l.iterator() : l.reverseIterator());
155
156        List<Relation> relsFound = new ArrayList<>();
157        do {
158            Iterator<RelationMember> i = stack.peek();
159            if (!i.hasNext())
160                stack.pop();
161            while (i.hasNext()) {
162                RelationMember m = i.next();
163                if (m.isRelation() && !m.getRelation().isIncomplete()) {
164                    l = new BidiIterableList(m.getRelation().getMembers());
165                    stack.push(mode.contains(FROM_FIRST_MEMBER) ? l.iterator() : l.reverseIterator());
166                    relsFound.add(m.getRelation());
167                    break;
168                }
169                if (m.isWay()) {
170                    flat.add(m);
171                }
172            }
173        } while (!stack.isEmpty());
174
175        GpxData gpxData = new GpxData();
176        String layerName = " (GPX export)";
177        long time = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) - 24*3600;
178
179        if (!flat.isEmpty()) {
180            Map<String, Object> trkAttr = new HashMap<>();
181            Collection<Collection<WayPoint>> trk = new ArrayList<>();
182            List<WayPoint> trkseg = new ArrayList<>();
183            trk.add(trkseg);
184
185            List<WayConnectionType> wct = new WayConnectionTypeCalculator().updateLinks(flat);
186            final HashMap<String, Integer> names = new HashMap<>();
187            for (int i = 0; i < flat.size(); i++) {
188                if (!wct.get(i).isOnewayLoopBackwardPart) {
189                    if (!wct.get(i).direction.isRoundabout()) {
190                        if (!wct.get(i).linkPrev && !trkseg.isEmpty()) {
191                            gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr));
192                            trkAttr.clear();
193                            trk.clear();
194                            trkseg.clear();
195                            trk.add(trkseg);
196                        }
197                        if (trkAttr.isEmpty()) {
198                            Relation r = Way.getParentRelations(Arrays.asList(flat.get(i).getWay()))
199                                    .stream().filter(relsFound::contains).findFirst().orElseGet(null);
200                            if (r != null) {
201                                trkAttr.put("name", r.getName() != null ? r.getName() : r.getId());
202                                trkAttr.put("desc", tr("based on osm route relation data, timestamps are synthetic"));
203                            }
204                            GpxData.ensureUniqueName(trkAttr, names);
205                        }
206                        List<Node> ln = flat.get(i).getWay().getNodes();
207                        if (wct.get(i).direction == WayConnectionType.Direction.BACKWARD)
208                            Collections.reverse(ln);
209                        for (Node n: ln) {
210                            trkseg.add(OsmDataLayer.nodeToWayPoint(n, TimeUnit.SECONDS.toMillis(time)));
211                            time += 1;
212                        }
213                    }
214                }
215            }
216            gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr));
217
218            String lprefix = relations.iterator().next().getName();
219            if (lprefix == null || relations.size() > 1)
220                lprefix = tr("Selected Relations");
221            layerName = lprefix + layerName;
222        }
223
224        return new GpxLayer(gpxData, layerName, true);
225    }
226
227    /**
228     *
229     * @param e the ActionEvent
230     */
231    @Override
232    public void actionPerformed(ActionEvent e) {
233        if (mode.contains(TO_LAYER))
234            MainApplication.getLayerManager().addLayer(getLayer());
235        if (mode.contains(TO_FILE))
236            super.actionPerformed(e);
237    }
238
239    @Override
240    public void setPrimitives(Collection<? extends IPrimitive> primitives) {
241        relations = Collections.<Relation>emptySet();
242        if (primitives != null && !primitives.isEmpty()) {
243            relations = new SubclassFilteredCollection<>(primitives,
244                r -> r instanceof Relation && r.hasTag("type", Arrays.asList("route", "superroute")));
245        }
246        updateEnabledState();
247    }
248
249    @Override
250    protected void updateEnabledState() {
251        setEnabled(!relations.isEmpty());
252    }
253}
Note: See TracBrowser for help on using the repository browser.