001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer.gpx; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.GridBagLayout; 007import java.awt.event.ActionEvent; 008import java.awt.event.ActionListener; 009import java.util.ArrayList; 010import java.util.Collection; 011import java.util.Date; 012import java.util.List; 013import java.util.Map.Entry; 014 015import javax.swing.BorderFactory; 016import javax.swing.ButtonGroup; 017import javax.swing.JCheckBox; 018import javax.swing.JLabel; 019import javax.swing.JPanel; 020import javax.swing.JRadioButton; 021 022import org.openstreetmap.josm.data.gpx.GpxConstants; 023import org.openstreetmap.josm.data.gpx.GpxTrack; 024import org.openstreetmap.josm.data.gpx.GpxTrackSegment; 025import org.openstreetmap.josm.data.gpx.WayPoint; 026import org.openstreetmap.josm.data.osm.DataSet; 027import org.openstreetmap.josm.data.osm.Node; 028import org.openstreetmap.josm.data.osm.Way; 029import org.openstreetmap.josm.gui.ExtendedDialog; 030import org.openstreetmap.josm.gui.MainApplication; 031import org.openstreetmap.josm.gui.layer.GpxLayer; 032import org.openstreetmap.josm.gui.layer.OsmDataLayer; 033import org.openstreetmap.josm.spi.preferences.Config; 034import org.openstreetmap.josm.tools.GBC; 035import org.openstreetmap.josm.tools.date.DateUtils; 036 037/** 038 * Converts a {@link GpxLayer} to a {@link OsmDataLayer}. 039 * @since 14129 (extracted from {@link ConvertToDataLayerAction}) 040 */ 041public class ConvertFromGpxLayerAction extends ConvertToDataLayerAction<GpxLayer> { 042 043 private static final String GPX_SETTING = "gpx.convert-tags"; 044 045 /** 046 * Creates a new {@code FromGpxLayer}. 047 * @param layer the source layer 048 */ 049 public ConvertFromGpxLayerAction(GpxLayer layer) { 050 super(layer); 051 } 052 053 @Override 054 public DataSet convert() { 055 final DataSet ds = new DataSet(); 056 057 List<String> keys = new ArrayList<>(); 058 String convertTags = Config.getPref().get(GPX_SETTING, "ask"); 059 boolean check = "list".equals(convertTags) || "ask".equals(convertTags); 060 boolean none = "no".equals(convertTags); // no need to convert tags when no dialog will be shown anyways 061 062 for (GpxTrack trk : layer.data.getTracks()) { 063 for (GpxTrackSegment segment : trk.getSegments()) { 064 List<Node> nodes = new ArrayList<>(); 065 for (WayPoint p : segment.getWayPoints()) { 066 Node n = new Node(p.getCoor()); 067 for (Entry<String, Object> entry : p.attr.entrySet()) { 068 String key = entry.getKey(); 069 Object obj = p.get(key); 070 if (check && !keys.contains(key) && (obj instanceof String || obj instanceof Number || obj instanceof Date)) { 071 keys.add(key); 072 } 073 if (!none && (obj instanceof String || obj instanceof Number)) { 074 // only convert when required 075 n.put(key, obj.toString()); 076 } else if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) { 077 // timestamps should always be converted 078 Date date = (Date) obj; 079 if (!none) { //... but the tag will only be set when required 080 n.put(key, DateUtils.fromDate(date)); 081 } 082 n.setTimestamp(date); 083 } 084 } 085 ds.addPrimitive(n); 086 nodes.add(n); 087 } 088 Way w = new Way(); 089 w.setNodes(nodes); 090 ds.addPrimitive(w); 091 } 092 } 093 //gpx.convert-tags: all, list, *ask, no 094 //gpx.convert-tags.last: *all, list, no 095 //gpx.convert-tags.list.yes 096 //gpx.convert-tags.list.no 097 List<String> listPos = Config.getPref().getList(GPX_SETTING + ".list.yes"); 098 List<String> listNeg = Config.getPref().getList(GPX_SETTING + ".list.no"); 099 if (check && !keys.isEmpty()) { 100 // Either "list" or "ask" was stored in the settings, so the Nodes have to be filtered after all tags have been processed 101 List<String> allTags = new ArrayList<>(listPos); 102 allTags.addAll(listNeg); 103 if (!allTags.containsAll(keys) || "ask".equals(convertTags)) { 104 // not all keys are in positive/negative list, so we have to ask the user 105 TagConversionDialogResponse res = showTagConversionDialog(keys, listPos, listNeg); 106 if (res.sel == null) { 107 return null; 108 } 109 listPos = res.listPos; 110 111 if ("no".equals(res.sel)) { 112 // User just chose not to convert any tags, but that was unknown before the initial conversion 113 return filterDataSet(ds, null); 114 } else if ("all".equals(res.sel)) { 115 return ds; 116 } 117 } 118 if (!listPos.containsAll(keys)) { 119 return filterDataSet(ds, listPos); 120 } 121 } 122 return ds; 123 } 124 125 /** 126 * Filters the tags of the given {@link DataSet} 127 * @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 129 * @return The {@link DataSet} 130 * @since 14103 131 */ 132 public DataSet filterDataSet(DataSet ds, List<String> listPos) { 133 Collection<Node> nodes = ds.getNodes(); 134 for (Node n : nodes) { 135 for (String key : n.keySet()) { 136 if (listPos == null || !listPos.contains(key)) { 137 n.put(key, null); 138 } 139 } 140 } 141 return ds; 142 } 143 144 /** 145 * Shows the TagConversionDialog asking the user whether to keep all, some or no tags 146 * @param keys The keys present during the current conversion 147 * @param listPos The keys that were previously selected 148 * @param listNeg The keys that were previously unselected 149 * @return {@link TagConversionDialogResponse} containing the selection 150 */ 151 private static TagConversionDialogResponse showTagConversionDialog(List<String> keys, List<String> listPos, List<String> listNeg) { 152 TagConversionDialogResponse res = new TagConversionDialogResponse(listPos, listNeg); 153 String lSel = Config.getPref().get(GPX_SETTING + ".last", "all"); 154 155 JPanel p = new JPanel(new GridBagLayout()); 156 ButtonGroup r = new ButtonGroup(); 157 158 p.add(new JLabel( 159 tr("The GPX layer contains fields that can be converted to OSM tags. How would you like to proceed?")), 160 GBC.eol()); 161 JRadioButton rAll = new JRadioButton(tr("Convert all fields"), "all".equals(lSel)); 162 r.add(rAll); 163 p.add(rAll, GBC.eol()); 164 165 JRadioButton rList = new JRadioButton(tr("Only convert the following fields:"), "list".equals(lSel)); 166 r.add(rList); 167 p.add(rList, GBC.eol()); 168 169 JPanel q = new JPanel(); 170 171 List<JCheckBox> checkList = new ArrayList<>(); 172 for (String key : keys) { 173 JCheckBox cTmp = new JCheckBox(key, !listNeg.contains(key)); 174 checkList.add(cTmp); 175 q.add(cTmp); 176 } 177 178 q.setBorder(BorderFactory.createEmptyBorder(0, 20, 5, 0)); 179 p.add(q, GBC.eol()); 180 181 JRadioButton rNone = new JRadioButton(tr("Do not convert any fields"), "no".equals(lSel)); 182 r.add(rNone); 183 p.add(rNone, GBC.eol()); 184 185 ActionListener enabler = new TagConversionDialogRadioButtonActionListener(checkList, true); 186 ActionListener disabler = new TagConversionDialogRadioButtonActionListener(checkList, false); 187 188 if (!"list".equals(lSel)) { 189 disabler.actionPerformed(null); 190 } 191 192 rAll.addActionListener(disabler); 193 rList.addActionListener(enabler); 194 rNone.addActionListener(disabler); 195 196 ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), tr("Options"), 197 tr("Convert"), tr("Convert and remember selection"), tr("Cancel")) 198 .setButtonIcons("exportgpx", "exportgpx", "cancel").setContent(p); 199 int ret = ed.showDialog().getValue(); 200 201 if (ret == 1 || ret == 2) { 202 for (JCheckBox cItem : checkList) { 203 String key = cItem.getText(); 204 if (cItem.isSelected()) { 205 if (!res.listPos.contains(key)) { 206 res.listPos.add(key); 207 } 208 res.listNeg.remove(key); 209 } else { 210 if (!res.listNeg.contains(key)) { 211 res.listNeg.add(key); 212 } 213 res.listPos.remove(key); 214 } 215 } 216 if (rAll.isSelected()) { 217 res.sel = "all"; 218 } else if (rNone.isSelected()) { 219 res.sel = "no"; 220 } 221 Config.getPref().put(GPX_SETTING + ".last", res.sel); 222 if (ret == 2) { 223 Config.getPref().put(GPX_SETTING, res.sel); 224 } else { 225 Config.getPref().put(GPX_SETTING, "ask"); 226 } 227 Config.getPref().putList(GPX_SETTING + ".list.yes", res.listPos); 228 Config.getPref().putList(GPX_SETTING + ".list.no", res.listNeg); 229 } else { 230 res.sel = null; 231 } 232 return res; 233 } 234 235 private static class TagConversionDialogResponse { 236 237 final List<String> listPos; 238 final List<String> listNeg; 239 String sel = "list"; 240 241 TagConversionDialogResponse(List<String> p, List<String> n) { 242 listPos = new ArrayList<>(p); 243 listNeg = new ArrayList<>(n); 244 } 245 } 246 247 private static class TagConversionDialogRadioButtonActionListener implements ActionListener { 248 249 private final boolean enable; 250 private final List<JCheckBox> checkList; 251 252 TagConversionDialogRadioButtonActionListener(List<JCheckBox> chks, boolean en) { 253 enable = en; 254 checkList = chks; 255 } 256 257 @Override 258 public void actionPerformed(ActionEvent arg0) { 259 for (JCheckBox ch : checkList) { 260 ch.setEnabled(enable); 261 } 262 } 263 } 264}