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