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