Changeset 13210 in josm
- Timestamp:
- 2017-12-17T15:37:11+01:00 (7 years ago)
- Location:
- trunk
- Files:
-
- 1 added
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/GpxExportAction.java
r12671 r13210 37 37 Shortcut.registerShortcut("file:exportgpx", tr("Export to GPX..."), KeyEvent.VK_E, Shortcut.CTRL)); 38 38 putValue("help", ht("/Action/GpxExport")); 39 } 40 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 13210 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); 39 57 } 40 58 -
trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java
r13136 r13210 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 … … 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; … … 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 … … 165 188 track.removeListener(proxy); 166 189 fireInvalidate(); 190 } 191 192 /** 193 * Combine tracks into a single, segmented track. 194 * The attributes of the first track are used, the rest discarded. 195 * 196 * @since 13210 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 13210 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 13210 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 13210 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 13210 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 13210 285 */ 286 public synchronized int getTrackSegsCount() { 287 return privateTracks.stream().collect(Collectors.summingInt(t -> t.getSegments().size())); 167 288 } 168 289 -
trunk/src/org/openstreetmap/josm/data/gpx/ImmutableGpxTrack.java
r12289 r13210 35 35 this.attr = Collections.unmodifiableMap(new HashMap<>(attributes)); 36 36 this.segments = Collections.unmodifiableList(newSegments); 37 this.length = calculateLength(); 38 this.bounds = calculateBounds(); 39 } 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 * @since 13210 49 */ 50 public ImmutableGpxTrack(List<GpxTrackSegment> segments, Map<String, Object> attributes) { 51 this.attr = Collections.unmodifiableMap(new HashMap<>(attributes)); 52 this.segments = Collections.unmodifiableList(segments); 37 53 this.length = calculateLength(); 38 54 this.bounds = calculateBounds(); -
trunk/src/org/openstreetmap/josm/data/gpx/WayPoint.java
r12656 r13210 143 143 public void setTime() { 144 144 setTimeFromAttribute(); 145 } 146 147 /** 148 * Set the the time stamp of the waypoint into seconds from the epoch, 149 * @param time millisecond from the epoch 150 * @since 13210 151 */ 152 public void setTime(long time) { 153 this.time = time / 1000.; 145 154 } 146 155 -
trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
r12846 r13210 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; … … 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; … … 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; … … 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); … … 603 619 604 620 private void setupPopupMenuHandler() { 621 List<JMenuItem> checkDisabled = new ArrayList<>(); 605 622 606 623 // -- select action … … 612 629 popupMenuHandler.addAction(addMembersToSelectionAction); 613 630 631 // -- download members action 614 632 popupMenuHandler.addSeparator(); 615 // -- download members action616 633 popupMenuHandler.addAction(downloadMembersAction); 617 618 // -- download incomplete members action619 634 popupMenuHandler.addAction(downloadSelectedIncompleteMembersAction); 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)); 620 643 621 644 popupMenuHandler.addSeparator(); … … 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 -
trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
r12987 r13210 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; 13 15 import java.util.List; 16 17 import javax.swing.AbstractAction; 14 18 import javax.swing.Action; 15 19 import javax.swing.Icon; … … 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; … … 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 … … 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 … … 97 104 setName(name); 98 105 isLocalFile = isLocal; 106 ExpertToggleAction.addExpertModeChangeListener(this, true); 99 107 } 100 108 … … 139 147 @Override 140 148 public Object getInfoComponent() { 141 StringBuilder info = new StringBuilder(48).append("<html>"); 149 StringBuilder info = new StringBuilder(128) 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")) { … … 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 … … 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")); … … 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())); … … 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(), … … 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 … … 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>") … … 330 363 return new GpxDrawHelper(this); 331 364 } 365 366 /** 367 * Action to merge tracks into a single segmented track 368 * 369 * @since 13210 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 13210 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 13210 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 } 456 457 @Override 458 public boolean isEnabled() { 459 return layer.data.getTrackCount() > 1; 460 } 461 } 462 463 @Override 464 public void expertChanged(boolean isExpert) { 465 this.isExpertMode = isExpert; 466 } 332 467 } -
trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
r13206 r13210 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 * @since 13210 746 */ 747 public static WayPoint nodeToWayPoint(Node n) { 748 return nodeToWayPoint(n, 0); 749 } 750 751 /** 752 * @param n the {@code Node} to convert 753 * @param time a time value in milliseconds from the epoch. 754 * @return {@code WayPoint} object 755 * @since 13210 756 */ 757 public static WayPoint nodeToWayPoint(Node n, long time) { 743 758 WayPoint wpt = new WayPoint(n.getCoor()); 744 759 … … 747 762 addDoubleIfPresent(wpt, n, GpxConstants.PT_ELE); 748 763 749 if (!n.isTimestampEmpty()) { 764 if (time > 0) { 765 wpt.setTime(time); 766 } else if (!n.isTimestampEmpty()) { 750 767 wpt.put("time", DateUtils.fromTimestamp(n.getRawTimestamp())); 751 768 wpt.setTime(); -
trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java
r13079 r13210 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; … … 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
Note:
See TracChangeset
for help on using the changeset viewer.