001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer.gpx; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.GridBagLayout; 008import java.awt.event.ActionEvent; 009import java.io.File; 010import java.text.DateFormat; 011import java.util.ArrayList; 012import java.util.Arrays; 013import java.util.Collection; 014import java.util.Date; 015import java.util.Iterator; 016import java.util.List; 017import java.util.Map.Entry; 018import java.util.Optional; 019 020import javax.swing.AbstractAction; 021import javax.swing.JLabel; 022import javax.swing.JOptionPane; 023import javax.swing.JPanel; 024 025import org.openstreetmap.josm.Main; 026import org.openstreetmap.josm.data.gpx.GpxConstants; 027import org.openstreetmap.josm.data.gpx.GpxTrack; 028import org.openstreetmap.josm.data.gpx.GpxTrackSegment; 029import org.openstreetmap.josm.data.gpx.WayPoint; 030import org.openstreetmap.josm.data.osm.DataSet; 031import org.openstreetmap.josm.data.osm.Node; 032import org.openstreetmap.josm.data.osm.Way; 033import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 034import org.openstreetmap.josm.gui.MainApplication; 035import org.openstreetmap.josm.gui.layer.GpxLayer; 036import org.openstreetmap.josm.gui.layer.Layer; 037import org.openstreetmap.josm.gui.layer.OsmDataLayer; 038import org.openstreetmap.josm.gui.layer.markerlayer.Marker; 039import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer; 040import org.openstreetmap.josm.gui.widgets.UrlLabel; 041import org.openstreetmap.josm.spi.preferences.Config; 042import org.openstreetmap.josm.tools.GBC; 043import org.openstreetmap.josm.tools.ImageProvider; 044import org.openstreetmap.josm.tools.Logging; 045import org.openstreetmap.josm.tools.UncheckedParseException; 046import org.openstreetmap.josm.tools.date.DateUtils; 047 048/** 049 * An abstract action for a conversion from a {@code T} {@link Layer} to a {@link OsmDataLayer}. 050 * @param <T> the source layer class 051 */ 052public abstract class ConvertToDataLayerAction<T extends Layer> extends AbstractAction { 053 /** source layer */ 054 protected final transient T layer; 055 056 /** 057 * Constructs a new {@code ConvertToDataLayerAction} 058 * @param layer source layer 059 */ 060 protected ConvertToDataLayerAction(final T layer) { 061 super(tr("Convert to data layer")); 062 new ImageProvider("converttoosm").getResource().attachImageIcon(this, true); 063 this.layer = layer; 064 putValue("help", ht("/Action/ConvertToDataLayer")); 065 } 066 067 /** 068 * Converts a {@link GpxLayer} to a {@link OsmDataLayer}. 069 */ 070 public static class FromGpxLayer extends ConvertToDataLayerAction<GpxLayer> { 071 072 private final DateFormat timeFormatter = DateUtils.getGpxFormat(); 073 074 /** 075 * Creates a new {@code FromGpxLayer}. 076 * @param layer the source layer 077 */ 078 public FromGpxLayer(GpxLayer layer) { 079 super(layer); 080 } 081 082 @Override 083 public DataSet convert() { 084 final DataSet ds = new DataSet(); 085 for (GpxTrack trk : layer.data.getTracks()) { 086 for (GpxTrackSegment segment : trk.getSegments()) { 087 List<Node> nodes = new ArrayList<>(); 088 for (WayPoint p : segment.getWayPoints()) { 089 Node n = new Node(p.getCoor()); 090 for (Entry<String, Object> entry : p.attr.entrySet()) { 091 String key = entry.getKey(); 092 String str = p.getString(key); 093 if (str != null) { 094 n.put(key, str); 095 if (GpxConstants.PT_TIME.equals(key)) { 096 try { 097 n.setTimestamp(DateUtils.fromString(str)); 098 } catch (UncheckedParseException e) { 099 Logging.log(Logging.LEVEL_WARN, e); 100 } 101 } 102 } else { 103 Object obj = p.get(key); 104 if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) { 105 Date date = (Date) obj; 106 n.put(key, timeFormatter.format(date)); 107 n.setTimestamp(date); 108 } 109 } 110 } 111 ds.addPrimitive(n); 112 nodes.add(n); 113 } 114 Way w = new Way(); 115 w.setNodes(nodes); 116 ds.addPrimitive(w); 117 } 118 } 119 return ds; 120 } 121 } 122 123 /** 124 * Converts a {@link MarkerLayer} to a {@link OsmDataLayer}. 125 */ 126 public static class FromMarkerLayer extends ConvertToDataLayerAction<MarkerLayer> { 127 128 /** 129 * Converts a {@link MarkerLayer} to a {@link OsmDataLayer}. 130 * @param layer marker layer 131 */ 132 public FromMarkerLayer(MarkerLayer layer) { 133 super(layer); 134 } 135 136 @Override 137 public DataSet convert() { 138 final DataSet ds = new DataSet(); 139 for (Marker marker : layer.data) { 140 final Node node = new Node(marker.getCoor()); 141 final Collection<String> mapping = Config.getPref().getList("gpx.to-osm-mapping", Arrays.asList( 142 GpxConstants.GPX_NAME, "name", 143 GpxConstants.GPX_DESC, "description", 144 GpxConstants.GPX_CMT, "note", 145 GpxConstants.GPX_SRC, "source", 146 GpxConstants.PT_SYM, "gpxicon")); 147 if (mapping.size() % 2 == 0) { 148 final Iterator<String> it = mapping.iterator(); 149 while (it.hasNext()) { 150 final String gpxKey = it.next(); 151 final String osmKey = it.next(); 152 Optional.ofNullable(marker.getTemplateValue(gpxKey, false)) 153 .map(String::valueOf) 154 .ifPresent(s -> node.put(osmKey, s)); 155 } 156 } else { 157 Logging.warn("Invalid gpx.to-osm-mapping Einstein setting: expecting even number of entries"); 158 } 159 ds.addPrimitive(node); 160 } 161 return ds; 162 } 163 } 164 165 /** 166 * Performs the conversion to a {@link DataSet}. 167 * @return the resulting dataset 168 */ 169 public abstract DataSet convert(); 170 171 @Override 172 public void actionPerformed(ActionEvent e) { 173 JPanel msg = new JPanel(new GridBagLayout()); 174 msg.add(new JLabel( 175 tr("<html>Upload of unprocessed GPS data as map data is considered harmful.<br>" 176 + "If you want to upload traces, look here:</html>")), 177 GBC.eol()); 178 msg.add(new UrlLabel(Main.getOSMWebsite() + "/traces", 2), GBC.eop()); 179 if (!ConditionalOptionPaneUtil.showConfirmationDialog("convert_to_data", Main.parent, msg, tr("Warning"), 180 JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, JOptionPane.OK_OPTION)) { 181 return; 182 } 183 final DataSet ds = convert(); 184 final OsmDataLayer osmLayer = new OsmDataLayer(ds, tr("Converted from: {0}", layer.getName()), null); 185 if (layer.getAssociatedFile() != null) { 186 osmLayer.setAssociatedFile(new File(layer.getAssociatedFile().getParentFile(), layer.getAssociatedFile().getName() + ".osm")); 187 } 188 osmLayer.setUploadDiscouraged(true); 189 MainApplication.getLayerManager().addLayer(osmLayer, false); 190 MainApplication.getLayerManager().removeLayer(layer); 191 } 192}