Ticket #16755: MergeGPXV1.diff
| File MergeGPXV1.diff, 54.5 KB (added by , 8 years ago) |
|---|
-
src/org/openstreetmap/josm/actions/AbstractMergeAction.java
9 9 import java.util.List; 10 10 11 11 import javax.swing.DefaultListCellRenderer; 12 import javax.swing.JCheckBox; 12 13 import javax.swing.JLabel; 13 14 import javax.swing.JList; 14 15 import javax.swing.JOptionPane; … … 44 45 } 45 46 46 47 /** 48 * <code>TargetLayerDialogResult</code> returned by {@link #askTargetLayer(List, String, boolean)} containing the selectedTargetLayer and whether the checkbox is ticked 49 * @param <T> type of layer 50 */ 51 public static class TargetLayerDialogResult<T extends Layer> { 52 /** 53 * The selected target layer of type T 54 */ 55 public T selectedTargetLayer; 56 /** 57 * Whether the checkbox is ticked 58 */ 59 public boolean checkboxTicked = false; 60 /** 61 * Constructs a new {@link TargetLayerDialogResult} 62 */ 63 public TargetLayerDialogResult() { 64 } 65 /** 66 * Constructs a new {@link TargetLayerDialogResult} 67 * @param sel the selected target layer of type T 68 */ 69 public TargetLayerDialogResult(T sel) { 70 selectedTargetLayer = sel; 71 } 72 /** 73 * Constructs a new {@link TargetLayerDialogResult} 74 * @param sel the selected target layer of type T 75 * @param ch whether the checkbox was ticked 76 */ 77 public TargetLayerDialogResult(T sel, boolean ch) { 78 selectedTargetLayer = sel; 79 checkboxTicked = ch; 80 } 81 } 82 83 /** 47 84 * Constructs a new {@code AbstractMergeAction}. 48 85 * @param name the action's text as displayed on the menu (if it is added to a menu) 49 86 * @param iconName the filename of the icon to use … … 84 121 * @return the chosen layer 85 122 */ 86 123 protected static Layer askTargetLayer(List<Layer> targetLayers) { 124 return askTargetLayer(targetLayers, false, null, false).selectedTargetLayer; 125 } 126 127 /** 128 * Ask user to choose the target layer and shows a checkbox. 129 * @param targetLayers list of candidate target layers. 130 * @param checkbox The text of the checkbox shown to the user. 131 * @param checkboxDefault whether the checkbox is ticked by default 132 * @return The {@link TargetLayerDialogResult} containing the chosen target layer and the state of the checkbox 133 */ 134 protected static TargetLayerDialogResult<Layer> askTargetLayer(List<Layer> targetLayers, String checkbox, boolean checkboxDefault) { 135 return askTargetLayer(targetLayers, true, checkbox, checkboxDefault); 136 } 137 138 /** 139 * Ask user to choose the target layer and shows a checkbox. 140 * @param targetLayers list of candidate target layers. 141 * @param showCheckbox whether the checkbox is shown 142 * @param checkbox The text of the checkbox shown to the user. 143 * @param checkboxDefault whether the checkbox is ticked by default 144 * @return The {@link TargetLayerDialogResult} containing the chosen target layer and the state of the checkbox 145 */ 146 protected static TargetLayerDialogResult<Layer> askTargetLayer(List<Layer> targetLayers, boolean showCheckbox, String checkbox, boolean checkboxDefault) { 87 147 return askTargetLayer(targetLayers.toArray(new Layer[0]), 88 148 tr("Please select the target layer."), 149 checkbox, 89 150 tr("Select target layer"), 90 tr("Merge"), "dialogs/mergedown"); 151 tr("Merge"), 152 "dialogs/mergedown", 153 showCheckbox, 154 checkboxDefault); 91 155 } 92 156 93 157 /** 94 * Ask s atarget layer.158 * Ask user to choose the target layer. 95 159 * @param <T> type of layer 96 160 * @param targetLayers array of proposed target layers 97 161 * @param label label displayed in dialog … … 98 162 * @param title title of dialog 99 163 * @param buttonText text of button used to select target layer 100 164 * @param buttonIcon icon name of button used to select target layer 101 * @return cho osen target layer165 * @return chosen target layer 102 166 */ 167 public static <T extends Layer> T askTargetLayer(T[] targetLayers, String label, String title, String buttonText, String buttonIcon) { 168 return askTargetLayer(targetLayers, label, null, title, buttonText, buttonIcon, false, false).selectedTargetLayer; 169 } 170 171 /** 172 * Ask user to choose the target layer. Can show a checkbox. 173 * @param <T> type of layer 174 * @param targetLayers array of proposed target layers 175 * @param label label displayed in dialog 176 * @param checkbox text of the checkbox displayed 177 * @param title title of dialog 178 * @param buttonText text of button used to select target layer 179 * @param buttonIcon icon name of button used to select target layer 180 * @param showCheckbox whether the checkbox is shown 181 * @param checkboxDefault whether the checkbox is ticked by default 182 * @return The {@link TargetLayerDialogResult} containing the chosen target layer and the state of the checkbox 183 */ 103 184 @SuppressWarnings("unchecked") 104 public static <T extends Layer> T askTargetLayer(T[] targetLayers, String label, String title, String buttonText, String buttonIcon) {185 public static <T extends Layer> TargetLayerDialogResult<T> askTargetLayer(T[] targetLayers, String label, String checkbox, String title, String buttonText, String buttonIcon, boolean showCheckbox, boolean checkboxDefault) { 105 186 JosmComboBox<T> layerList = new JosmComboBox<>(targetLayers); 106 187 layerList.setRenderer(new LayerListCellRenderer()); 107 188 layerList.setSelectedIndex(0); … … 111 192 pnl.add(layerList, GBC.eol().fill(GBC.HORIZONTAL)); 112 193 if (GraphicsEnvironment.isHeadless()) { 113 194 // return first layer in headless mode, for unit tests 114 return targetLayers[0];195 return new TargetLayerDialogResult<>(targetLayers[0]); 115 196 } 197 198 JCheckBox cb = null; 199 if (showCheckbox) { 200 cb = new JCheckBox(checkbox); 201 cb.setSelected(checkboxDefault); 202 pnl.add(cb, GBC.eol()); 203 } 204 116 205 ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), title, buttonText, tr("Cancel")); 117 206 ed.setButtonIcons(buttonIcon, "cancel"); 118 207 ed.setContent(pnl); 119 208 ed.showDialog(); 120 209 if (ed.getValue() != 1) { 121 return n ull;210 return new TargetLayerDialogResult<>(); 122 211 } 123 return (T) layerList.getSelectedItem();212 return new TargetLayerDialogResult<>((T) layerList.getSelectedItem(), cb != null && cb.isSelected()); 124 213 } 125 214 126 215 /** -
src/org/openstreetmap/josm/actions/MergeLayerAction.java
6 6 7 7 import java.awt.event.ActionEvent; 8 8 import java.awt.event.KeyEvent; 9 import java.util.ArrayList; 9 10 import java.util.Collection; 10 11 import java.util.Collections; 11 12 import java.util.List; … … 13 14 14 15 import org.openstreetmap.josm.gui.MainApplication; 15 16 import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 17 import org.openstreetmap.josm.gui.dialogs.layer.MergeGpxLayerDialog; 18 import org.openstreetmap.josm.gui.layer.GpxLayer; 16 19 import org.openstreetmap.josm.gui.layer.Layer; 17 20 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 18 21 import org.openstreetmap.josm.gui.util.GuiHelper; 22 import org.openstreetmap.josm.spi.preferences.Config; 19 23 import org.openstreetmap.josm.tools.ImageProvider; 20 24 import org.openstreetmap.josm.tools.Logging; 21 25 import org.openstreetmap.josm.tools.Shortcut; … … 47 51 * @since 11885 (return type) 48 52 */ 49 53 protected Future<?> doMerge(List<Layer> targetLayers, final Collection<Layer> sourceLayers) { 50 final Layer targetLayer = askTargetLayer(targetLayers); 54 55 boolean ogpx = true; 56 for (Layer l : targetLayers) { 57 if (!(l instanceof GpxLayer)) { 58 ogpx = false; 59 break; 60 } 61 } 62 final boolean onlygpx = ogpx; 63 64 final TargetLayerDialogResult<Layer> res = askTargetLayer( 65 targetLayers, 66 onlygpx, 67 tr("Cut timewise overlapping parts of tracks"), 68 onlygpx && Config.getPref().getBoolean("mergelayer.gpx.cut", false)); 69 70 final Layer targetLayer = res.selectedTargetLayer; 51 71 if (targetLayer == null) 52 72 return null; 73 74 if (onlygpx) { 75 Config.getPref().putBoolean("mergelayer.gpx.cut", res.checkboxTicked); 76 } 77 53 78 final Object actionName = getValue(NAME); 79 if (onlygpx && res.checkboxTicked) { 80 List<GpxLayer> layers = new ArrayList<>(); 81 layers.add((GpxLayer) targetLayer); 82 for (Layer sl : sourceLayers) { 83 if (sl != null && !sl.equals(targetLayer)) { 84 layers.add((GpxLayer) sl); 85 } 86 } 87 final MergeGpxLayerDialog d = new MergeGpxLayerDialog(MainApplication.getMainFrame(), layers); 88 89 if (d.showDialog().getValue() == 1) { 90 91 final boolean connect = d.connectCuts(); 92 final List<GpxLayer> sortedLayers = d.getSortedLayers(); 93 94 return MainApplication.worker.submit(() -> { 95 final long start = System.currentTimeMillis(); 96 97 for (int i = sortedLayers.size() - 2; i >= 0; i--) { 98 final GpxLayer lower = sortedLayers.get(i + 1); 99 sortedLayers.get(i).mergeFrom(lower, true, connect); 100 GuiHelper.runInEDTAndWait(() -> getLayerManager().removeLayer(lower)); 101 } 102 103 Logging.info(tr("{0} completed in {1}", actionName, Utils.getDurationString(System.currentTimeMillis() - start))); 104 }); 105 } 106 } 107 54 108 return MainApplication.worker.submit(() -> { 55 final long start = System.currentTimeMillis(); 56 boolean layerMerged = false; 57 for (final Layer sourceLayer: sourceLayers) { 58 if (sourceLayer != null && !sourceLayer.equals(targetLayer)) { 59 if (sourceLayer instanceof OsmDataLayer && targetLayer instanceof OsmDataLayer 60 && ((OsmDataLayer) sourceLayer).isUploadDiscouraged() != ((OsmDataLayer) targetLayer).isUploadDiscouraged() 61 && Boolean.TRUE.equals(GuiHelper.runInEDTAndWaitAndReturn(() -> 62 warnMergingUploadDiscouragedLayers(sourceLayer, targetLayer)))) { 63 break; 64 } 65 targetLayer.mergeFrom(sourceLayer); 66 GuiHelper.runInEDTAndWait(() -> getLayerManager().removeLayer(sourceLayer)); 67 layerMerged = true; 109 final long start = System.currentTimeMillis(); 110 boolean layerMerged = false; 111 for (final Layer sourceLayer: sourceLayers) { 112 if (sourceLayer != null && !sourceLayer.equals(targetLayer)) { 113 if (sourceLayer instanceof OsmDataLayer && targetLayer instanceof OsmDataLayer 114 && ((OsmDataLayer) sourceLayer).isUploadDiscouraged() != ((OsmDataLayer) targetLayer).isUploadDiscouraged() 115 && Boolean.TRUE.equals(GuiHelper.runInEDTAndWaitAndReturn(() -> 116 warnMergingUploadDiscouragedLayers(sourceLayer, targetLayer)))) { 117 break; 68 118 } 119 targetLayer.mergeFrom(sourceLayer); 120 GuiHelper.runInEDTAndWait(() -> getLayerManager().removeLayer(sourceLayer)); 121 layerMerged = true; 69 122 } 70 if (layerMerged) { 71 getLayerManager().setActiveLayer(targetLayer); 72 Logging.info(tr("{0} completed in {1}", actionName, Utils.getDurationString(System.currentTimeMillis() - start))); 73 } 123 } 124 125 if (layerMerged) { 126 getLayerManager().setActiveLayer(targetLayer); 127 Logging.info(tr("{0} completed in {1}", actionName, Utils.getDurationString(System.currentTimeMillis() - start))); 128 } 74 129 }); 75 130 } 76 131 132 77 133 /** 78 134 * Merges a list of layers together. 79 135 * @param sourceLayers The layers to merge -
src/org/openstreetmap/josm/data/gpx/GpxData.java
58 58 */ 59 59 private final ArrayList<GpxTrack> privateTracks = new ArrayList<>(); 60 60 /** 61 * G XProutes in this file61 * GPX routes in this file 62 62 */ 63 63 private final ArrayList<GpxRoute> privateRoutes = new ArrayList<>(); 64 64 /** … … 108 108 109 109 private final ListenerList<GpxDataChangeListener> listeners = ListenerList.create(); 110 110 111 static class TimestampConfictException extends Exception {} 112 113 private List<GpxTrackSegmentSpan> segSpans; 114 111 115 /** 112 116 * Merges data from another object. 113 117 * @param other existing GPX data 114 118 */ 115 119 public synchronized void mergeFrom(GpxData other) { 120 mergeFrom(other, false, false); 121 } 122 123 /** 124 * Merges data from another object. 125 * @param other existing GPX data 126 * @param cutOverlapping whether overlapping parts of the given track should be removed 127 * @param connect whether the tracks should be connected on cuts 128 */ 129 public synchronized void mergeFrom(GpxData other, boolean cutOverlapping, boolean connect) { 130 116 131 if (storageFile == null && other.storageFile != null) { 117 132 storageFile = other.storageFile; 118 133 } … … 130 145 put(k, ent.getValue()); 131 146 } 132 147 } 133 other.privateTracks.forEach(this::addTrack); 148 149 if (cutOverlapping) { 150 for (GpxTrack trk : other.privateTracks) { 151 List<GpxTrackSegment> segsOld = new ArrayList<>(trk.getSegments()); 152 List<GpxTrackSegment> segsNew = new ArrayList<>(); 153 for (GpxTrackSegment seg : segsOld) { 154 GpxTrackSegmentSpan s = GpxTrackSegmentSpan.tryGetFromSegment(seg); 155 if (s != null && anySegmentOverlapsWith(s)) { 156 List<WayPoint> wpsNew = new ArrayList<>(); 157 List<WayPoint> wpsOld = new ArrayList<>(seg.getWayPoints()); 158 if (s.isInverted()) { 159 Collections.reverse(wpsOld); 160 } 161 boolean split = false; 162 WayPoint prevLastOwnWp = null; 163 Date prevWpTime = null; 164 for (WayPoint wp : wpsOld) { 165 Date wpTime = wp.setTimeFromAttribute(); 166 boolean overlap = false; 167 if (wpTime != null) { 168 for (GpxTrackSegmentSpan ownspan : getSegmentSpans()) { 169 if (wpTime.after(ownspan.firstTime) && wpTime.before(ownspan.lastTime)) { 170 overlap = true; 171 if (connect) { 172 if (!split) { 173 wpsNew.add(ownspan.getFirstWp()); 174 } else { 175 connectTracks(prevLastOwnWp, ownspan, trk.getAttributes()); 176 } 177 prevLastOwnWp = ownspan.getLastWp(); 178 } 179 split = true; 180 break; 181 } else if (connect && prevWpTime != null 182 && prevWpTime.before(ownspan.firstTime) 183 && wpTime.after(ownspan.lastTime)) { 184 //the overlapping high priority track is shorter than the distance between two waypoints of the low priority track 185 if (split) { 186 connectTracks(prevLastOwnWp, ownspan, trk.getAttributes()); 187 prevLastOwnWp = ownspan.getLastWp(); 188 } else { 189 wpsNew.add(ownspan.getFirstWp()); 190 //splitting needs to be handled here, because other high priority tracks between the same waypoints could follow 191 if (!wpsNew.isEmpty()) { 192 segsNew.add(new ImmutableGpxTrackSegment(wpsNew)); 193 } 194 if (!segsNew.isEmpty()) { 195 privateTracks.add(new ImmutableGpxTrack(segsNew, trk.getAttributes())); 196 } 197 segsNew = new ArrayList<>(); 198 wpsNew = new ArrayList<>(); 199 wpsNew.add(ownspan.getLastWp()); 200 //therefore no break, because another segment could overlap, see above 201 } 202 } 203 } 204 prevWpTime = wpTime; 205 } 206 if (!overlap) { 207 if (split) { 208 //track has to be split, because we have an overlapping short track in the middle 209 if (!wpsNew.isEmpty()) { 210 segsNew.add(new ImmutableGpxTrackSegment(wpsNew)); 211 } 212 if (!segsNew.isEmpty()) { 213 privateTracks.add(new ImmutableGpxTrack(segsNew, trk.getAttributes())); 214 } 215 segsNew = new ArrayList<>(); 216 wpsNew = new ArrayList<>(); 217 if (connect && prevLastOwnWp != null) { 218 wpsNew.add(new WayPoint(prevLastOwnWp)); 219 } 220 prevLastOwnWp = null; 221 split = false; 222 } 223 wpsNew.add(new WayPoint(wp)); 224 } 225 } 226 if (!wpsNew.isEmpty()) { 227 segsNew.add(new ImmutableGpxTrackSegment(wpsNew)); 228 } 229 } else { 230 segsNew.add(seg); 231 } 232 } 233 if (segsNew.equals(segsOld)) { 234 privateTracks.add(trk); 235 } else if (!segsNew.isEmpty()) { 236 privateTracks.add(new ImmutableGpxTrack(segsNew, trk.getAttributes())); 237 } 238 } 239 } else { 240 other.privateTracks.forEach(this::addTrack); 241 } 134 242 other.privateRoutes.forEach(this::addRoute); 135 243 other.privateWaypoints.forEach(this::addWaypoint); 136 244 dataSources.addAll(other.dataSources); … … 137 245 fireInvalidate(); 138 246 } 139 247 248 private void connectTracks(WayPoint prevWp, GpxTrackSegmentSpan span, Map<String, Object> attr) { 249 if (prevWp != null && !span.lastEquals(prevWp)) { 250 privateTracks.add( 251 new ImmutableGpxTrack( 252 Arrays.asList(Arrays.asList( 253 new WayPoint(prevWp), 254 span.getFirstWp())), 255 attr) 256 ); 257 } 258 } 259 260 static class GpxTrackSegmentSpan { 261 262 public Date firstTime, lastTime; 263 private boolean inv; 264 private WayPoint firstWp, lastWp; 265 266 public GpxTrackSegmentSpan(WayPoint a, WayPoint b) { 267 Date at = a.getTime(); 268 Date bt = b.getTime(); 269 inv = bt.before(at); 270 if (inv) { 271 firstWp = b; 272 firstTime = bt; 273 lastWp = a; 274 lastTime = at; 275 } else { 276 firstWp = a; 277 firstTime = at; 278 lastWp = b; 279 lastTime = bt; 280 } 281 } 282 283 public WayPoint getFirstWp() { 284 return new WayPoint(firstWp); 285 } 286 public WayPoint getLastWp() { 287 return new WayPoint(lastWp); 288 } 289 290 //no new instances needed, therefore own methods for that 291 public boolean firstEquals(Object other) { 292 return firstWp.equals(other); 293 } 294 public boolean lastEquals(Object other) { 295 return lastWp.equals(other); 296 } 297 298 public boolean isInverted() { 299 return inv; 300 } 301 302 public boolean overlapsWith(GpxTrackSegmentSpan other) { 303 return (firstTime.before(other.lastTime) && other.firstTime.before(lastTime)) || (other.firstTime.before(lastTime) && firstTime.before(other.lastTime)); 304 } 305 306 public static GpxTrackSegmentSpan tryGetFromSegment(GpxTrackSegment seg) { 307 WayPoint b = getNextWpWithTime(seg, true); 308 if (b != null) { 309 WayPoint e = getNextWpWithTime(seg, false); 310 if (e != null) { 311 return new GpxTrackSegmentSpan(b, e); 312 } 313 } 314 return null; 315 } 316 317 private static WayPoint getNextWpWithTime(GpxTrackSegment seg, boolean forward) { 318 List<WayPoint> wps = new ArrayList<>(seg.getWayPoints()); 319 for (int i = forward ? 0 : wps.size() - 1; i >= 0 && i < wps.size(); i += forward ? 1 : -1) { 320 if (wps.get(i).setTimeFromAttribute() != null) { 321 return wps.get(i); 322 } 323 } 324 return null; 325 } 326 } 327 140 328 /** 329 * Get a list of SegmentSpans containing the beginning and end of each segment 330 * @return the <code>List<GpxTrackSegmentSpan></code> 331 * @since xxx 332 */ 333 public synchronized List<GpxTrackSegmentSpan> getSegmentSpans() { 334 if (segSpans == null) { 335 segSpans = new ArrayList<>(); 336 for (GpxTrack trk : privateTracks) { 337 for (GpxTrackSegment seg : trk.getSegments()) { 338 GpxTrackSegmentSpan s = GpxTrackSegmentSpan.tryGetFromSegment(seg); 339 if (s != null) { 340 segSpans.add(s); 341 } 342 } 343 } 344 segSpans.sort((o1, o2) -> { 345 return o1.firstTime.compareTo(o2.firstTime); 346 }); 347 } 348 return segSpans; 349 } 350 351 private boolean anySegmentOverlapsWith(GpxTrackSegmentSpan other) { 352 for (GpxTrackSegmentSpan s : getSegmentSpans()) { 353 if (s.overlapsWith(other)) { 354 return true; 355 } 356 } 357 return false; 358 } 359 360 361 /** 141 362 * Get all tracks contained in this data set. 142 363 * @return The tracks. 143 364 */ -
src/org/openstreetmap/josm/gui/dialogs/layer/MergeGpxLayerDialog.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.dialogs.layer; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.awt.Component; 7 import java.awt.GridBagLayout; 8 import java.awt.event.ActionEvent; 9 import java.awt.event.ActionListener; 10 import java.util.Collections; 11 import java.util.List; 12 13 import javax.swing.JButton; 14 import javax.swing.JCheckBox; 15 import javax.swing.JLabel; 16 import javax.swing.JOptionPane; 17 import javax.swing.JPanel; 18 import javax.swing.JScrollPane; 19 import javax.swing.JTable; 20 import javax.swing.ListSelectionModel; 21 import javax.swing.event.ListSelectionEvent; 22 import javax.swing.event.ListSelectionListener; 23 import javax.swing.table.AbstractTableModel; 24 import javax.swing.table.TableColumnModel; 25 26 import org.openstreetmap.josm.data.SystemOfMeasurement; 27 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 28 import org.openstreetmap.josm.gui.ExtendedDialog; 29 import org.openstreetmap.josm.gui.layer.GpxLayer; 30 import org.openstreetmap.josm.spi.preferences.Config; 31 import org.openstreetmap.josm.tools.GBC; 32 import org.openstreetmap.josm.tools.ImageProvider; 33 import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; 34 35 /** 36 * The Dialog asking the user to prioritize GPX layers when cutting overlapping tracks. 37 * Shows a checkbox asking whether to combine the tracks on cuts. 38 */ 39 public class MergeGpxLayerDialog extends ExtendedDialog { 40 41 GpxLayersTableModel model; 42 JTable t; 43 JCheckBox c; 44 Component parent; 45 JButton btnUp, btnDown; 46 47 /** 48 * Constructs a new <code>MergeGpxLayerDialog</dialog> 49 * @param par the parent 50 * @param layers the GpxLayers to choose from 51 */ 52 public MergeGpxLayerDialog(Component par, List<GpxLayer> layers) { 53 super(par, tr("Merge GPX layers"), tr("Merge"), tr("Cancel")); 54 setButtonIcons("dialogs/mergedown", "cancel"); 55 parent = par; 56 57 JPanel p = new JPanel(new GridBagLayout()); 58 p.add(new JLabel(tr("<html>Please select the order of the selected layers:<br>Tracks will be cut, when timestamps of higher layers are overlapping.</html>")), 59 GBC.std(0, 0).fill(GBC.HORIZONTAL).span(2)); 60 61 c = new JCheckBox(tr("Connect overlapping tracks on cuts")); 62 c.setSelected(Config.getPref().getBoolean("mergelayer.gpx.connect", true)); 63 p.add(c, GBC.std(0, 1).fill(GBC.HORIZONTAL).span(2)); 64 65 model = new GpxLayersTableModel(layers); 66 t = new JTable(model); 67 t.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 68 t.setRowSelectionInterval(0, 0); 69 70 JScrollPane sp = new JScrollPane(t); 71 p.add(sp, GBC.std(0, 2).fill().span(2)); 72 73 t.getSelectionModel().addListSelectionListener(new rowSelectionChangedListener()); 74 TableColumnModel cmodel = t.getColumnModel(); 75 cmodel.getColumn(0).setPreferredWidth((int) (sp.getPreferredSize().getWidth() - 150)); 76 cmodel.getColumn(1).setPreferredWidth(75); 77 cmodel.getColumn(2).setPreferredWidth(75); 78 79 btnUp = new JButton(tr("Move layer up")); 80 btnUp.setIcon(ImageProvider.get("dialogs", "up", ImageSizes.SMALLICON)); 81 btnUp.setEnabled(false); 82 83 btnDown = new JButton(tr("Move layer down")); 84 btnDown.setIcon(ImageProvider.get("dialogs", "down", ImageSizes.SMALLICON)); 85 86 p.add(btnUp, GBC.std(0, 3).fill(GBC.HORIZONTAL)); 87 p.add(btnDown, GBC.std(1, 3).fill(GBC.HORIZONTAL)); 88 89 btnUp.addActionListener(new moveLayersActionListener(true)); 90 btnDown.addActionListener(new moveLayersActionListener(false)); 91 92 setContent(p); 93 } 94 95 @Override 96 public MergeGpxLayerDialog showDialog() { 97 super.showDialog(); 98 if (getValue() == 1) { 99 Config.getPref().putBoolean("mergelayer.gpx.connect", c.isSelected()); 100 } 101 return this; 102 } 103 104 /** 105 * Whether the user chose to connect the tracks on cuts 106 * @return the checkbox state 107 */ 108 public boolean connectCuts() { 109 return c.isSelected(); 110 } 111 112 /** 113 * The List<GpxLayer> as sorted by the user 114 * @return the list 115 */ 116 public List<GpxLayer> getSortedLayers() { 117 return model.getSortedLayers(); 118 } 119 120 class moveLayersActionListener implements ActionListener { 121 122 boolean moveUp; 123 124 public moveLayersActionListener(boolean up) { 125 moveUp = up; 126 } 127 128 @Override 129 public void actionPerformed(ActionEvent arg0) { 130 int row = t.getSelectedRow(); 131 int newRow = row + (moveUp ? -1 : 1); 132 133 if ((row == 0 || newRow == 0) 134 && (!ConditionalOptionPaneUtil.showConfirmationDialog( 135 "gpx_target_change", 136 parent, 137 new JLabel(tr( 138 "<html>This will change the target layer to \"{0}\".<br>Would you like to continue?</html>", 139 model.getValueAt(1, 0).toString())), 140 tr("Information"), 141 JOptionPane.OK_CANCEL_OPTION, 142 JOptionPane.INFORMATION_MESSAGE, 143 JOptionPane.OK_OPTION))) { 144 return; 145 } 146 147 model.moveRow(row, newRow); 148 t.getSelectionModel().setSelectionInterval(newRow, newRow); 149 t.repaint(); 150 } 151 152 } 153 154 class rowSelectionChangedListener implements ListSelectionListener { 155 156 @Override 157 public void valueChanged(ListSelectionEvent arg0) { 158 btnUp.setEnabled(t.getSelectedRow() > 0); 159 btnDown.setEnabled(t.getSelectedRow() < model.getRowCount() - 1); 160 } 161 162 } 163 164 static class GpxLayersTableModel extends AbstractTableModel { 165 166 final String[] cols = {tr("GPX layer"), tr("Length"), tr("Segments")}; 167 List<GpxLayer> layers; 168 169 public GpxLayersTableModel(List<GpxLayer> l) { 170 layers = l; 171 } 172 173 @Override 174 public String getColumnName(int column) { 175 return cols[column]; 176 } 177 178 @Override 179 public int getColumnCount() { 180 return cols.length; 181 } 182 183 @Override 184 public int getRowCount() { 185 return layers.size(); 186 187 } 188 189 public void moveRow(int row, int newRow) { 190 Collections.swap(layers, row, newRow); 191 } 192 193 public List<GpxLayer> getSortedLayers() { 194 return layers; 195 } 196 197 @Override 198 public Object getValueAt(int row, int col) { 199 switch (col) { 200 case 0: 201 String n = layers.get(row).getName(); 202 if (row == 0) { 203 return tr("{0} (target layer)", n); 204 } else { 205 return n; 206 } 207 case 1: 208 return SystemOfMeasurement.getSystemOfMeasurement().getDistText(layers.get(row).data.length()); 209 case 2: 210 return layers.get(row).data.getTrackSegsCount(); 211 } 212 throw new IndexOutOfBoundsException(); 213 } 214 } 215 } -
src/org/openstreetmap/josm/gui/layer/GpxLayer.java
306 306 public void mergeFrom(Layer from) { 307 307 if (!(from instanceof GpxLayer)) 308 308 throw new IllegalArgumentException("not a GpxLayer: " + from); 309 data.mergeFrom(((GpxLayer) from).data); 309 mergeFrom((GpxLayer) from, false, false); 310 } 311 312 /** 313 * Merges the given GpxLayer into this layer and can remove timewise overlapping parts of the given track 314 * @param from The GpxLayer that gets merged into this one 315 * @param cutOverlapping whether overlapping parts of the given track should be removed 316 * @param connect whether the tracks should be connected on cuts 317 */ 318 public void mergeFrom(GpxLayer from, boolean cutOverlapping, boolean connect) { 319 data.mergeFrom(from.data, cutOverlapping, connect); 310 320 invalidate(); 311 321 } 312 322 -
test/data/mergelayers/Layer1.gpx
1 <?xml version='1.0' encoding='UTF-8'?> 2 <gpx version="1.1" creator="JOSM GPX export" xmlns="http://www.topografix.com/GPX/1/1" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> 5 <metadata> 6 <bounds minlat="-44.6794593" minlon="168.9676659" maxlat="-44.6267934" maxlon="169.114549"/> 7 </metadata> 8 <trk> 9 <trkseg> 10 <trkpt lat="-44.64903055828478" lon="168.9688660654368"> 11 <time>2018-08-01T04:15:00.000Z</time> 12 </trkpt> 13 <trkpt lat="-44.627129349" lon="168.96766593933"> 14 <time>2018-08-01T04:45:00.000Z</time> 15 </trkpt> 16 </trkseg> 17 </trk> 18 <trk> 19 <trkseg> 20 <trkpt lat="-44.67377988143" lon="169.10087551117"> 21 <time>2018-08-01T01:15:00.000Z</time> 22 </trkpt> 23 <trkpt lat="-44.67574681965" lon="169.08905899514"> 24 <time>2018-08-01T01:20:00.000Z</time> 25 </trkpt> 26 </trkseg> 27 </trk> 28 <trk> 29 <trkseg> 30 <trkpt lat="-44.67945926209" lon="169.08388386828"> 31 <time>2018-08-01T01:30:00.000Z</time> 32 </trkpt> 33 <trkpt lat="-44.67193249915" lon="169.05767836406"> 34 </trkpt> 35 <trkpt lat="-44.67829447303" lon="169.03051544255"> 36 <time>2018-08-01T02:30:00.000Z</time> 37 </trkpt> 38 </trkseg> 39 </trk> 40 <trk> 41 <trkseg> 42 <trkpt lat="-44.67601752425" lon="169.02167947454"> 43 <time>2018-08-01T02:40:00.000Z</time> 44 </trkpt> 45 <trkpt lat="-44.67302186733" lon="169.01440310326"> 46 <time>2018-08-01T02:50:00.000Z</time> 47 </trkpt> 48 </trkseg> 49 </trk> 50 <trk> 51 <trkseg> 52 <trkpt lat="-44.67206867129" lon="169.00865859961"> 53 <time>2018-08-01T02:55:00.000Z</time> 54 </trkpt> 55 <trkpt lat="-44.6715239808" lon="168.98031904829"> 56 <time>2018-08-01T03:15:00.000Z</time> 57 </trkpt> 58 </trkseg> 59 </trk> 60 <trk> 61 <trkseg> 62 <trkpt lat="-44.6716601539" lon="169.11454895015"> 63 <time>2018-08-01T01:05:00.000Z</time> 64 </trkpt> 65 <trkpt lat="-44.67261335666" lon="169.10708109541"> 66 <time>2018-08-01T01:10:00.000Z</time> 67 </trkpt> 68 </trkseg> 69 </trk> 70 <trk> 71 <trkseg> 72 <trkpt lat="-44.62679341842" lon="169.09608703937"> 73 <time>2018-08-01T06:30:00.000Z</time> 74 </trkpt> 75 <trkpt lat="-44.64397805726" lon="169.10662502858"> 76 <time>2018-08-01T07:30:00.000Z</time> 77 </trkpt> 78 </trkseg> 79 </trk> 80 </gpx> 81 No newline at end of file -
test/data/mergelayers/Layer2.gpx
1 <?xml version='1.0' encoding='UTF-8'?> 2 <gpx version="1.1" creator="JOSM GPX export" xmlns="http://www.topografix.com/GPX/1/1" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> 5 <metadata> 6 <bounds minlat="-44.6918439" minlon="168.9588303" maxlat="-44.6149174" maxlon="169.1237066"/> 7 </metadata> 8 <trk> 9 <trkseg> 10 <trkpt lat="-44.63074821844668" lon="168.95976943655546"> 11 <time>2018-08-01T04:30:00.000Z</time> 12 </trkpt> 13 <trkpt lat="-44.61491742259" lon="168.97007025177"> 14 <time>2018-08-01T04:45:00.000Z</time> 15 </trkpt> 16 <trkpt lat="-44.61710528856" lon="169.04603159064"> 17 <time>2018-08-01T05:00:00.000Z</time> 18 </trkpt> 19 </trkseg> 20 </trk> 21 <trk> 22 <trkseg> 23 <trkpt lat="-44.61835546068" lon="169.08598813305"> 24 <time>2018-08-01T06:00:00.000Z</time> 25 </trkpt> 26 <trkpt lat="-44.62322244297" lon="169.08944198935"> 27 </trkpt> 28 <trkpt lat="-44.63880660641" lon="169.09324299504"> 29 <time>2018-08-01T07:00:00.000Z</time> 30 </trkpt> 31 <trkpt lat="-44.65772210426" lon="169.10838136012"> 32 <time>2018-08-01T08:00:00.000Z</time> 33 </trkpt> 34 </trkseg> 35 </trk> 36 <trk> 37 <trkseg> 38 <trkpt lat="-44.67719792038" lon="169.12370664597"> 39 <time>2018-08-01T01:00:00.000Z</time> 40 </trkpt> 41 <trkpt lat="-44.6918438522" lon="169.04766065598"> 42 <time>2018-08-01T02:00:00.000Z</time> 43 </trkpt> 44 <trkpt lat="-44.67329159113" lon="168.99993862152"> 45 <time>2018-08-01T03:00:00.000Z</time> 46 </trkpt> 47 <trkpt lat="-44.677348434487726" lon="168.9702040298994"> 48 <time>2018-08-01T03:30:00.000Z</time> 49 </trkpt> 50 <trkpt lat="-44.66921934791758" lon="168.96284277369952"> 51 <time>2018-08-01T03:45:00.000Z</time> 52 </trkpt> 53 <trkpt lat="-44.659434880491546" lon="168.95883032510446"> 54 <time>2018-08-01T04:00:00.000Z</time> 55 </trkpt> 56 </trkseg> 57 </trk> 58 </gpx> 59 No newline at end of file -
test/data/mergelayers/Merged-all.gpx
1 <?xml version='1.0' encoding='UTF-8'?> 2 <gpx version="1.1" creator="JOSM GPX export" xmlns="http://www.topografix.com/GPX/1/1" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> 5 <metadata> 6 <bounds minlat="-44.6918439" minlon="168.9588303" maxlat="-44.6149174" maxlon="169.1237066"/> 7 </metadata> 8 <trk> 9 <trkseg> 10 <trkpt lat="-44.64903055828478" lon="168.9688660654368"> 11 <time>2018-08-01T04:15:00.000Z</time> 12 </trkpt> 13 <trkpt lat="-44.627129349" lon="168.96766593933"> 14 <time>2018-08-01T04:45:00.000Z</time> 15 </trkpt> 16 </trkseg> 17 </trk> 18 <trk> 19 <trkseg> 20 <trkpt lat="-44.67377988143" lon="169.10087551117"> 21 <time>2018-08-01T01:15:00.000Z</time> 22 </trkpt> 23 <trkpt lat="-44.67574681965" lon="169.08905899514"> 24 <time>2018-08-01T01:20:00.000Z</time> 25 </trkpt> 26 </trkseg> 27 </trk> 28 <trk> 29 <trkseg> 30 <trkpt lat="-44.67945926209" lon="169.08388386828"> 31 <time>2018-08-01T01:30:00.000Z</time> 32 </trkpt> 33 <trkpt lat="-44.67193249915" lon="169.05767836406"/> 34 <trkpt lat="-44.67829447303" lon="169.03051544255"> 35 <time>2018-08-01T02:30:00.000Z</time> 36 </trkpt> 37 </trkseg> 38 </trk> 39 <trk> 40 <trkseg> 41 <trkpt lat="-44.67601752425" lon="169.02167947454"> 42 <time>2018-08-01T02:40:00.000Z</time> 43 </trkpt> 44 <trkpt lat="-44.67302186733" lon="169.01440310326"> 45 <time>2018-08-01T02:50:00.000Z</time> 46 </trkpt> 47 </trkseg> 48 </trk> 49 <trk> 50 <trkseg> 51 <trkpt lat="-44.67206867129" lon="169.00865859961"> 52 <time>2018-08-01T02:55:00.000Z</time> 53 </trkpt> 54 <trkpt lat="-44.6715239808" lon="168.98031904829"> 55 <time>2018-08-01T03:15:00.000Z</time> 56 </trkpt> 57 </trkseg> 58 </trk> 59 <trk> 60 <trkseg> 61 <trkpt lat="-44.6716601539" lon="169.11454895015"> 62 <time>2018-08-01T01:05:00.000Z</time> 63 </trkpt> 64 <trkpt lat="-44.67261335666" lon="169.10708109541"> 65 <time>2018-08-01T01:10:00.000Z</time> 66 </trkpt> 67 </trkseg> 68 </trk> 69 <trk> 70 <trkseg> 71 <trkpt lat="-44.62679341842" lon="169.09608703937"> 72 <time>2018-08-01T06:30:00.000Z</time> 73 </trkpt> 74 <trkpt lat="-44.64397805726" lon="169.10662502858"> 75 <time>2018-08-01T07:30:00.000Z</time> 76 </trkpt> 77 </trkseg> 78 </trk> 79 <trk> 80 <trkseg> 81 <trkpt lat="-44.63074821844668" lon="168.95976943655546"> 82 <time>2018-08-01T04:30:00.000Z</time> 83 </trkpt> 84 <trkpt lat="-44.61491742259" lon="168.97007025177"> 85 <time>2018-08-01T04:45:00.000Z</time> 86 </trkpt> 87 <trkpt lat="-44.61710528856" lon="169.04603159064"> 88 <time>2018-08-01T05:00:00.000Z</time> 89 </trkpt> 90 </trkseg> 91 </trk> 92 <trk> 93 <trkseg> 94 <trkpt lat="-44.61835546068" lon="169.08598813305"> 95 <time>2018-08-01T06:00:00.000Z</time> 96 </trkpt> 97 <trkpt lat="-44.62322244297" lon="169.08944198935"/> 98 <trkpt lat="-44.63880660641" lon="169.09324299504"> 99 <time>2018-08-01T07:00:00.000Z</time> 100 </trkpt> 101 <trkpt lat="-44.65772210426" lon="169.10838136012"> 102 <time>2018-08-01T08:00:00.000Z</time> 103 </trkpt> 104 </trkseg> 105 </trk> 106 <trk> 107 <trkseg> 108 <trkpt lat="-44.67719792038" lon="169.12370664597"> 109 <time>2018-08-01T01:00:00.000Z</time> 110 </trkpt> 111 <trkpt lat="-44.6918438522" lon="169.04766065598"> 112 <time>2018-08-01T02:00:00.000Z</time> 113 </trkpt> 114 <trkpt lat="-44.67329159113" lon="168.99993862152"> 115 <time>2018-08-01T03:00:00.000Z</time> 116 </trkpt> 117 <trkpt lat="-44.677348434487726" lon="168.9702040298994"> 118 <time>2018-08-01T03:30:00.000Z</time> 119 </trkpt> 120 <trkpt lat="-44.66921934791758" lon="168.96284277369952"> 121 <time>2018-08-01T03:45:00.000Z</time> 122 </trkpt> 123 <trkpt lat="-44.659434880491546" lon="168.95883032510446"> 124 <time>2018-08-01T04:00:00.000Z</time> 125 </trkpt> 126 </trkseg> 127 </trk> 128 </gpx> 129 No newline at end of file -
test/data/mergelayers/Merged-cut-connect.gpx
1 <?xml version='1.0' encoding='UTF-8'?> 2 <gpx version="1.1" creator="JOSM GPX export" xmlns="http://www.topografix.com/GPX/1/1" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> 5 <metadata> 6 <bounds minlat="-44.6794593" minlon="168.9588303" maxlat="-44.6149174" maxlon="169.1237066"/> 7 </metadata> 8 <trk> 9 <trkseg> 10 <trkpt lat="-44.64903055828478" lon="168.9688660654368"> 11 <time>2018-08-01T04:15:00.000Z</time> 12 </trkpt> 13 <trkpt lat="-44.627129349" lon="168.96766593933"> 14 <time>2018-08-01T04:45:00.000Z</time> 15 </trkpt> 16 </trkseg> 17 </trk> 18 <trk> 19 <trkseg> 20 <trkpt lat="-44.67377988143" lon="169.10087551117"> 21 <time>2018-08-01T01:15:00.000Z</time> 22 </trkpt> 23 <trkpt lat="-44.67574681965" lon="169.08905899514"> 24 <time>2018-08-01T01:20:00.000Z</time> 25 </trkpt> 26 </trkseg> 27 </trk> 28 <trk> 29 <trkseg> 30 <trkpt lat="-44.67945926209" lon="169.08388386828"> 31 <time>2018-08-01T01:30:00.000Z</time> 32 </trkpt> 33 <trkpt lat="-44.67193249915" lon="169.05767836406"/> 34 <trkpt lat="-44.67829447303" lon="169.03051544255"> 35 <time>2018-08-01T02:30:00.000Z</time> 36 </trkpt> 37 </trkseg> 38 </trk> 39 <trk> 40 <trkseg> 41 <trkpt lat="-44.67601752425" lon="169.02167947454"> 42 <time>2018-08-01T02:40:00.000Z</time> 43 </trkpt> 44 <trkpt lat="-44.67302186733" lon="169.01440310326"> 45 <time>2018-08-01T02:50:00.000Z</time> 46 </trkpt> 47 </trkseg> 48 </trk> 49 <trk> 50 <trkseg> 51 <trkpt lat="-44.67206867129" lon="169.00865859961"> 52 <time>2018-08-01T02:55:00.000Z</time> 53 </trkpt> 54 <trkpt lat="-44.6715239808" lon="168.98031904829"> 55 <time>2018-08-01T03:15:00.000Z</time> 56 </trkpt> 57 </trkseg> 58 </trk> 59 <trk> 60 <trkseg> 61 <trkpt lat="-44.6716601539" lon="169.11454895015"> 62 <time>2018-08-01T01:05:00.000Z</time> 63 </trkpt> 64 <trkpt lat="-44.67261335666" lon="169.10708109541"> 65 <time>2018-08-01T01:10:00.000Z</time> 66 </trkpt> 67 </trkseg> 68 </trk> 69 <trk> 70 <trkseg> 71 <trkpt lat="-44.62679341842" lon="169.09608703937"> 72 <time>2018-08-01T06:30:00.000Z</time> 73 </trkpt> 74 <trkpt lat="-44.64397805726" lon="169.10662502858"> 75 <time>2018-08-01T07:30:00.000Z</time> 76 </trkpt> 77 </trkseg> 78 </trk> 79 <trk> 80 <trkseg> 81 <trkpt lat="-44.64903055828478" lon="168.9688660654368"> 82 <time>2018-08-01T04:15:00.000Z</time> 83 </trkpt> 84 </trkseg> 85 </trk> 86 <trk> 87 <trkseg> 88 <trkpt lat="-44.627129349" lon="168.96766593933"> 89 <time>2018-08-01T04:45:00.000Z</time> 90 </trkpt> 91 <trkpt lat="-44.61491742259" lon="168.97007025177"> 92 <time>2018-08-01T04:45:00.000Z</time> 93 </trkpt> 94 <trkpt lat="-44.61710528856" lon="169.04603159064"> 95 <time>2018-08-01T05:00:00.000Z</time> 96 </trkpt> 97 </trkseg> 98 </trk> 99 <trk> 100 <trkseg> 101 <trkpt lat="-44.61835546068" lon="169.08598813305"> 102 <time>2018-08-01T06:00:00.000Z</time> 103 </trkpt> 104 <trkpt lat="-44.62322244297" lon="169.08944198935"/> 105 <trkpt lat="-44.62679341842" lon="169.09608703937"> 106 <time>2018-08-01T06:30:00.000Z</time> 107 </trkpt> 108 </trkseg> 109 </trk> 110 <trk> 111 <trkseg> 112 <trkpt lat="-44.64397805726" lon="169.10662502858"> 113 <time>2018-08-01T07:30:00.000Z</time> 114 </trkpt> 115 <trkpt lat="-44.65772210426" lon="169.10838136012"> 116 <time>2018-08-01T08:00:00.000Z</time> 117 </trkpt> 118 </trkseg> 119 </trk> 120 <trk> 121 <trkseg> 122 <trkpt lat="-44.67719792038" lon="169.12370664597"> 123 <time>2018-08-01T01:00:00.000Z</time> 124 </trkpt> 125 <trkpt lat="-44.6716601539" lon="169.11454895015"> 126 <time>2018-08-01T01:05:00.000Z</time> 127 </trkpt> 128 </trkseg> 129 </trk> 130 <trk> 131 <trkseg> 132 <trkpt lat="-44.67261335666" lon="169.10708109541"> 133 <time>2018-08-01T01:10:00.000Z</time> 134 </trkpt> 135 <trkpt lat="-44.67377988143" lon="169.10087551117"> 136 <time>2018-08-01T01:15:00.000Z</time> 137 </trkpt> 138 </trkseg> 139 </trk> 140 <trk> 141 <trkseg> 142 <trkpt lat="-44.67829447303" lon="169.03051544255"> 143 <time>2018-08-01T02:30:00.000Z</time> 144 </trkpt> 145 <trkpt lat="-44.67601752425" lon="169.02167947454"> 146 <time>2018-08-01T02:40:00.000Z</time> 147 </trkpt> 148 </trkseg> 149 </trk> 150 <trk> 151 <trkseg> 152 <trkpt lat="-44.67302186733" lon="169.01440310326"> 153 <time>2018-08-01T02:50:00.000Z</time> 154 </trkpt> 155 <trkpt lat="-44.67206867129" lon="169.00865859961"> 156 <time>2018-08-01T02:55:00.000Z</time> 157 </trkpt> 158 </trkseg> 159 </trk> 160 <trk> 161 <trkseg> 162 <trkpt lat="-44.67574681965" lon="169.08905899514"> 163 <time>2018-08-01T01:20:00.000Z</time> 164 </trkpt> 165 <trkpt lat="-44.67945926209" lon="169.08388386828"> 166 <time>2018-08-01T01:30:00.000Z</time> 167 </trkpt> 168 </trkseg> 169 </trk> 170 <trk> 171 <trkseg> 172 <trkpt lat="-44.6715239808" lon="168.98031904829"> 173 <time>2018-08-01T03:15:00.000Z</time> 174 </trkpt> 175 <trkpt lat="-44.677348434487726" lon="168.9702040298994"> 176 <time>2018-08-01T03:30:00.000Z</time> 177 </trkpt> 178 <trkpt lat="-44.66921934791758" lon="168.96284277369952"> 179 <time>2018-08-01T03:45:00.000Z</time> 180 </trkpt> 181 <trkpt lat="-44.659434880491546" lon="168.95883032510446"> 182 <time>2018-08-01T04:00:00.000Z</time> 183 </trkpt> 184 </trkseg> 185 </trk> 186 </gpx> 187 No newline at end of file -
test/data/mergelayers/Merged-cut.gpx
1 <?xml version='1.0' encoding='UTF-8'?> 2 <gpx version="1.1" creator="JOSM GPX export" xmlns="http://www.topografix.com/GPX/1/1" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> 5 <metadata> 6 <bounds minlat="-44.6794593" minlon="168.9588303" maxlat="-44.6149174" maxlon="169.1237066"/> 7 </metadata> 8 <trk> 9 <trkseg> 10 <trkpt lat="-44.64903055828478" lon="168.9688660654368"> 11 <time>2018-08-01T04:15:00.000Z</time> 12 </trkpt> 13 <trkpt lat="-44.627129349" lon="168.96766593933"> 14 <time>2018-08-01T04:45:00.000Z</time> 15 </trkpt> 16 </trkseg> 17 </trk> 18 <trk> 19 <trkseg> 20 <trkpt lat="-44.67377988143" lon="169.10087551117"> 21 <time>2018-08-01T01:15:00.000Z</time> 22 </trkpt> 23 <trkpt lat="-44.67574681965" lon="169.08905899514"> 24 <time>2018-08-01T01:20:00.000Z</time> 25 </trkpt> 26 </trkseg> 27 </trk> 28 <trk> 29 <trkseg> 30 <trkpt lat="-44.67945926209" lon="169.08388386828"> 31 <time>2018-08-01T01:30:00.000Z</time> 32 </trkpt> 33 <trkpt lat="-44.67193249915" lon="169.05767836406"/> 34 <trkpt lat="-44.67829447303" lon="169.03051544255"> 35 <time>2018-08-01T02:30:00.000Z</time> 36 </trkpt> 37 </trkseg> 38 </trk> 39 <trk> 40 <trkseg> 41 <trkpt lat="-44.67601752425" lon="169.02167947454"> 42 <time>2018-08-01T02:40:00.000Z</time> 43 </trkpt> 44 <trkpt lat="-44.67302186733" lon="169.01440310326"> 45 <time>2018-08-01T02:50:00.000Z</time> 46 </trkpt> 47 </trkseg> 48 </trk> 49 <trk> 50 <trkseg> 51 <trkpt lat="-44.67206867129" lon="169.00865859961"> 52 <time>2018-08-01T02:55:00.000Z</time> 53 </trkpt> 54 <trkpt lat="-44.6715239808" lon="168.98031904829"> 55 <time>2018-08-01T03:15:00.000Z</time> 56 </trkpt> 57 </trkseg> 58 </trk> 59 <trk> 60 <trkseg> 61 <trkpt lat="-44.6716601539" lon="169.11454895015"> 62 <time>2018-08-01T01:05:00.000Z</time> 63 </trkpt> 64 <trkpt lat="-44.67261335666" lon="169.10708109541"> 65 <time>2018-08-01T01:10:00.000Z</time> 66 </trkpt> 67 </trkseg> 68 </trk> 69 <trk> 70 <trkseg> 71 <trkpt lat="-44.62679341842" lon="169.09608703937"> 72 <time>2018-08-01T06:30:00.000Z</time> 73 </trkpt> 74 <trkpt lat="-44.64397805726" lon="169.10662502858"> 75 <time>2018-08-01T07:30:00.000Z</time> 76 </trkpt> 77 </trkseg> 78 </trk> 79 <trk> 80 <trkseg> 81 <trkpt lat="-44.61491742259" lon="168.97007025177"> 82 <time>2018-08-01T04:45:00.000Z</time> 83 </trkpt> 84 <trkpt lat="-44.61710528856" lon="169.04603159064"> 85 <time>2018-08-01T05:00:00.000Z</time> 86 </trkpt> 87 </trkseg> 88 </trk> 89 <trk> 90 <trkseg> 91 <trkpt lat="-44.61835546068" lon="169.08598813305"> 92 <time>2018-08-01T06:00:00.000Z</time> 93 </trkpt> 94 <trkpt lat="-44.62322244297" lon="169.08944198935"/> 95 </trkseg> 96 </trk> 97 <trk> 98 <trkseg> 99 <trkpt lat="-44.65772210426" lon="169.10838136012"> 100 <time>2018-08-01T08:00:00.000Z</time> 101 </trkpt> 102 </trkseg> 103 </trk> 104 <trk> 105 <trkseg> 106 <trkpt lat="-44.67719792038" lon="169.12370664597"> 107 <time>2018-08-01T01:00:00.000Z</time> 108 </trkpt> 109 </trkseg> 110 </trk> 111 <trk> 112 <trkseg> 113 <trkpt lat="-44.677348434487726" lon="168.9702040298994"> 114 <time>2018-08-01T03:30:00.000Z</time> 115 </trkpt> 116 <trkpt lat="-44.66921934791758" lon="168.96284277369952"> 117 <time>2018-08-01T03:45:00.000Z</time> 118 </trkpt> 119 <trkpt lat="-44.659434880491546" lon="168.95883032510446"> 120 <time>2018-08-01T04:00:00.000Z</time> 121 </trkpt> 122 </trkseg> 123 </trk> 124 </gpx> 125 No newline at end of file -
test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java
27 27 import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeEvent; 28 28 import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener; 29 29 import org.openstreetmap.josm.data.projection.ProjectionRegistry; 30 import org.openstreetmap.josm.io.GpxReaderTest; 30 31 import org.openstreetmap.josm.testutils.JOSMTestRules; 31 32 import org.openstreetmap.josm.tools.ListenerList; 32 33 … … 85 86 } 86 87 87 88 /** 89 * Test method for {@link GpxData#mergeFrom(GpxData, boolean, boolean)} including cutting/connecting tracks using actual files. 90 * @throws Exception if the track cannot be parsed 91 */ 92 @Test 93 public void testMergeFromFiles() throws Exception { 94 testMerge(false, false, "Merged-all"); //regular merging 95 testMerge(true, false, "Merged-cut"); //cut overlapping tracks, but do not connect them 96 testMerge(true, true, "Merged-cut-connect"); //cut overlapping tracks and connect them 97 } 98 99 private void testMerge(boolean cut, boolean connect, String exp) throws Exception { 100 final GpxData own = getGpx("Layer1"); 101 final GpxData other = getGpx("Layer2"); 102 final GpxData expected = getGpx(exp); 103 own.mergeFrom(other, cut, connect); 104 assertEquals(expected, own); 105 } 106 107 private GpxData getGpx(String file) throws Exception { 108 return GpxReaderTest.parseGpxData(TestUtils.getTestDataRoot() + "mergelayers/" + file + ".gpx"); 109 } 110 111 /** 88 112 * Test method for {@link GpxData#getTracks()}, {@link GpxData#addTrack(GpxTrack)}, {@link GpxData#removeTrack(GpxTrack)}. 89 113 */ 90 114 @Test … … 448 472 public void testEqualsContract() { 449 473 TestUtils.assumeWorkingEqualsVerifier(); 450 474 EqualsVerifier.forClass(GpxData.class).usingGetClass() 451 .withIgnoredFields("attr", "creator", "fromServer", "storageFile", "listeners", "tracks", "routes", "waypoints", "proxy" )475 .withIgnoredFields("attr", "creator", "fromServer", "storageFile", "listeners", "tracks", "routes", "waypoints", "proxy", "segSpans") 452 476 .withPrefabValues(WayPoint.class, new WayPoint(LatLon.NORTH_POLE), new WayPoint(LatLon.SOUTH_POLE)) 453 477 .withPrefabValues(ListenerList.class, ListenerList.create(), ListenerList.create()) 454 478 .verify();
