Ticket #16755: MergeGPXV1.diff

File MergeGPXV1.diff, 54.5 KB (added by Bjoeni, 8 years ago)
  • src/org/openstreetmap/josm/actions/AbstractMergeAction.java

     
    99import java.util.List;
    1010
    1111import javax.swing.DefaultListCellRenderer;
     12import javax.swing.JCheckBox;
    1213import javax.swing.JLabel;
    1314import javax.swing.JList;
    1415import javax.swing.JOptionPane;
     
    4445    }
    4546
    4647    /**
     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    /**
    4784     * Constructs a new {@code AbstractMergeAction}.
    4885     * @param name the action's text as displayed on the menu (if it is added to a menu)
    4986     * @param iconName the filename of the icon to use
     
    84121     * @return the chosen layer
    85122     */
    86123    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) {
    87147        return askTargetLayer(targetLayers.toArray(new Layer[0]),
    88148                tr("Please select the target layer."),
     149                checkbox,
    89150                tr("Select target layer"),
    90                 tr("Merge"), "dialogs/mergedown");
     151                tr("Merge"),
     152                "dialogs/mergedown",
     153                showCheckbox,
     154                checkboxDefault);
    91155    }
    92156
    93157    /**
    94      * Asks a target layer.
     158     * Ask user to choose the target layer.
    95159     * @param <T> type of layer
    96160     * @param targetLayers array of proposed target layers
    97161     * @param label label displayed in dialog
     
    98162     * @param title title of dialog
    99163     * @param buttonText text of button used to select target layer
    100164     * @param buttonIcon icon name of button used to select target layer
    101      * @return choosen target layer
     165     * @return chosen target layer
    102166     */
     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     */
    103184    @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) {
    105186        JosmComboBox<T> layerList = new JosmComboBox<>(targetLayers);
    106187        layerList.setRenderer(new LayerListCellRenderer());
    107188        layerList.setSelectedIndex(0);
     
    111192        pnl.add(layerList, GBC.eol().fill(GBC.HORIZONTAL));
    112193        if (GraphicsEnvironment.isHeadless()) {
    113194            // return first layer in headless mode, for unit tests
    114             return targetLayers[0];
     195            return new TargetLayerDialogResult<>(targetLayers[0]);
    115196        }
     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
    116205        ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), title, buttonText, tr("Cancel"));
    117206        ed.setButtonIcons(buttonIcon, "cancel");
    118207        ed.setContent(pnl);
    119208        ed.showDialog();
    120209        if (ed.getValue() != 1) {
    121             return null;
     210            return new TargetLayerDialogResult<>();
    122211        }
    123         return (T) layerList.getSelectedItem();
     212        return new TargetLayerDialogResult<>((T) layerList.getSelectedItem(), cb != null && cb.isSelected());
    124213    }
    125214
    126215    /**
  • src/org/openstreetmap/josm/actions/MergeLayerAction.java

     
    66
    77import java.awt.event.ActionEvent;
    88import java.awt.event.KeyEvent;
     9import java.util.ArrayList;
    910import java.util.Collection;
    1011import java.util.Collections;
    1112import java.util.List;
     
    1314
    1415import org.openstreetmap.josm.gui.MainApplication;
    1516import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
     17import org.openstreetmap.josm.gui.dialogs.layer.MergeGpxLayerDialog;
     18import org.openstreetmap.josm.gui.layer.GpxLayer;
    1619import org.openstreetmap.josm.gui.layer.Layer;
    1720import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    1821import org.openstreetmap.josm.gui.util.GuiHelper;
     22import org.openstreetmap.josm.spi.preferences.Config;
    1923import org.openstreetmap.josm.tools.ImageProvider;
    2024import org.openstreetmap.josm.tools.Logging;
    2125import org.openstreetmap.josm.tools.Shortcut;
     
    4751     * @since 11885 (return type)
    4852     */
    4953    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;
    5171        if (targetLayer == null)
    5272            return null;
     73
     74        if (onlygpx) {
     75            Config.getPref().putBoolean("mergelayer.gpx.cut", res.checkboxTicked);
     76        }
     77
    5378        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
    54108        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;
    68118                    }
     119                    targetLayer.mergeFrom(sourceLayer);
     120                    GuiHelper.runInEDTAndWait(() -> getLayerManager().removeLayer(sourceLayer));
     121                    layerMerged = true;
    69122                }
    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            }
    74129        });
    75130    }
    76131
     132
    77133    /**
    78134     * Merges a list of layers together.
    79135     * @param sourceLayers The layers to merge
  • src/org/openstreetmap/josm/data/gpx/GpxData.java

     
    5858     */
    5959    private final ArrayList<GpxTrack> privateTracks = new ArrayList<>();
    6060    /**
    61      * GXP routes in this file
     61     * GPX routes in this file
    6262     */
    6363    private final ArrayList<GpxRoute> privateRoutes = new ArrayList<>();
    6464    /**
     
    108108
    109109    private final ListenerList<GpxDataChangeListener> listeners = ListenerList.create();
    110110
     111    static class TimestampConfictException extends Exception {}
     112
     113    private List<GpxTrackSegmentSpan> segSpans;
     114
    111115    /**
    112116     * Merges data from another object.
    113117     * @param other existing GPX data
    114118     */
    115119    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
    116131        if (storageFile == null && other.storageFile != null) {
    117132            storageFile = other.storageFile;
    118133        }
     
    130145                put(k, ent.getValue());
    131146            }
    132147        }
    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        }
    134242        other.privateRoutes.forEach(this::addRoute);
    135243        other.privateWaypoints.forEach(this::addWaypoint);
    136244        dataSources.addAll(other.dataSources);
     
    137245        fireInvalidate();
    138246    }
    139247
     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
    140328    /**
     329     * Get a list of SegmentSpans containing the beginning and end of each segment
     330     * @return the <code>List&lt;GpxTrackSegmentSpan&gt;</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    /**
    141362     * Get all tracks contained in this data set.
    142363     * @return The tracks.
    143364     */
  • src/org/openstreetmap/josm/gui/dialogs/layer/MergeGpxLayerDialog.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.dialogs.layer;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.Component;
     7import java.awt.GridBagLayout;
     8import java.awt.event.ActionEvent;
     9import java.awt.event.ActionListener;
     10import java.util.Collections;
     11import java.util.List;
     12
     13import javax.swing.JButton;
     14import javax.swing.JCheckBox;
     15import javax.swing.JLabel;
     16import javax.swing.JOptionPane;
     17import javax.swing.JPanel;
     18import javax.swing.JScrollPane;
     19import javax.swing.JTable;
     20import javax.swing.ListSelectionModel;
     21import javax.swing.event.ListSelectionEvent;
     22import javax.swing.event.ListSelectionListener;
     23import javax.swing.table.AbstractTableModel;
     24import javax.swing.table.TableColumnModel;
     25
     26import org.openstreetmap.josm.data.SystemOfMeasurement;
     27import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
     28import org.openstreetmap.josm.gui.ExtendedDialog;
     29import org.openstreetmap.josm.gui.layer.GpxLayer;
     30import org.openstreetmap.josm.spi.preferences.Config;
     31import org.openstreetmap.josm.tools.GBC;
     32import org.openstreetmap.josm.tools.ImageProvider;
     33import 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 */
     39public 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&lt;GpxLayer&gt; 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

     
    306306    public void mergeFrom(Layer from) {
    307307        if (!(from instanceof GpxLayer))
    308308            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);
    310320        invalidate();
    311321    }
    312322
  • 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

     
    2727import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeEvent;
    2828import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener;
    2929import org.openstreetmap.josm.data.projection.ProjectionRegistry;
     30import org.openstreetmap.josm.io.GpxReaderTest;
    3031import org.openstreetmap.josm.testutils.JOSMTestRules;
    3132import org.openstreetmap.josm.tools.ListenerList;
    3233
     
    8586    }
    8687
    8788    /**
     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    /**
    88112     * Test method for {@link GpxData#getTracks()},  {@link GpxData#addTrack(GpxTrack)},  {@link GpxData#removeTrack(GpxTrack)}.
    89113     */
    90114    @Test
     
    448472    public void testEqualsContract() {
    449473        TestUtils.assumeWorkingEqualsVerifier();
    450474        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")
    452476            .withPrefabValues(WayPoint.class, new WayPoint(LatLon.NORTH_POLE), new WayPoint(LatLon.SOUTH_POLE))
    453477            .withPrefabValues(ListenerList.class, ListenerList.create(), ListenerList.create())
    454478            .verify();