From f3035d89f2db9f16c5c0cdb1f43fb4338f042781 Mon Sep 17 00:00:00 2001 From: Taylor Smock Date: Tue, 29 Oct 2019 09:40:15 -0600 Subject: [PATCH] Allow users to modify tags (hidden in non-expert mode) Signed-off-by: Taylor Smock --- .../mapwithai/MapWithAIPreferences.java | 118 +++++++++++++----- .../mapwithai/ReplacementPreferenceTable.java | 52 ++++++++ .../mapwithai/backend/GetDataRunnable.java | 16 +++ .../backend/MapWithAIPreferenceHelper.java | 27 ++++ 4 files changed, 183 insertions(+), 30 deletions(-) create mode 100644 src/main/java/org/openstreetmap/josm/plugins/mapwithai/ReplacementPreferenceTable.java diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPreferences.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPreferences.java index 9aba471..fb52364 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPreferences.java +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPreferences.java @@ -3,28 +3,43 @@ package org.openstreetmap.josm.plugins.mapwithai; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Component; +import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; -import java.awt.Insets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import javax.swing.Box; +import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTextField; import javax.swing.SpinnerNumberModel; +import org.openstreetmap.josm.actions.ExpertToggleAction; import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane; import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting; import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting; +import org.openstreetmap.josm.gui.preferences.advanced.PrefEntry; import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIPreferenceHelper; +import org.openstreetmap.josm.spi.preferences.StringSetting; +import org.openstreetmap.josm.tools.GBC; public class MapWithAIPreferences implements SubPreferenceSetting { private final JComboBox possibleMapWithAIApiUrl; private final JCheckBox switchLayerCheckBox; private final JCheckBox mergeBuildingAddressCheckBox; private final JSpinner maximumAdditionSpinner; + private final ReplacementPreferenceTable table; + private final List displayData; public MapWithAIPreferences() { possibleMapWithAIApiUrl = new JComboBox<>(); @@ -32,6 +47,16 @@ public class MapWithAIPreferences implements SubPreferenceSetting { maximumAdditionSpinner = new JSpinner( new SpinnerNumberModel(MapWithAIPreferenceHelper.getMaximumAddition(), 0, 100, 1)); mergeBuildingAddressCheckBox = new JCheckBox(); + displayData = new ArrayList<>(); + fillDisplayData(displayData); + table = new ReplacementPreferenceTable(displayData); + } + + private static void fillDisplayData(List list) { + Map current = new TreeMap<>(MapWithAIPreferenceHelper.getReplacementTags()); + for (Entry entry : current.entrySet()) { + list.add(new PrefEntry(entry.getKey(), new StringSetting(entry.getValue()), new StringSetting(null), false)); + } } @Override @@ -40,9 +65,9 @@ public class MapWithAIPreferences implements SubPreferenceSetting { final JLabel switchLayer = new JLabel(tr("Automatically switch layers")); final JLabel maximumAddition = new JLabel(tr("Maximum features (add)")); final JLabel mergeBuildingWithAddress = new JLabel(tr("Merge address nodes and buildings")); - final JPanel container = new JPanel(new GridBagLayout()); - container.setAlignmentY(Component.TOP_ALIGNMENT); - final GridBagConstraints constraints = new GridBagConstraints(); + final JPanel nonExpert = new JPanel(new GridBagLayout()); + nonExpert.setAlignmentY(Component.TOP_ALIGNMENT); + nonExpert.setAlignmentX(Component.LEFT_ALIGNMENT); possibleMapWithAIApiUrl.setEditable(true); possibleMapWithAIApiUrl.setPrototypeDisplayValue("https://example.url/some/end/point"); @@ -58,38 +83,64 @@ public class MapWithAIPreferences implements SubPreferenceSetting { switchLayerCheckBox.setSelected(MapWithAIPreferenceHelper.isSwitchLayers()); mergeBuildingAddressCheckBox.setSelected(MapWithAIPreferenceHelper.isMergeBuildingAddress()); - constraints.gridx = 0; - constraints.gridy = 0; - constraints.weightx = .1; - constraints.weighty = 0; - constraints.insets = new Insets(5, 10, 5, 10); - constraints.anchor = GridBagConstraints.EAST; - constraints.fill = GridBagConstraints.HORIZONTAL; - container.add(mapWithAIApiUrl, constraints); + nonExpert.add(mapWithAIApiUrl); - constraints.gridx++; - container.add(possibleMapWithAIApiUrl, constraints); + nonExpert.add(possibleMapWithAIApiUrl, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); - constraints.gridx--; - constraints.gridy++; - container.add(switchLayer, constraints); + nonExpert.add(switchLayer); - constraints.gridx++; - container.add(switchLayerCheckBox, constraints); + nonExpert.add(switchLayerCheckBox, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); - constraints.gridx--; - constraints.gridy++; - container.add(maximumAddition, constraints); - constraints.gridx++; - container.add(maximumAdditionSpinner, constraints); + nonExpert.add(maximumAddition); + nonExpert.add(maximumAdditionSpinner, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); - constraints.gridx--; - constraints.gridy++; - container.add(mergeBuildingWithAddress, constraints); - constraints.gridx++; - container.add(mergeBuildingAddressCheckBox, constraints); + nonExpert.add(mergeBuildingWithAddress); + nonExpert.add(mergeBuildingAddressCheckBox, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); - getTabPreferenceSetting(gui).addSubTab(this, MapWithAIPlugin.NAME, container); + final JPanel expert = new JPanel(new GridBagLayout()); + expert.add(Box.createHorizontalGlue(), GBC.std().fill(GridBagConstraints.HORIZONTAL)); + JScrollPane scroll = new JScrollPane(table); + expert.add(scroll, GBC.eol().fill(GridBagConstraints.BOTH)); + scroll.setPreferredSize(new Dimension(400, 200)); + + JButton add = new JButton(tr("Add")); + expert.add(Box.createHorizontalGlue(), GBC.std().fill(GridBagConstraints.HORIZONTAL)); + expert.add(add, GBC.std().insets(0, 5, 0, 0)); + add.addActionListener(e -> { + PrefEntry pe = table.addPreference(gui); + if (pe != null && pe.getValue() instanceof StringSetting) { + displayData.add(pe); + Collections.sort(displayData); + table.fireDataChanged(); + } + }); + + JButton edit = new JButton(tr("Edit")); + expert.add(edit, GBC.std().insets(5, 5, 0, 0)); + edit.addActionListener(e -> { + List toEdit = table.getSelectedItems(); + if (toEdit.size() == 1) { + table.editPreference(gui); + } + }); + + JButton delete = new JButton(tr("Delete")); + expert.add(delete, GBC.std().insets(5, 5, 0, 0)); + delete.addActionListener(e -> { + List toRemove = table.getSelectedItems(); + if (!toRemove.isEmpty()) { + displayData.removeAll(toRemove); + } + table.fireDataChanged(); + }); + + ExpertToggleAction.addVisibilitySwitcher(expert); + + JPanel pane = new JPanel(new GridBagLayout()); + pane.add(nonExpert, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + pane.add(expert); + + getTabPreferenceSetting(gui).addSubTab(this, MapWithAIPlugin.NAME, pane); } @Override @@ -100,9 +151,16 @@ public class MapWithAIPreferences implements SubPreferenceSetting { if (value instanceof Number) { MapWithAIPreferenceHelper.setMaximumAddition(((Number) value).intValue(), true); } + MapWithAIPreferenceHelper.setReplacementTags(convertPrefToMap(displayData)); return false; } + private static Map convertPrefToMap(List displayData) { + Map returnMap = displayData.isEmpty() ? Collections.emptyMap() : new TreeMap<>(); + displayData.forEach(entry -> returnMap.put(entry.getKey(), entry.getValue().getValue().toString())); + return returnMap; + } + @Override public boolean isExpert() { return false; diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/ReplacementPreferenceTable.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/ReplacementPreferenceTable.java new file mode 100644 index 0000000..aadb8cd --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/ReplacementPreferenceTable.java @@ -0,0 +1,52 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.mapwithai; + +import static org.openstreetmap.josm.tools.I18n.tr; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import org.openstreetmap.josm.gui.ExtendedDialog; +import org.openstreetmap.josm.gui.preferences.advanced.PrefEntry; +import org.openstreetmap.josm.gui.preferences.advanced.PreferencesTable; +import org.openstreetmap.josm.gui.widgets.JosmTextField; +import org.openstreetmap.josm.spi.preferences.StringSetting; +import org.openstreetmap.josm.tools.GBC; + +/** + * @author Taylor Smock + */ +public class ReplacementPreferenceTable extends PreferencesTable { + private static final long serialVersionUID = 8057277761625324262L; + + public ReplacementPreferenceTable(List displayData) { + super(displayData); + } + + @Override + public PrefEntry addPreference(final JComponent gui) { + JPanel p = new JPanel(new GridBagLayout()); + p.add(new JLabel(tr("Original Tag")), GBC.std().insets(0, 0, 5, 0)); + JosmTextField tkey = new JosmTextField("", 50); + p.add(tkey, GBC.eop().insets(5, 0, 0, 0).fill(GridBagConstraints.HORIZONTAL)); + p.add(new JLabel(tr("Replacement Tag")), GBC.std().insets(0, 0, 5, 0)); + JosmTextField tValue = new JosmTextField("", 50); + p.add(tValue, GBC.eop().insets(5, 0, 0, 0).fill(GridBagConstraints.HORIZONTAL)); + + PrefEntry pe = null; + if (askAddSetting(gui, p)) { + pe = new PrefEntry(tkey.getText(), new StringSetting(tValue.getText()), new StringSetting(null), false); + } + return pe; + } + + private static boolean askAddSetting(JComponent gui, JPanel p) { + return new ExtendedDialog(gui, tr("Add setting"), tr("OK"), tr("Cancel")).setContent(p) + .setButtonIcons("ok", "cancel").showDialog().getValue() == 1; + } +} diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/GetDataRunnable.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/GetDataRunnable.java index 3a49ff0..48d2cb8 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/GetDataRunnable.java +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/GetDataRunnable.java @@ -22,6 +22,7 @@ import org.openstreetmap.josm.data.osm.BBox; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.data.osm.Tag; import org.openstreetmap.josm.data.osm.UploadPolicy; import org.openstreetmap.josm.gui.progress.NullProgressMonitor; import org.openstreetmap.josm.gui.progress.ProgressMonitor; @@ -31,6 +32,7 @@ import org.openstreetmap.josm.plugins.mapwithai.commands.MergeDuplicateWays; import org.openstreetmap.josm.tools.HttpClient; import org.openstreetmap.josm.tools.HttpClient.Response; import org.openstreetmap.josm.tools.Logging; +import org.openstreetmap.josm.tools.Pair; /** * Get data in a parallel manner @@ -92,6 +94,7 @@ public class GetDataRunnable extends RecursiveTask { synchronized (LOCK) { /* Microsoft buildings don't have a source, so we add one */ MapWithAIDataUtils.addSourceTags(dataSet, "building", "Microsoft"); + replaceTags(dataSet); removeCommonTags(dataSet); mergeNodes(dataSet); // filterDataSet(dataSet); @@ -103,6 +106,19 @@ public class GetDataRunnable extends RecursiveTask { return dataSet; } + /** + * Replace tags in a dataset with a set of replacement tags + * + * @param dataSet The dataset with primitives to change + */ + public static void replaceTags(DataSet dataSet) { + Map replaceTags = MapWithAIPreferenceHelper.getReplacementTags().entrySet().parallelStream() + .map(entry -> new Pair<>(Tag.ofString(entry.getKey()), Tag.ofString(entry.getValue()))) + .collect(Collectors.toMap(pair -> pair.a, pair -> pair.b)); + replaceTags.forEach((orig, replace) -> dataSet.allNonDeletedPrimitives().parallelStream() + .filter(prim -> prim.hasTag(orig.getKey(), orig.getValue())).forEach(prim -> prim.put(replace))); + } + private static void cleanupDataSet(DataSet dataSet) { Map origIds = dataSet.allPrimitives().parallelStream() .filter(prim -> prim.hasKey("orig_id")).distinct() diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIPreferenceHelper.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIPreferenceHelper.java index bd814a1..2c7884e 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIPreferenceHelper.java +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIPreferenceHelper.java @@ -3,8 +3,12 @@ package org.openstreetmap.josm.plugins.mapwithai.backend; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import org.openstreetmap.josm.data.osm.Tag; import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin; import org.openstreetmap.josm.spi.preferences.Config; @@ -183,7 +187,30 @@ public final class MapWithAIPreferenceHelper { } } + /** + * Get the maximum distance for a node to be considered a duplicate + * + * @return The max distance between nodes for duplicates + */ public static double getMaxNodeDistance() { return Config.getPref().getDouble(MapWithAIPlugin.NAME.concat(".duplicatenodedistance"), 0.6); } + + /** + * @return A map of tags to replacement tags (use {@link Tag.ofString} to parse) + */ + public static Map getReplacementTags() { + Map defaultMap = Collections.emptyMap(); + List> listOfMaps = Config.getPref() + .getListOfMaps(MapWithAIPlugin.NAME.concat(".replacementtags"), Arrays.asList(defaultMap)); + return listOfMaps.isEmpty() ? defaultMap : listOfMaps.get(0); + } + + /** + * @param tagsToReplace set the tags to replace + */ + public static void setReplacementTags(Map tagsToReplace) { + List> tags = tagsToReplace.isEmpty() ? null : Arrays.asList(new TreeMap<>(tagsToReplace)); + Config.getPref().putListOfMaps(MapWithAIPlugin.NAME.concat(".replacementtags"), tags); + } }