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

Last change on this file since 13664 was 13292, checked in by Klumbumbus, 6 years ago

see #15606 - adjust mouse over hint text

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