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