001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.io.importexport; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.File; 007import java.io.IOException; 008import java.io.InputStream; 009 010import javax.swing.JOptionPane; 011 012import org.openstreetmap.josm.actions.ExtensionFileFilter; 013import org.openstreetmap.josm.data.gpx.GpxData; 014import org.openstreetmap.josm.gui.MainApplication; 015import org.openstreetmap.josm.gui.Notification; 016import org.openstreetmap.josm.gui.layer.GpxLayer; 017import org.openstreetmap.josm.gui.layer.ImageryLayer; 018import org.openstreetmap.josm.gui.layer.OsmDataLayer; 019import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer; 020import org.openstreetmap.josm.gui.progress.ProgressMonitor; 021import org.openstreetmap.josm.gui.util.GuiHelper; 022import org.openstreetmap.josm.io.Compression; 023import org.openstreetmap.josm.io.GpxReader; 024import org.openstreetmap.josm.spi.preferences.Config; 025import org.openstreetmap.josm.tools.Logging; 026import org.xml.sax.SAXException; 027 028/** 029 * File importer allowing to import GPX files (*.gpx/gpx.gz files). 030 * 031 */ 032public class GpxImporter extends FileImporter { 033 034 /** 035 * Utility class containing imported GPX and marker layers, and a task to run after they are added to MapView. 036 */ 037 public static class GpxImporterData { 038 /** 039 * The imported GPX layer. May be null if no GPX data. 040 */ 041 private final GpxLayer gpxLayer; 042 /** 043 * The imported marker layer. May be null if no marker. 044 */ 045 private final MarkerLayer markerLayer; 046 /** 047 * The task to run after GPX and/or marker layer has been added to MapView. 048 */ 049 private final Runnable postLayerTask; 050 051 /** 052 * Constructs a new {@code GpxImporterData}. 053 * @param gpxLayer The imported GPX layer. May be null if no GPX data. 054 * @param markerLayer The imported marker layer. May be null if no marker. 055 * @param postLayerTask The task to run after GPX and/or marker layer has been added to MapView. 056 */ 057 public GpxImporterData(GpxLayer gpxLayer, MarkerLayer markerLayer, Runnable postLayerTask) { 058 this.gpxLayer = gpxLayer; 059 this.markerLayer = markerLayer; 060 this.postLayerTask = postLayerTask; 061 } 062 063 /** 064 * Returns the imported GPX layer. May be null if no GPX data. 065 * @return the imported GPX layer. May be null if no GPX data. 066 */ 067 public GpxLayer getGpxLayer() { 068 return gpxLayer; 069 } 070 071 /** 072 * Returns the imported marker layer. May be null if no marker. 073 * @return the imported marker layer. May be null if no marker. 074 */ 075 public MarkerLayer getMarkerLayer() { 076 return markerLayer; 077 } 078 079 /** 080 * Returns the task to run after GPX and/or marker layer has been added to MapView. 081 * @return the task to run after GPX and/or marker layer has been added to MapView. 082 */ 083 public Runnable getPostLayerTask() { 084 return postLayerTask; 085 } 086 } 087 088 /** 089 * Constructs a new {@code GpxImporter}. 090 */ 091 public GpxImporter() { 092 super(getFileFilter()); 093 } 094 095 /** 096 * Returns a GPX file filter (*.gpx and *.gpx.gz files). 097 * @return a GPX file filter 098 */ 099 public static ExtensionFileFilter getFileFilter() { 100 return ExtensionFileFilter.newFilterWithArchiveExtensions("gpx", 101 Config.getPref().get("save.extension.gpx", "gpx"), tr("GPX Files"), true); 102 } 103 104 @Override 105 public void importData(File file, ProgressMonitor progressMonitor) throws IOException { 106 final String fileName = file.getName(); 107 108 try (InputStream is = Compression.getUncompressedFileInputStream(file)) { 109 GpxReader r = new GpxReader(is); 110 boolean parsedProperly = r.parse(true); 111 r.getGpxData().storageFile = file; 112 addLayers(loadLayers(r.getGpxData(), parsedProperly, fileName, tr("Markers from {0}", fileName))); 113 } catch (SAXException e) { 114 Logging.error(e); 115 throw new IOException(e.getLocalizedMessage(), e); 116 } 117 } 118 119 /** 120 * Adds the specified GPX and marker layers to Map.main 121 * @param data The layers to add 122 * @see #loadLayers 123 */ 124 public static void addLayers(final GpxImporterData data) { 125 // FIXME: remove UI stuff from the IO subsystem 126 GuiHelper.runInEDT(() -> { 127 if (data.markerLayer != null) { 128 MainApplication.getLayerManager().addLayer(data.markerLayer); 129 } 130 if (data.gpxLayer != null) { 131 MainApplication.getLayerManager().addLayer(data.gpxLayer); 132 } 133 data.postLayerTask.run(); 134 }); 135 } 136 137 /** 138 * Replies the new GPX and marker layers corresponding to the specified GPX data. 139 * @param data The GPX data 140 * @param parsedProperly True if GPX data has been properly parsed by {@link GpxReader#parse} 141 * @param gpxLayerName The GPX layer name 142 * @param markerLayerName The marker layer name 143 * @return the new GPX and marker layers corresponding to the specified GPX data, to be used with {@link #addLayers} 144 * @see #addLayers 145 */ 146 public static GpxImporterData loadLayers(final GpxData data, final boolean parsedProperly, 147 final String gpxLayerName, String markerLayerName) { 148 GpxLayer gpxLayer = null; 149 MarkerLayer markerLayer = null; 150 gpxLayer = new GpxLayer(data, gpxLayerName, data.storageFile != null); 151 if (Config.getPref().getBoolean("marker.makeautomarkers", true) && !data.waypoints.isEmpty()) { 152 markerLayer = new MarkerLayer(data, markerLayerName, data.storageFile, gpxLayer); 153 if (markerLayer.data.isEmpty()) { 154 markerLayer = null; 155 } else { 156 gpxLayer.setLinkedMarkerLayer(markerLayer); 157 } 158 } 159 160 final boolean isSameColor = MainApplication.getLayerManager() 161 .getLayersOfType(ImageryLayer.class) 162 .stream().noneMatch(ImageryLayer::isVisible) 163 && data.getTracks().stream().anyMatch(t -> OsmDataLayer.getBackgroundColor().equals(t.getColor())); 164 165 Runnable postLayerTask = () -> { 166 if (!parsedProperly) { 167 String msg; 168 if (data.storageFile == null) { 169 msg = tr("Error occurred while parsing gpx data for layer ''{0}''. Only a part of the file will be available.", 170 gpxLayerName); 171 } else { 172 msg = tr("Error occurred while parsing gpx file ''{0}''. Only a part of the file will be available.", 173 data.storageFile.getPath()); 174 } 175 JOptionPane.showMessageDialog(null, msg); 176 } 177 if (isSameColor) { 178 new Notification(tr("The imported track \"{0}\" might not be visible because it has the same color as the background." + 179 "<br>You can change this in the context menu of the imported layer.", gpxLayerName)) 180 .setIcon(JOptionPane.WARNING_MESSAGE) 181 .setDuration(Notification.TIME_LONG) 182 .show(); 183 } 184 }; 185 return new GpxImporterData(gpxLayer, markerLayer, postLayerTask); 186 } 187 188 /** 189 * Replies the new GPX and marker layers corresponding to the specified GPX file. 190 * @param is input stream to GPX data 191 * @param associatedFile GPX file 192 * @param gpxLayerName The GPX layer name 193 * @param markerLayerName The marker layer name 194 * @param progressMonitor The progress monitor 195 * @return the new GPX and marker layers corresponding to the specified GPX file 196 * @throws IOException if an I/O error occurs 197 */ 198 public static GpxImporterData loadLayers(InputStream is, final File associatedFile, 199 final String gpxLayerName, String markerLayerName, ProgressMonitor progressMonitor) throws IOException { 200 try { 201 final GpxReader r = new GpxReader(is); 202 final boolean parsedProperly = r.parse(true); 203 r.getGpxData().storageFile = associatedFile; 204 return loadLayers(r.getGpxData(), parsedProperly, gpxLayerName, markerLayerName); 205 } catch (SAXException e) { 206 Logging.error(e); 207 throw new IOException(tr("Parsing data for layer ''{0}'' failed", gpxLayerName), e); 208 } 209 } 210}