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}