Ticket #2760: 2760-simplify-V3.diff

File 2760-simplify-V3.diff, 21.8 KB (added by Bjoeni, 7 years ago)
  • data/validator/unnecessary.mapcss

     
    165165  assertNoMatch: "way name=house building=house";
    166166  assertMatch: "way name=house building=yes";
    167167}
     168
     169/* #2760 */
     170*[/^gpx:/] {
     171  throwWarning: tr("{0} should not be uploaded", "{0.key}");
     172  group: tr("unnecessary tag");
     173  fixRemove: "{0.key}";
     174  assertMatch: "node gpx:time=2018-01-01T12:00:00Z";
     175  assertNoMatch: "node source=gpx:foo";
     176}
  • src/org/openstreetmap/josm/actions/SimplifyWayAction.java

     
    55import static org.openstreetmap.josm.tools.I18n.tr;
    66import static org.openstreetmap.josm.tools.I18n.trn;
    77
     8import java.awt.GridBagLayout;
    89import java.awt.event.ActionEvent;
    910import java.awt.event.KeyEvent;
    1011import java.util.ArrayList;
     
    1718import java.util.Set;
    1819import java.util.stream.Collectors;
    1920
     21import javax.swing.BorderFactory;
     22import javax.swing.JCheckBox;
     23import javax.swing.JLabel;
    2024import javax.swing.JOptionPane;
     25import javax.swing.JPanel;
     26import javax.swing.JSpinner;
     27import javax.swing.SpinnerNumberModel;
    2128import javax.swing.SwingUtilities;
    2229
    2330import org.openstreetmap.josm.command.ChangeCommand;
     
    2431import org.openstreetmap.josm.command.Command;
    2532import org.openstreetmap.josm.command.DeleteCommand;
    2633import org.openstreetmap.josm.command.SequenceCommand;
     34import org.openstreetmap.josm.data.SystemOfMeasurement;
    2735import org.openstreetmap.josm.data.UndoRedoHandler;
    2836import org.openstreetmap.josm.data.osm.DataSet;
    2937import org.openstreetmap.josm.data.osm.Node;
     
    3038import org.openstreetmap.josm.data.osm.OsmPrimitive;
    3139import org.openstreetmap.josm.data.osm.Way;
    3240import org.openstreetmap.josm.data.projection.Ellipsoid;
     41import org.openstreetmap.josm.gui.ExtendedDialog;
    3342import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    3443import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
    3544import org.openstreetmap.josm.gui.MainApplication;
    3645import org.openstreetmap.josm.gui.Notification;
    3746import org.openstreetmap.josm.spi.preferences.Config;
     47import org.openstreetmap.josm.spi.preferences.IPreferences;
     48import org.openstreetmap.josm.tools.GBC;
    3849import org.openstreetmap.josm.tools.ImageProvider;
    3950import org.openstreetmap.josm.tools.Shortcut;
    4051
     
    93104                );
    94105    }
    95106
     107    /**
     108     * Asks the user for max-err value used to simplify ways, if not remembered before
     109     * @param text the text being shown
     110     * @param auto whether it's called automatically (conversion) or by the user
     111     * @return the max-err value or -1 if canceled
     112     */
     113    public static double askSimplifyWays(String text, boolean auto) {
     114        IPreferences s = Config.getPref();
     115        String key = "simplify-way." + (auto ? "auto." : "");
     116        String keyRemember = key + "remember";
     117        String keyError = key + "max-error";
     118
     119        String r = s.get(keyRemember, "ask");
     120        if (auto && "no".equals(r)) {
     121            return -1;
     122        } else if ("yes".equals(r)) {
     123            return s.getDouble(keyError, 3.0);
     124        }
     125
     126        JPanel p = new JPanel(new GridBagLayout());
     127        p.add(new JLabel("<html><body style=\"width: 375px;\">" + text + "<br><br>" +
     128                tr("This reduces unnecessary nodes along the way and is especially recommended if GPS tracks were recorded by time "
     129                 + "(e.g. one point per second) or when the accuracy was low (reduces \"zigzag\" tracks).")
     130                + "</body></html>"), GBC.eol());
     131        p.setBorder(BorderFactory.createEmptyBorder(5, 10, 10, 5));
     132        JPanel q = new JPanel(new GridBagLayout());
     133        q.add(new JLabel(tr("Maximum error (meters): ")));
     134        JSpinner n = new JSpinner(new SpinnerNumberModel(
     135                s.getDouble(keyError, 3.0), 0.01, 100, 0.5));
     136        q.add(n);
     137        q.setBorder(BorderFactory.createEmptyBorder(14, 0, 10, 0));
     138        p.add(q, GBC.eol());
     139        JCheckBox c = new JCheckBox(tr("Do not ask again"));
     140        p.add(c, GBC.eol());
     141
     142        ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(),
     143                tr("Simplify way"), tr("Simplify"),
     144                auto ? tr("Proceed without simplifying") : tr("Cancel"))
     145                .setContent(p)
     146                .configureContextsensitiveHelp(("Action/SimplifyWay"), true);
     147        if (auto) {
     148            ed.setButtonIcons("simplify", "ok");
     149        } else {
     150            ed.setButtonIcons("ok", "cancel");
     151        }
     152
     153        int ret = ed.showDialog().getValue();
     154        double val = (double) n.getValue();
     155        if (ret == 1) {
     156            s.putDouble(keyError, val);
     157            if (c.isSelected()) {
     158                s.put(keyRemember, "yes");
     159            }
     160            return val;
     161        } else {
     162            if (auto && c.isSelected()) { //do not remember cancel for manual simplify, otherwise nothing would happen
     163                s.put(keyRemember, "no");
     164            }
     165            return -1;
     166        }
     167    }
     168
    96169    @Override
    97170    public void actionPerformed(ActionEvent e) {
    98171        DataSet ds = getLayerManager().getEditDataSet();
     
    108181                return;
    109182            }
    110183
    111             Collection<Command> allCommands = new LinkedList<>();
    112             for (Way way: ways) {
    113                 SequenceCommand simplifyCommand = simplifyWay(way);
    114                 if (simplifyCommand == null) {
    115                     continue;
    116                 }
    117                 allCommands.add(simplifyCommand);
     184            String lengthstr = SystemOfMeasurement.getSystemOfMeasurement().getDistText(
     185                    ways.stream().collect(
     186                            Collectors.summingDouble(w -> {
     187                                return w.getLength();
     188                            })));
     189
     190            double err = askSimplifyWays(trn(
     191                    "You are about to simplify {0} way with a total length of {1}.",
     192                    "You are about to simplify {0} ways with a total length of {1}.",
     193                    ways.size(), ways.size(), lengthstr), false);
     194
     195            if (err > 0) {
     196                simplifyWays(ways, err);
    118197            }
    119             if (allCommands.isEmpty()) return;
    120             SequenceCommand rootCommand = new SequenceCommand(
    121                     trn("Simplify {0} way", "Simplify {0} ways", allCommands.size(), allCommands.size()),
    122                     allCommands
    123                     );
    124             UndoRedoHandler.getInstance().add(rootCommand);
    125198        } finally {
    126199            ds.endUpdate();
    127200        }
     
    156229    }
    157230
    158231    /**
    159      * Simplifies a way with default threshold (read from preferences).
    160      *
    161      * @param w the way to simplify
    162      * @return The sequence of commands to run
    163      * @since 6411
    164      */
    165     public final SequenceCommand simplifyWay(Way w) {
    166         return simplifyWay(w, Config.getPref().getDouble("simplify-way.max-error", 3.0));
    167     }
    168 
    169     /**
    170232     * Calculate a set of nodes which occurs more than once in the way
    171233     * @param w the way
    172234     * @return a set of nodes which occurs more than once in the way
     
    182244    }
    183245
    184246    /**
    185      * Simplifies a way with a given threshold.
     247     * Runs the commands to simplify the ways with the given threshold
    186248     *
     249     * @param ways the ways to simplify
     250     * @param threshold the max error threshold
     251     * @since xxx
     252     */
     253    public static void simplifyWays(List<Way> ways, double threshold) {
     254        Collection<Command> allCommands = new LinkedList<>();
     255        for (Way way : ways) {
     256            SequenceCommand simplifyCommand = createSimplifyCommand(way, threshold);
     257            if (simplifyCommand == null) {
     258                continue;
     259            }
     260            allCommands.add(simplifyCommand);
     261        }
     262        if (allCommands.isEmpty())
     263            return;
     264        SequenceCommand rootCommand = new SequenceCommand(
     265                trn("Simplify {0} way", "Simplify {0} ways", allCommands.size(), allCommands.size()),
     266                allCommands);
     267        UndoRedoHandler.getInstance().add(rootCommand);
     268    }
     269
     270    /**
     271     * Creates the SequenceCommand to simplify a way with default threshold.
     272     * @deprecated Replaced by {@link #createSimplifyCommand(Way)}. You can also use {@link #simplifyWays(List, double)} directly.
     273     *
    187274     * @param w the way to simplify
     275     * @return The sequence of commands to run
     276     * @since 6411
     277     */
     278    @Deprecated
     279    public final SequenceCommand simplifyWay(Way w) {
     280        return createSimplifyCommand(w);
     281    }
     282
     283    /**
     284     * Creates the SequenceCommand to simplify a way with a given threshold.
     285     * @deprecated Replaced by {@link #createSimplifyCommand(Way, double)}. You can also use {@link #simplifyWays(List, double)} directly.
     286     *
     287     * @param w the way to simplify
    188288     * @param threshold the max error threshold
    189289     * @return The sequence of commands to run
    190290     * @since 6411
    191291     */
     292    @Deprecated
    192293    public static SequenceCommand simplifyWay(Way w, double threshold) {
     294        return createSimplifyCommand(w, threshold);
     295    }
     296
     297    /**
     298     * Creates the SequenceCommand to simplify a way with default threshold.
     299     *
     300     * @param w the way to simplify
     301     * @return The sequence of commands to run
     302     * @since xxx
     303     */
     304    public final SequenceCommand createSimplifyCommand(Way w) {
     305        return createSimplifyCommand(w, Config.getPref().getDouble("simplify-way.max-error", 3.0));
     306    }
     307
     308    /**
     309     * Creates the SequenceCommand to simplify a way with a given threshold.
     310     *
     311     * @param w the way to simplify
     312     * @param threshold the max error threshold
     313     * @return The sequence of commands to run
     314     * @since xxx
     315     */
     316    public static SequenceCommand createSimplifyCommand(Way w, double threshold) {
    193317        int lower = 0;
    194318        int i = 0;
    195319
  • src/org/openstreetmap/josm/data/gpx/GpxConstants.java

     
    1414 */
    1515public interface GpxConstants {
    1616
     17    /** Prefix used for attributes when converting to OSM data */
     18    String GPX_PREFIX = "gpx:";
     19
    1720    /** GPS name of the element. This field will be transferred to and from the GPS.
    1821     *  GPX does not place restrictions on the length of this field or the characters contained in it.
    1922     *  It is up to the receiving application to validate the field before sending it to the GPS. */
  • src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java

     
    2020import java.util.concurrent.atomic.AtomicLong;
    2121import java.util.function.BiPredicate;
    2222
     23import org.openstreetmap.josm.data.gpx.GpxConstants;
    2324import org.openstreetmap.josm.spi.preferences.Config;
    2425import org.openstreetmap.josm.tools.Utils;
    2526
     
    740741        if (uninteresting == null) {
    741742            List<String> l = new LinkedList<>(Arrays.asList(
    742743                "source", "source_ref", "source:", "comment",
    743                 "watch", "watch:", "description", "attribution"));
     744                "watch", "watch:", "description", "attribution", GpxConstants.GPX_PREFIX));
    744745            l.addAll(getDiscardableKeys());
    745746            l.addAll(getWorkInProgressKeys());
    746747            uninteresting = new HashSet<>(Config.getPref().getList("tags.uninteresting", l));
  • src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

     
    3131import java.util.LinkedHashMap;
    3232import java.util.List;
    3333import java.util.Map;
     34import java.util.Optional;
    3435import java.util.Set;
    3536import java.util.concurrent.CopyOnWriteArrayList;
    3637import java.util.concurrent.atomic.AtomicBoolean;
     
    779780            Collection<Collection<WayPoint>> trk = new ArrayList<>();
    780781            Map<String, Object> trkAttr = new HashMap<>();
    781782
    782             String name = w.get("name");
     783            String name = gpxVal(w, "name");
    783784            if (name != null) {
    784785                trkAttr.put("name", name);
    785786            }
     
    806807
    807808    private static boolean containsOnlyGpxTags(Tagged t) {
    808809        for (String key : t.getKeys().keySet()) {
    809             if (!GpxConstants.WPT_KEYS.contains(key)) {
     810            if (!GpxConstants.WPT_KEYS.contains(key) && !key.startsWith(GpxConstants.GPX_PREFIX)) {
    810811                return false;
    811812            }
    812813        }
     
    814815    }
    815816
    816817    /**
     818     * Reads the Gpx key from the given {@link OsmPrimitive}, with or without &quot;gpx:&quot; prefix
     819     * @param node
     820     * @param key
     821     * @return the value or <code>null</code> if not present
     822     */
     823    public static String gpxVal(OsmPrimitive node, String key) {
     824        return Optional.ofNullable(node.get(GpxConstants.GPX_PREFIX + key)).orElse(node.get(key));
     825    }
     826
     827    /**
    817828     * @param n the {@code Node} to convert
    818829     * @return {@code WayPoint} object
    819830     * @since 13210
     
    836847        addDoubleIfPresent(wpt, n, GpxConstants.PT_ELE);
    837848
    838849        try {
     850            String v;
    839851            if (time > Long.MIN_VALUE) {
    840852                wpt.setTimeInMillis(time);
    841             } else if (n.hasKey(GpxConstants.PT_TIME)) {
    842                 wpt.setTimeInMillis(DateUtils.tsFromString(n.get(GpxConstants.PT_TIME)));
     853            } else if ((v = gpxVal(n, GpxConstants.PT_TIME)) != null) {
     854                wpt.setTimeInMillis(DateUtils.tsFromString(v));
    843855            } else if (!n.isTimestampEmpty()) {
    844856                wpt.setTime(Integer.toUnsignedLong(n.getRawTimestamp()));
    845857            }
     
    859871
    860872        Collection<GpxLink> links = new ArrayList<>();
    861873        for (String key : new String[]{"link", "url", "website", "contact:website"}) {
    862             String value = n.get(key);
     874            String value = gpxVal(n, key);
    863875            if (value != null) {
    864876                links.add(new GpxLink(value));
    865877            }
     
    897909        List<String> possibleKeys = new ArrayList<>(Arrays.asList(osmKeys));
    898910        possibleKeys.add(0, gpxKey);
    899911        for (String key : possibleKeys) {
    900             String value = p.get(key);
     912            String value = gpxVal(p, key);
    901913            if (value != null) {
    902914                try {
    903915                    int i = Integer.parseInt(value);
     
    918930        List<String> possibleKeys = new ArrayList<>(Arrays.asList(osmKeys));
    919931        possibleKeys.add(0, gpxKey);
    920932        for (String key : possibleKeys) {
    921             String value = p.get(key);
     933            String value = gpxVal(p, key);
    922934            if (value != null) {
    923935                try {
    924936                    double d = Double.parseDouble(value);
     
    938950        List<String> possibleKeys = new ArrayList<>(Arrays.asList(osmKeys));
    939951        possibleKeys.add(0, gpxKey);
    940952        for (String key : possibleKeys) {
    941             String value = p.get(key);
     953            String value = gpxVal(p, key);
    942954            // Sanity checks
    943955            if (value != null && (!GpxConstants.PT_FIX.equals(gpxKey) || GpxConstants.FIX_VALUES.contains(value))) {
    944956                wpt.put(gpxKey, value);
  • src/org/openstreetmap/josm/gui/layer/gpx/ConvertFromGpxLayerAction.java

     
    5454    public DataSet convert() {
    5555        final DataSet ds = new DataSet();
    5656
    57         List<String> keys = new ArrayList<>();
     57        List<String> keys = new ArrayList<>(); //note that items in this list don't have the GPX_PREFIX
    5858        String convertTags = Config.getPref().get(GPX_SETTING, "ask");
    5959        boolean check = "list".equals(convertTags) || "ask".equals(convertTags);
    6060        boolean none = "no".equals(convertTags); // no need to convert tags when no dialog will be shown anyways
     
    7272                        }
    7373                        if (!none && (obj instanceof String || obj instanceof Number)) {
    7474                            // only convert when required
    75                             n.put(key, obj.toString());
     75                            n.put(GpxConstants.GPX_PREFIX + key, obj.toString());
    7676                        } else if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) {
    7777                            // timestamps should always be converted
    7878                            Date date = (Date) obj;
    7979                            if (!none) { //... but the tag will only be set when required
    80                                 n.put(key, DateUtils.fromDate(date));
     80                                n.put(GpxConstants.GPX_PREFIX + key, DateUtils.fromDate(date));
    8181                            }
    8282                            n.setTimestamp(date);
    8383                        }
     
    125125    /**
    126126     * Filters the tags of the given {@link DataSet}
    127127     * @param ds The {@link DataSet}
    128      * @param listPos A {@code List<String>} containing the tags to be kept, can be {@code null} if all tags are to be removed
     128     * @param listPos A {@code List<String>} containing the tags (without prefix) to be kept, can be {@code null} if all tags are to be removed
    129129     * @return The {@link DataSet}
    130130     * @since 14103
    131131     */
     
    133133        Collection<Node> nodes = ds.getNodes();
    134134        for (Node n : nodes) {
    135135            for (String key : n.keySet()) {
    136                 if (listPos == null || !listPos.contains(key)) {
     136                if (listPos == null || !listPos.contains(key.substring(GpxConstants.GPX_PREFIX.length()))) {
    137137                    n.put(key, null);
    138138                }
    139139            }
  • src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java

     
    77import java.awt.GridBagLayout;
    88import java.awt.event.ActionEvent;
    99import java.io.File;
     10import java.util.ArrayList;
    1011
    1112import javax.swing.AbstractAction;
    1213import javax.swing.JLabel;
     
    1314import javax.swing.JOptionPane;
    1415import javax.swing.JPanel;
    1516
     17import org.openstreetmap.josm.actions.SimplifyWayAction;
    1618import org.openstreetmap.josm.data.osm.DataSet;
    1719import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    1820import org.openstreetmap.josm.gui.MainApplication;
     
    6264        }
    6365        final DataSet ds = convert();
    6466        if (ds != null) {
     67            double err = SimplifyWayAction.askSimplifyWays(tr("Would you like to simplify the ways in the converted layer?"), true);
     68            if (err > 0) {
     69                SimplifyWayAction.simplifyWays(new ArrayList<>(ds.getWays()), err);
     70            }
    6571            final OsmDataLayer osmLayer = new OsmDataLayer(ds, tr("Converted from: {0}", layer.getName()), null);
    6672            if (layer.getAssociatedFile() != null) {
    6773                osmLayer.setAssociatedFile(new File(layer.getAssociatedFile().getParentFile(),
  • test/unit/org/openstreetmap/josm/actions/SimplifyWayActionTest.java

     
    129129        final Way w = new Way();
    130130        Stream.of(n1, n2, n3, n4, w).forEach(ds::addPrimitive);
    131131        Stream.of(n1, n2, n3, n4, n1).forEach(w::addNode);
    132         final SequenceCommand command = action.simplifyWay(w);
     132        final SequenceCommand command = SimplifyWayAction.createSimplifyCommand(w, 3);
    133133        assertNotNull(command);
    134134        assertEquals(2, command.getChildren().size());
    135135        final Collection<DeleteCommand> deleteCommands = Utils.filteredCollection(command.getChildren(), DeleteCommand.class);
  • test/unit/org/openstreetmap/josm/gui/layer/OsmDataLayerTest.java

     
    215215                "<?xml version='1.0' encoding='UTF-8'?>\n" +
    216216                "<osm version='0.6' upload='false' generator='JOSM'>\n" +
    217217                "  <node id='-546306' timestamp='2018-08-01T10:00:00Z' lat='47.0' lon='9.0'>\n" +
    218                 "    <tag k='ele' v='123' />\n" +
    219                 "    <tag k='time' v='2018-08-01T10:00:00Z' />\n" +
     218                "    <tag k='gpx:ele' v='123' />\n" +
     219                "    <tag k='gpx:time' v='2018-08-01T10:00:00Z' />\n" +
    220220                "  </node>\n" +
    221221                "  <node id='-546307' timestamp='2018-08-01T10:01:00Z' lat='47.1' lon='9.1'>\n" +
    222222                "    <tag k='ele' v='456' />\n" +
    223                 "    <tag k='time' v='2018-08-01T10:01:00Z' />\n" +
     223                "    <tag k='gpx:time' v='2018-08-01T10:01:00Z' />\n" +
    224224                "  </node>\n" +
    225225                "  <node id='-546308' timestamp='2018-08-01T10:02:00Z' lat='47.05' lon='9.05'>\n" +
    226226                "    <tag k='ele' v='789' />\n" +