Ticket #15606: josm-export-route-relations-to-GPX-file_or_convert-to-GPX-layer.patch
| File josm-export-route-relations-to-GPX-file_or_convert-to-GPX-layer.patch, 36.7 KB (added by , 9 years ago) |
|---|
-
src/org/openstreetmap/josm/actions/GpxExportAction.java
39 39 } 40 40 41 41 /** 42 * Deferring constructor for child classes. 43 * 44 * @param name see {@code DiskAccessAction} 45 * @param iconName see {@code DiskAccessAction} 46 * @param tooltip see {@code DiskAccessAction} 47 * @param shortcut see {@code DiskAccessAction} 48 * @param register see {@code DiskAccessAction} 49 * @param toolbarId see {@code DiskAccessAction} 50 * @param installAdapters see {@code DiskAccessAction} 51 * 52 * @since xxx 53 */ 54 protected GpxExportAction(String name, String iconName, String tooltip, Shortcut shortcut, 55 boolean register, String toolbarId, boolean installAdapters) { 56 super(name, iconName, tooltip, shortcut, register, toolbarId, installAdapters); 57 } 58 59 /** 42 60 * Get the layer to export. 43 61 * @return The layer to export, either a {@link GpxLayer} or {@link OsmDataLayer}. 44 62 */ -
src/org/openstreetmap/josm/actions/relation/ExportRelationToGpxAction.java
1 // License: GPL. 2 package org.openstreetmap.josm.actions.relation; 3 4 import static org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode.FROM_FIRST_MEMBER; 5 import static org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode.TO_FILE; 6 import static org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode.TO_LAYER; 7 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 8 import static org.openstreetmap.josm.tools.I18n.tr; 9 10 import java.awt.event.ActionEvent; 11 import java.util.ArrayList; 12 import java.util.Arrays; 13 import java.util.Collection; 14 import java.util.Collections; 15 import java.util.EnumSet; 16 import java.util.HashMap; 17 import java.util.Iterator; 18 import java.util.List; 19 import java.util.ListIterator; 20 import java.util.Map; 21 import java.util.Stack; 22 23 import org.openstreetmap.josm.actions.GpxExportAction; 24 import org.openstreetmap.josm.actions.OsmPrimitiveAction; 25 import org.openstreetmap.josm.data.gpx.GpxData; 26 import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack; 27 import org.openstreetmap.josm.data.gpx.WayPoint; 28 import org.openstreetmap.josm.data.osm.Node; 29 import org.openstreetmap.josm.data.osm.OsmPrimitive; 30 import org.openstreetmap.josm.data.osm.Relation; 31 import org.openstreetmap.josm.data.osm.RelationMember; 32 import org.openstreetmap.josm.data.osm.Way; 33 import org.openstreetmap.josm.gui.MainApplication; 34 import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType; 35 import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator; 36 import org.openstreetmap.josm.gui.layer.GpxLayer; 37 import org.openstreetmap.josm.gui.layer.Layer; 38 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 39 import 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 xxx 46 */ 47 public 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(tr("{0} starting from {1} member", 80 mode.contains(TO_FILE) ? tr("Export GPX file") : tr("Convert to GPX layer"), 81 mode.contains(FROM_FIRST_MEMBER) ? tr("first") : tr("last")), 82 mode.contains(TO_FILE) ? "exportgpx" : "dialogs/layerlist", 83 tr("Flatten this relation to a single gpx track "+ 84 "recursively, starting with the {0} member(s), "+ 85 "successively continuing to the {1}.", 86 mode.contains(FROM_FIRST_MEMBER) ? tr("first") : tr("last"), 87 mode.contains(FROM_FIRST_MEMBER) ? tr("last") : tr("first")), 88 null, false, null, false); 89 putValue("help", ht("/Action/ExportRelationToGpx")); 90 this.mode = mode; 91 } 92 93 private static class BidiIterableList { 94 private final List<RelationMember> l; 95 96 private BidiIterableList(List<RelationMember> l) { 97 this.l = l; 98 } 99 100 public Iterator<RelationMember> iterator() { 101 return l.iterator(); 102 } 103 104 public Iterator<RelationMember> reverseIterator() { 105 ListIterator<RelationMember> li = l.listIterator(l.size()); 106 return new Iterator<RelationMember>() { 107 @Override 108 public boolean hasNext() { 109 return li.hasPrevious(); 110 } 111 112 @Override 113 public RelationMember next() { 114 return li.previous(); 115 } 116 117 @Override 118 public void remove() { 119 li.remove(); 120 } 121 }; 122 } 123 } 124 125 @Override 126 protected Layer getLayer() { 127 List<RelationMember> flat = new ArrayList<>(); 128 129 List<RelationMember> init = new ArrayList<>(); 130 relations.forEach(t -> init.add(new RelationMember("", t))); 131 BidiIterableList l = new BidiIterableList(init); 132 133 Stack<Iterator<RelationMember>> stack = new Stack<>(); 134 stack.push(mode.contains(FROM_FIRST_MEMBER) ? l.iterator() : l.reverseIterator()); 135 136 List<Relation> relsFound = new ArrayList<>(); 137 do { 138 Iterator<RelationMember> i = stack.peek(); 139 if (!i.hasNext()) 140 stack.pop(); 141 while (i.hasNext()) { 142 RelationMember m = i.next(); 143 if (m.isRelation() && !m.getRelation().isIncomplete()) { 144 l = new BidiIterableList(m.getRelation().getMembers()); 145 stack.push(mode.contains(FROM_FIRST_MEMBER) ? l.iterator() : l.reverseIterator()); 146 relsFound.add(m.getRelation()); 147 break; 148 } 149 if (m.isWay()) { 150 flat.add(m); 151 } 152 } 153 } while (!stack.isEmpty()); 154 155 GpxData gpxData = new GpxData(); 156 String layerName = " (GPX export)"; 157 long time = System.currentTimeMillis()-24*3600*1000; 158 159 if (!flat.isEmpty()) { 160 Map<String, Object> trkAttr = new HashMap<>(); 161 Collection<Collection<WayPoint>> trk = new ArrayList<>(); 162 List<WayPoint> trkseg = new ArrayList<>(); 163 trk.add(trkseg); 164 165 //RelationSorter.sortMembersByConnectivity(defaultMembers); // preserve given order 166 List<WayConnectionType> wct = new WayConnectionTypeCalculator().updateLinks(flat); 167 final HashMap<String, Integer> names = new HashMap<>(); 168 for (int i=0; i<flat.size(); i++) { 169 if (!wct.get(i).isOnewayLoopBackwardPart) { 170 if (!wct.get(i).direction.isRoundabout()) { 171 if (!wct.get(i).linkPrev && trkseg.size() > 0) { 172 gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr)); 173 trkAttr.clear(); 174 trk.clear(); 175 trkseg.clear(); 176 trk.add(trkseg); 177 } 178 if (trkAttr.isEmpty()/* && (!wct.get(i).linkNext || (i+1 == flat.size()))*/) { 179 Relation r = Way.getParentRelations(Arrays.asList(flat.get(i).getWay())) 180 .stream().filter(relsFound::contains).findFirst().orElseGet(null); 181 if (r != null) 182 trkAttr.put("name", r.getName()!=null ? r.getName() : r.getId()); 183 GpxData.ensureUniqueName(trkAttr, names); 184 } 185 List<Node> ln = flat.get(i).getWay().getNodes(); 186 if (wct.get(i).direction == WayConnectionType.Direction.BACKWARD) 187 Collections.reverse(ln); 188 for (Node n: ln) { 189 trkseg.add(OsmDataLayer.nodeToWayPoint(n,time)); 190 time += 1000; 191 } 192 } 193 } 194 } 195 gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr)); 196 197 String lprefix = relations.iterator().next().getName(); 198 if (lprefix==null || relations.size()>1) 199 lprefix = tr("Selected Relations"); 200 layerName = lprefix + layerName; 201 } 202 203 //Relation debug = new Relation(); debug.setMembers(flat); 204 //MainApplication.getLayerManager().getEditDataSet().addPrimitive(debug); 205 return new GpxLayer(gpxData, layerName, true); 206 } 207 208 /** 209 * 210 * @param e the ActionEvent 211 */ 212 @Override 213 public void actionPerformed(ActionEvent e) { 214 if(mode.contains(TO_LAYER)) 215 MainApplication.getLayerManager().addLayer(getLayer()); 216 if(mode.contains(TO_FILE)) 217 super.actionPerformed(e); 218 } 219 220 @Override 221 public void setPrimitives(Collection<? extends OsmPrimitive> primitives) { 222 relations = Collections.<Relation>emptySet(); 223 if (primitives != null && !primitives.isEmpty()) { 224 relations = new SubclassFilteredCollection<>(primitives, 225 r -> r instanceof Relation && r.hasTag("type", Arrays.asList("route", "superroute"))); 226 } 227 updateEnabledState(); 228 } 229 230 @Override 231 protected void updateEnabledState() { 232 setEnabled(!relations.isEmpty()); 233 } 234 } -
src/org/openstreetmap/josm/data/gpx/GpxData.java
4 4 import java.io.File; 5 5 import java.text.MessageFormat; 6 6 import java.util.ArrayList; 7 import java.util.Arrays; 7 8 import java.util.Collection; 8 9 import java.util.Collections; 9 10 import java.util.Date; 10 11 import java.util.DoubleSummaryStatistics; 12 import java.util.HashMap; 11 13 import java.util.HashSet; 12 14 import java.util.Iterator; 15 import java.util.List; 13 16 import java.util.Map; 14 17 import java.util.NoSuchElementException; 15 18 import java.util.Set; 19 import java.util.stream.Collectors; 16 20 import java.util.stream.Stream; 17 21 18 22 import org.openstreetmap.josm.Main; … … 21 25 import org.openstreetmap.josm.data.DataSource; 22 26 import org.openstreetmap.josm.data.coor.EastNorth; 23 27 import org.openstreetmap.josm.data.gpx.GpxTrack.GpxTrackChangeListener; 28 import org.openstreetmap.josm.gui.MainApplication; 29 import org.openstreetmap.josm.gui.layer.GpxLayer; 24 30 import org.openstreetmap.josm.tools.ListenerList; 25 31 import org.openstreetmap.josm.tools.ListeningCollection; 26 32 … … 140 146 } 141 147 142 148 /** 149 * Get Stream<> of track segments as introduced in Java 8. 150 * @return {@code Stream<GPXTrack>} 151 */ 152 private synchronized Stream<GpxTrackSegment> getTrackSegmentsStream() { 153 return getTracks().stream().flatMap(trk -> trk.getSegments().stream()); 154 } 155 156 /** 157 * Clear all tracks, empties the current privateTracks container, 158 * helper method for some gpx manipulations. 159 */ 160 private synchronized void clearTracks() { 161 privateTracks.forEach(t -> { t.removeListener(proxy); }); 162 privateTracks.clear(); 163 } 164 165 /** 143 166 * Add a new track 144 167 * @param track The new track 145 168 * @since 12156 … … 167 190 } 168 191 169 192 /** 193 * Combine tracks into a single, segmented track. 194 * The attributes of the first track are used, the rest discarded. 195 * 196 * @since xxx 197 */ 198 public synchronized void combineTracksToSegmentedTrack() { 199 List<GpxTrackSegment> segs = getTrackSegmentsStream() 200 .collect(Collectors.toCollection(ArrayList<GpxTrackSegment>::new)); 201 Map<String, Object> attrs = new HashMap<>(privateTracks.get(0).getAttributes()); 202 203 // do not let the name grow if split / combine operations are called iteratively 204 attrs.put("name", attrs.get("name").toString().replaceFirst(" #\\d+$", "")); 205 206 clearTracks(); 207 addTrack(new ImmutableGpxTrack(segs, attrs)); 208 } 209 210 /** 211 * @param attrs attributes of/for an gpx track, written to if the name appeared previously in {@code counts}. 212 * @param counts a {@code HashMap} of previously seen names, associated with their count. 213 * @return the unique name for the gpx track. 214 * 215 * @since xxx 216 */ 217 public static String ensureUniqueName(Map<String, Object> attrs, Map<String, Integer> counts) { 218 String name = attrs.getOrDefault("name", "GPX split result").toString(); 219 Integer count = counts.getOrDefault(name, 0) + 1; 220 counts.put(name, count); 221 222 attrs.put("name", MessageFormat.format("{0}{1}", name, (count>1) ? " #"+count : "")); 223 return attrs.get("name").toString(); 224 } 225 226 /** 227 * Split tracks so that only single-segment tracks remain. 228 * Each segment will make up one individual track after this operation. 229 * 230 * @since xxx 231 */ 232 public synchronized void splitTrackSegmentsToTracks() { 233 final HashMap<String, Integer> counts = new HashMap<>(); 234 235 List<GpxTrack> trks = getTracks().stream() 236 .flatMap(trk -> { 237 return trk.getSegments().stream().map(seg -> { 238 HashMap<String, Object> attrs = new HashMap<>(trk.getAttributes()); 239 ensureUniqueName(attrs, counts); 240 return new ImmutableGpxTrack(Arrays.asList(seg), attrs); 241 }); 242 }) 243 .collect(Collectors.toCollection(ArrayList<GpxTrack>::new)); 244 245 clearTracks(); 246 trks.stream().forEachOrdered(trk -> addTrack(trk)); 247 } 248 249 /** 250 * Split tracks into layers, the result is one layer for each track. 251 * If this layer currently has only one GpxTrack this is a no-operation. 252 * 253 * The new GpxLayers are added to the LayerManager, the original GpxLayer 254 * is untouched as to preserve potential route or wpt parts. 255 * 256 * @since xxx 257 */ 258 public synchronized void splitTracksToLayers() { 259 final HashMap<String, Integer> counts = new HashMap<>(); 260 261 getTracks().stream() 262 .filter(trk -> privateTracks.size() > 1) 263 .map(trk -> { 264 HashMap<String, Object> attrs = new HashMap<>(trk.getAttributes()); 265 GpxData d = new GpxData(); 266 d.addTrack(trk); 267 return new GpxLayer(d, ensureUniqueName(attrs, counts)); }) 268 .forEachOrdered(layer -> MainApplication.getLayerManager().addLayer(layer)); 269 } 270 271 /** 272 * Replies the current number of tracks in this GpxData 273 * @return track count 274 * @since xxx 275 */ 276 public synchronized int getTrackCount() { 277 return privateTracks.size(); 278 } 279 280 /** 281 * Replies the accumulated total of all track segments, 282 * the sum of segment counts for each track present. 283 * @return track segments count 284 * @since xxx 285 */ 286 public synchronized int getTrackSegsCount() { 287 return privateTracks.stream().collect(Collectors.summingInt(t -> t.getSegments().size())); 288 } 289 290 /** 170 291 * Gets the list of all routes defined in this data set. 171 292 * @return The routes 172 293 * @since 12156 -
src/org/openstreetmap/josm/data/gpx/ImmutableGpxTrack.java
38 38 this.bounds = calculateBounds(); 39 39 } 40 40 41 /** 42 * Constructs a new {@code ImmutableGpxTrack} from {@code GpxTrackSegment} objects. 43 * @param segments The segments to build the track from. Input is not deep-copied, 44 * which means the caller may reuse the same segments to build 45 * multiple ImmutableGpxTrack instances from. This should not be 46 * a problem, since this object cannot modify {@code this.segments}. 47 * @param attributes Attributes for the GpxTrack, the input map is copied. 48 */ 49 public ImmutableGpxTrack(List<GpxTrackSegment> segments, Map<String, Object> attributes) { 50 this.attr = Collections.unmodifiableMap(new HashMap<>(attributes)); 51 this.segments = Collections.unmodifiableList(segments); 52 this.length = calculateLength(); 53 this.bounds = calculateBounds(); 54 } 55 41 56 private double calculateLength() { 42 57 double result = 0.0; // in meters 43 58 -
src/org/openstreetmap/josm/data/gpx/WayPoint.java
145 145 } 146 146 147 147 /** 148 * Set the the time stamp of the waypoint into seconds from the epoch, 149 * @param time millisecond from the epoch 150 */ 151 public void setTime(long time) { 152 this.time = time / 1000.; 153 } 154 155 /** 148 156 * Convert the time stamp of the waypoint into seconds from the epoch 149 157 * @return The parsed time if successful, or {@code null} 150 158 * @since 9383 -
src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
12 12 import java.util.Arrays; 13 13 import java.util.Collection; 14 14 import java.util.Collections; 15 import java.util.EnumSet; 15 16 import java.util.HashSet; 16 17 import java.util.List; 17 18 import java.util.Set; … … 28 29 import javax.swing.JScrollPane; 29 30 import javax.swing.KeyStroke; 30 31 import javax.swing.ListSelectionModel; 32 import javax.swing.event.PopupMenuEvent; 33 import javax.swing.event.PopupMenuListener; 31 34 32 35 import org.openstreetmap.josm.Main; 33 36 import org.openstreetmap.josm.actions.ExpertToggleAction; 37 import org.openstreetmap.josm.actions.OsmPrimitiveAction; 34 38 import org.openstreetmap.josm.actions.relation.AddSelectionToRelations; 35 39 import org.openstreetmap.josm.actions.relation.DeleteRelationsAction; 36 40 import org.openstreetmap.josm.actions.relation.DownloadMembersAction; 37 41 import org.openstreetmap.josm.actions.relation.DownloadSelectedIncompleteMembersAction; 38 42 import org.openstreetmap.josm.actions.relation.DuplicateRelationAction; 39 43 import org.openstreetmap.josm.actions.relation.EditRelationAction; 44 import org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction; 45 import org.openstreetmap.josm.actions.relation.ExportRelationToGpxAction.Mode; 40 46 import org.openstreetmap.josm.actions.relation.RecentRelationsAction; 41 47 import org.openstreetmap.josm.actions.relation.SelectMembersAction; 42 48 import org.openstreetmap.josm.actions.relation.SelectRelationAction; … … 121 127 private final AddSelectionToRelations addSelectionToRelations = new AddSelectionToRelations(); 122 128 private transient JMenuItem addSelectionToRelationMenuItem; 123 129 130 /** export relation to GPX track action */ 131 private final ExportRelationToGpxAction exportRelationFromFirstAction = 132 new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_FIRST_MEMBER, Mode.TO_FILE)); 133 private final ExportRelationToGpxAction exportRelationFromLastAction = 134 new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_LAST_MEMBER, Mode.TO_FILE)); 135 private final ExportRelationToGpxAction exportRelationFromFirstToLayerAction = 136 new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_FIRST_MEMBER, Mode.TO_LAYER)); 137 private final ExportRelationToGpxAction exportRelationFromLastToLayerAction = 138 new ExportRelationToGpxAction(EnumSet.of(Mode.FROM_LAST_MEMBER, Mode.TO_LAYER)); 139 124 140 private final transient HighlightHelper highlightHelper = new HighlightHelper(); 125 141 private final boolean highlightEnabled = Config.getPref().getBoolean("draw.target-highlight", true); 126 142 private final transient RecentRelationsAction recentRelationsAction; … … 602 618 } 603 619 604 620 private void setupPopupMenuHandler() { 621 List<JMenuItem> checkDisabled = new ArrayList<>(); 605 622 606 623 // -- select action 607 624 popupMenuHandler.addAction(selectRelationAction); … … 611 628 popupMenuHandler.addAction(selectMembersAction); 612 629 popupMenuHandler.addAction(addMembersToSelectionAction); 613 630 614 popupMenuHandler.addSeparator();615 631 // -- download members action 632 popupMenuHandler.addSeparator(); 616 633 popupMenuHandler.addAction(downloadMembersAction); 617 618 // -- download incomplete members action619 634 popupMenuHandler.addAction(downloadSelectedIncompleteMembersAction); 620 635 636 // -- export relation to gpx action 637 popupMenuHandler.addSeparator(); 638 checkDisabled.add(popupMenuHandler.addAction(exportRelationFromFirstAction)); 639 checkDisabled.add(popupMenuHandler.addAction(exportRelationFromLastAction)); 640 popupMenuHandler.addSeparator(); 641 checkDisabled.add(popupMenuHandler.addAction(exportRelationFromFirstToLayerAction)); 642 checkDisabled.add(popupMenuHandler.addAction(exportRelationFromLastToLayerAction)); 643 621 644 popupMenuHandler.addSeparator(); 622 645 popupMenuHandler.addAction(editAction).setVisible(false); 623 646 popupMenuHandler.addAction(duplicateAction).setVisible(false); 624 647 popupMenuHandler.addAction(deleteRelationsAction).setVisible(false); 625 648 626 649 addSelectionToRelationMenuItem = popupMenuHandler.addAction(addSelectionToRelations); 650 651 popupMenuHandler.addListener(new PopupMenuListener() { 652 @Override 653 public void popupMenuWillBecomeVisible(PopupMenuEvent e) { 654 for (JMenuItem mi: checkDisabled) { 655 mi.setVisible(((OsmPrimitiveAction) mi.getAction()).isEnabled()); 656 657 Component sep = popupMenu.getComponent( 658 Math.max(0, popupMenu.getComponentIndex(mi)-1)); 659 if (!(sep instanceof JMenuItem)) { 660 sep.setVisible(mi.isVisible()); 661 } 662 } 663 } 664 665 @Override 666 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { 667 } 668 669 @Override 670 public void popupMenuCanceled(PopupMenuEvent e) { 671 } 672 }); 627 673 } 628 674 629 675 /* ---------------------------------------------------------------------------------- */ -
src/org/openstreetmap/josm/gui/layer/GpxLayer.java
6 6 7 7 import java.awt.Dimension; 8 8 import java.awt.Graphics2D; 9 import java.awt.event.ActionEvent; 9 10 import java.io.File; 10 11 import java.text.DateFormat; 12 import java.util.ArrayList; 11 13 import java.util.Arrays; 12 14 import java.util.Date; 15 import java.util.List; 13 16 17 import javax.swing.AbstractAction; 14 18 import javax.swing.Action; 15 19 import javax.swing.Icon; 16 20 import javax.swing.JScrollPane; 17 21 import javax.swing.SwingUtilities; 18 22 23 import org.openstreetmap.josm.actions.ExpertToggleAction; 24 import org.openstreetmap.josm.actions.ExpertToggleAction.ExpertModeChangeListener; 19 25 import org.openstreetmap.josm.actions.RenameLayerAction; 20 26 import org.openstreetmap.josm.actions.SaveActionBase; 21 27 import org.openstreetmap.josm.data.Bounds; … … 47 53 /** 48 54 * A layer that displays data from a Gpx file / the OSM gpx downloads. 49 55 */ 50 public class GpxLayer extends Layer {56 public class GpxLayer extends Layer implements ExpertModeChangeListener { 51 57 52 58 /** GPX data */ 53 59 public GpxData data; 54 60 private final boolean isLocalFile; 61 private boolean isExpertMode; 55 62 /** 56 63 * used by {@link ChooseTrackVisibilityAction} to determine which tracks to show/hide 57 64 * … … 83 90 } 84 91 85 92 /** 86 * Constructs a new {@code GpxLayer} with a given name, tha hcan be attached to a local file.93 * Constructs a new {@code GpxLayer} with a given name, that can be attached to a local file. 87 94 * @param d GPX data 88 95 * @param name layer name 89 96 * @param isLocal whether data is attached to a local file … … 96 103 Arrays.fill(trackVisibility, true); 97 104 setName(name); 98 105 isLocalFile = isLocal; 106 ExpertToggleAction.addExpertModeChangeListener(this, true); 99 107 } 100 108 101 109 @Override … … 138 146 139 147 @Override 140 148 public Object getInfoComponent() { 141 StringBuilder info = new StringBuilder(48).append("<html>"); 149 StringBuilder info = new StringBuilder(48) 150 .append("<html><head><style>") 151 .append("td { padding: 4px 16px; }") 152 .append("</style></head><body>"); 142 153 143 154 if (data.attr.containsKey("name")) { 144 155 info.append(tr("Name: {0}", data.get(GpxConstants.META_NAME))).append("<br>"); … … 150 161 151 162 if (!data.getTracks().isEmpty()) { 152 163 info.append("<table><thead align='center'><tr><td colspan='5'>") 153 .append(trn("{0} track", "{0} tracks", data.tracks.size(), data.tracks.size())) 154 .append("</td></tr><tr align='center'><td>").append(tr("Name")).append("</td><td>") 155 .append(tr("Description")).append("</td><td>").append(tr("Timespan")) 156 .append("</td><td>").append(tr("Length")).append("</td><td>").append(tr("URL")) 164 .append(trn("{0} track, {1} track segments", "{0} tracks, {1} track segments", 165 data.getTrackCount(), data.getTrackCount(), 166 data.getTrackSegsCount(), data.getTrackSegsCount())) 167 .append("</td></tr><tr align='center'><td>").append(tr("Name")) 168 .append("</td><td>").append(tr("Description")) 169 .append("</td><td>").append(tr("Timespan")) 170 .append("</td><td>").append(tr("Length")) 171 .append("</td><td>").append(tr("Number of<br/>Segments")) 172 .append("</td><td>").append(tr("URL")) 157 173 .append("</td></tr></thead>"); 158 174 159 175 for (GpxTrack trk : data.getTracks()) { … … 170 186 info.append("</td><td>"); 171 187 info.append(SystemOfMeasurement.getSystemOfMeasurement().getDistText(trk.length())); 172 188 info.append("</td><td>"); 189 info.append(trk.getSegments().size()); 190 info.append("</td><td>"); 173 191 if (trk.getAttributes().containsKey("url")) { 174 192 info.append(trk.get("url")); 175 193 } … … 180 198 181 199 info.append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length()))).append("<br>") 182 200 .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size())) 183 .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size())).append("<br></html>"); 201 .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size())) 202 .append("<br></body></html>"); 184 203 185 204 final JScrollPane sp = new JScrollPane(new HtmlPanel(info.toString())); 186 205 sp.setPreferredSize(new Dimension(sp.getPreferredSize().width+20, 370)); … … 195 214 196 215 @Override 197 216 public Action[] getMenuEntries() { 198 return new Action[] {217 List<Action> entries = new ArrayList<>(Arrays.asList( 199 218 LayerListDialog.getInstance().createShowHideLayerAction(), 200 219 LayerListDialog.getInstance().createDeleteLayerAction(), 201 220 LayerListDialog.getInstance().createMergeLayerAction(this), … … 212 231 new DownloadWmsAlongTrackAction(data), 213 232 SeparatorLayerAction.INSTANCE, 214 233 new ChooseTrackVisibilityAction(this), 215 new RenameLayerAction(getAssociatedFile(), this), 216 SeparatorLayerAction.INSTANCE, 217 new LayerListPopup.InfoAction(this) }; 234 new RenameLayerAction(getAssociatedFile(), this))); 235 236 List<Action> expert = Arrays.asList( 237 new CombineTracksToSegmentedTrackAction(this), 238 new SplitTrackSegementsToTracksAction(this), 239 new SplitTracksToLayersAction(this)); 240 241 if (isExpertMode && expert.stream().anyMatch(t -> t.isEnabled())) { 242 entries.add(SeparatorLayerAction.INSTANCE); 243 expert.stream().filter(t -> t.isEnabled()).forEach(t -> entries.add(t)); 244 } 245 246 entries.add(SeparatorLayerAction.INSTANCE); 247 entries.add(new LayerListPopup.InfoAction(this)); 248 return entries.toArray(new Action[0]); 218 249 } 219 250 220 251 /** … … 237 268 info.append(tr("Description: {0}", data.get(GpxConstants.META_DESC))).append("<br>"); 238 269 } 239 270 240 info.append(trn("{0} track, ", "{0} tracks, ", data.getTracks().size(), data.getTracks().size())) 271 info.append(trn("{0} track", "{0} tracks", data.getTrackCount(), data.getTrackCount())) 272 .append(trn(" ({0} segment)", " ({0} segments)", data.getTrackSegsCount(), data.getTrackSegsCount())) 273 .append(", ") 241 274 .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size())) 242 275 .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size())).append("<br>") 243 276 .append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length()))) … … 329 362 protected LayerPainter createMapViewPainter(MapViewEvent event) { 330 363 return new GpxDrawHelper(this); 331 364 } 365 366 /** 367 * Action to merge tracks into a single segmented track 368 * 369 * @since xxx 370 */ 371 public static class CombineTracksToSegmentedTrackAction extends AbstractAction { 372 private final transient GpxLayer layer; 373 374 /** 375 * Create a new CombineTracksToSegmentedTrackAction 376 * @param layer The layer with the data to work on. 377 */ 378 public CombineTracksToSegmentedTrackAction(GpxLayer layer) { 379 // FIXME: icon missing, create a new icon for this action 380 //new ImageProvider("gpx_tracks_to_segmented_track").getResource().attachImageIcon(this, true); 381 putValue(SHORT_DESCRIPTION, tr("Collect segments of all tracks and combine in a single track.")); 382 putValue(NAME, tr("Combine tracks of this layer")); 383 this.layer = layer; 384 } 385 386 @Override 387 public void actionPerformed(ActionEvent e) { 388 layer.data.combineTracksToSegmentedTrack(); 389 layer.invalidate(); 390 } 391 392 @Override 393 public boolean isEnabled() { 394 return layer.data.getTrackCount() > 1; 395 } 396 } 397 398 /** 399 * Action to split track segments into a multiple tracks with one segment each 400 * 401 * @since xxx 402 */ 403 public static class SplitTrackSegementsToTracksAction extends AbstractAction { 404 private final transient GpxLayer layer; 405 406 /** 407 * Create a new SplitTrackSegementsToTracksAction 408 * @param layer The layer with the data to work on. 409 */ 410 public SplitTrackSegementsToTracksAction(GpxLayer layer) { 411 // FIXME: icon missing, create a new icon for this action 412 //new ImageProvider("gpx_segmented_track_to_tracks").getResource().attachImageIcon(this, true); 413 putValue(SHORT_DESCRIPTION, tr("Split multiple track segments of one track into multiple tracks.")); 414 putValue(NAME, tr("Split track segments to tracks")); 415 this.layer = layer; 416 } 417 418 @Override 419 public void actionPerformed(ActionEvent e) { 420 layer.data.splitTrackSegmentsToTracks(); 421 layer.invalidate(); 422 } 423 424 @Override 425 public boolean isEnabled() { 426 return layer.data.getTrackSegsCount() > layer.data.getTrackCount(); 427 } 428 } 429 430 /** 431 * Action to split tracks of one gpx layer into multiple gpx layers, 432 * the result is one GPX track per gpx layer. 433 * 434 * @since xxx 435 */ 436 public static class SplitTracksToLayersAction extends AbstractAction { 437 private final transient GpxLayer layer; 438 439 /** 440 * Create a new SplitTrackSegementsToTracksAction 441 * @param layer The layer with the data to work on. 442 */ 443 public SplitTracksToLayersAction(GpxLayer layer) { 444 // FIXME: icon missing, create a new icon for this action 445 //new ImageProvider("gpx_split_tracks_to_layers").getResource().attachImageIcon(this, true); 446 putValue(SHORT_DESCRIPTION, tr("Split the tracks of this layer to one new layer each.")); 447 putValue(NAME, tr("Split tracks to new layers")); 448 this.layer = layer; 449 } 450 451 @Override 452 public void actionPerformed(ActionEvent e) { 453 layer.data.splitTracksToLayers(); 454 // layer is not modified by this action 455 //layer.invalidate(); 456 } 457 458 @Override 459 public boolean isEnabled() { 460 return layer.data.getTrackCount() > 1; 461 } 462 } 463 464 @Override 465 public void expertChanged(boolean isExpert) { 466 this.isExpertMode = isExpert; 467 } 332 468 } -
src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
739 739 }); 740 740 } 741 741 742 private static WayPoint nodeToWayPoint(Node n) { 742 /** 743 * @param n the {@code Node} to convert 744 * @return {@code WayPoint} object 745 */ 746 public static WayPoint nodeToWayPoint(Node n) { 747 return nodeToWayPoint(n, 0); 748 } 749 750 /** 751 * @param n the {@code Node} to convert 752 * @param time a time value in milliseconds from the epoch. 753 * @return {@code WayPoint} object 754 */ 755 public static WayPoint nodeToWayPoint(Node n, long time) { 743 756 WayPoint wpt = new WayPoint(n.getCoor()); 744 757 745 758 // Position info 746 759 747 760 addDoubleIfPresent(wpt, n, GpxConstants.PT_ELE); 748 761 749 if (!n.isTimestampEmpty()) { 762 if (time > 0) { 763 wpt.setTime(time); 764 } else if (!n.isTimestampEmpty()) { 750 765 wpt.put("time", DateUtils.fromTimestamp(n.getRawTimestamp())); 751 766 wpt.setTime(); 752 767 } -
test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java
9 9 10 10 import java.util.ArrayList; 11 11 import java.util.Arrays; 12 import java.util.Collection; 12 13 import java.util.Collections; 13 14 import java.util.Date; 14 15 import java.util.List; … … 423 424 } 424 425 425 426 private static ImmutableGpxTrack emptyGpxTrack() { 426 return new ImmutableGpxTrack(Collections. emptyList(), Collections.emptyMap());427 return new ImmutableGpxTrack(Collections.<Collection<WayPoint>>emptyList(), Collections.emptyMap()); 427 428 } 428 429 429 430 private static ImmutableGpxTrack singleWaypointGpxTrack() {
