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 Date)) { 071 keys.add(key); 072 } 073 if (obj instanceof String) { 074 String str = (String) obj; 075 if (!none) { 076 // only convert when required 077 n.put(key, str); 078 } 079 } else if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) { 080 // timestamps should always be converted 081 Date date = (Date) obj; 082 if (!none) { //... but the tag will only be set when required 083 n.put(key, DateUtils.fromDate(date)); 084 } 085 n.setTimestamp(date); 086 } 087 } 088 ds.addPrimitive(n); 089 nodes.add(n); 090 } 091 Way w = new Way(); 092 w.setNodes(nodes); 093 ds.addPrimitive(w); 094 } 095 } 096 //gpx.convert-tags: all, list, *ask, no 097 //gpx.convert-tags.last: *all, list, no 098 //gpx.convert-tags.list.yes 099 //gpx.convert-tags.list.no 100 List<String> listPos = Config.getPref().getList(GPX_SETTING + ".list.yes"); 101 List<String> listNeg = Config.getPref().getList(GPX_SETTING + ".list.no"); 102 if (check && !keys.isEmpty()) { 103 // Either "list" or "ask" was stored in the settings, so the Nodes have to be filtered after all tags have been processed 104 List<String> allTags = new ArrayList<>(listPos); 105 allTags.addAll(listNeg); 106 if (!allTags.containsAll(keys) || "ask".equals(convertTags)) { 107 // not all keys are in positive/negative list, so we have to ask the user 108 TagConversionDialogResponse res = showTagConversionDialog(keys, listPos, listNeg); 109 if (res.sel == null) { 110 return null; 111 } 112 listPos = res.listPos; 113 114 if ("no".equals(res.sel)) { 115 // User just chose not to convert any tags, but that was unknown before the initial conversion 116 return filterDataSet(ds, null); 117 } else if ("all".equals(res.sel)) { 118 return ds; 119 } 120 } 121 if (!listPos.containsAll(keys)) { 122 return filterDataSet(ds, listPos); 123 } 124 } 125 return ds; 126 } 127 128 /** 129 * Filters the tags of the given {@link DataSet} 130 * @param ds The {@link DataSet} 131 * @param listPos A {@code List<String>} containing the tags to be kept, can be {@code null} if all tags are to be removed 132 * @return The {@link DataSet} 133 * @since 14103 134 */ 135 public DataSet filterDataSet(DataSet ds, List<String> listPos) { 136 Collection<Node> nodes = ds.getNodes(); 137 for (Node n : nodes) { 138 for (String key : n.keySet()) { 139 if (listPos == null || !listPos.contains(key)) { 140 n.put(key, null); 141 } 142 } 143 } 144 return ds; 145 } 146 147 /** 148 * Shows the TagConversionDialog asking the user whether to keep all, some or no tags 149 * @param keys The keys present during the current conversion 150 * @param listPos The keys that were previously selected 151 * @param listNeg The keys that were previously unselected 152 * @return {@link TagConversionDialogResponse} containing the selection 153 */ 154 private static TagConversionDialogResponse showTagConversionDialog(List<String> keys, List<String> listPos, List<String> listNeg) { 155 TagConversionDialogResponse res = new TagConversionDialogResponse(listPos, listNeg); 156 String lSel = Config.getPref().get(GPX_SETTING + ".last", "all"); 157 158 JPanel p = new JPanel(new GridBagLayout()); 159 ButtonGroup r = new ButtonGroup(); 160 161 p.add(new JLabel( 162 tr("The GPX layer contains fields that can be converted to OSM tags. How would you like to proceed?")), 163 GBC.eol()); 164 JRadioButton rAll = new JRadioButton(tr("Convert all fields"), "all".equals(lSel)); 165 r.add(rAll); 166 p.add(rAll, GBC.eol()); 167 168 JRadioButton rList = new JRadioButton(tr("Only convert the following fields:"), "list".equals(lSel)); 169 r.add(rList); 170 p.add(rList, GBC.eol()); 171 172 JPanel q = new JPanel(); 173 174 List<JCheckBox> checkList = new ArrayList<>(); 175 for (String key : keys) { 176 JCheckBox cTmp = new JCheckBox(key, !listNeg.contains(key)); 177 checkList.add(cTmp); 178 q.add(cTmp); 179 } 180 181 q.setBorder(BorderFactory.createEmptyBorder(0, 20, 5, 0)); 182 p.add(q, GBC.eol()); 183 184 JRadioButton rNone = new JRadioButton(tr("Do not convert any fields"), "no".equals(lSel)); 185 r.add(rNone); 186 p.add(rNone, GBC.eol()); 187 188 ActionListener enabler = new TagConversionDialogRadioButtonActionListener(checkList, true); 189 ActionListener disabler = new TagConversionDialogRadioButtonActionListener(checkList, false); 190 191 if (!"list".equals(lSel)) { 192 disabler.actionPerformed(null); 193 } 194 195 rAll.addActionListener(disabler); 196 rList.addActionListener(enabler); 197 rNone.addActionListener(disabler); 198 199 ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), tr("Options"), 200 tr("Convert"), tr("Convert and remember selection"), tr("Cancel")) 201 .setButtonIcons("exportgpx", "exportgpx", "cancel").setContent(p); 202 int ret = ed.showDialog().getValue(); 203 204 if (ret == 1 || ret == 2) { 205 for (JCheckBox cItem : checkList) { 206 String key = cItem.getText(); 207 if (cItem.isSelected()) { 208 if (!res.listPos.contains(key)) { 209 res.listPos.add(key); 210 } 211 res.listNeg.remove(key); 212 } else { 213 if (!res.listNeg.contains(key)) { 214 res.listNeg.add(key); 215 } 216 res.listPos.remove(key); 217 } 218 } 219 if (rAll.isSelected()) { 220 res.sel = "all"; 221 } else if (rNone.isSelected()) { 222 res.sel = "no"; 223 } 224 Config.getPref().put(GPX_SETTING + ".last", res.sel); 225 if (ret == 2) { 226 Config.getPref().put(GPX_SETTING, res.sel); 227 } else { 228 Config.getPref().put(GPX_SETTING, "ask"); 229 } 230 Config.getPref().putList(GPX_SETTING + ".list.yes", res.listPos); 231 Config.getPref().putList(GPX_SETTING + ".list.no", res.listNeg); 232 } else { 233 res.sel = null; 234 } 235 return res; 236 } 237 238 private static class TagConversionDialogResponse { 239 240 final List<String> listPos; 241 final List<String> listNeg; 242 String sel = "list"; 243 244 TagConversionDialogResponse(List<String> p, List<String> n) { 245 listPos = new ArrayList<>(p); 246 listNeg = new ArrayList<>(n); 247 } 248 } 249 250 private static class TagConversionDialogRadioButtonActionListener implements ActionListener { 251 252 private final boolean enable; 253 private final List<JCheckBox> checkList; 254 255 TagConversionDialogRadioButtonActionListener(List<JCheckBox> chks, boolean en) { 256 enable = en; 257 checkList = chks; 258 } 259 260 @Override 261 public void actionPerformed(ActionEvent arg0) { 262 for (JCheckBox ch : checkList) { 263 ch.setEnabled(enable); 264 } 265 } 266 } 267}