001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer.gpx;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.event.ActionEvent;
007import java.awt.geom.Area;
008import java.awt.geom.Rectangle2D;
009
010import org.openstreetmap.josm.Main;
011import org.openstreetmap.josm.actions.DownloadAlongAction;
012import org.openstreetmap.josm.data.coor.LatLon;
013import org.openstreetmap.josm.data.gpx.GpxData;
014import org.openstreetmap.josm.data.gpx.GpxTrack;
015import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
016import org.openstreetmap.josm.data.gpx.WayPoint;
017import org.openstreetmap.josm.gui.PleaseWaitRunnable;
018import org.openstreetmap.josm.gui.help.HelpUtil;
019import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
020
021/**
022 * Action that issues a series of download requests to the API, following the GPX track.
023 *
024 * @author fred
025 */
026public class DownloadAlongTrackAction extends DownloadAlongAction {
027
028    static final int NEAR_TRACK = 0;
029    static final int NEAR_WAYPOINTS = 1;
030    static final int NEAR_BOTH = 2;
031
032    private static final String PREF_DOWNLOAD_ALONG_TRACK_OSM = "downloadAlongTrack.download.osm";
033    private static final String PREF_DOWNLOAD_ALONG_TRACK_GPS = "downloadAlongTrack.download.gps";
034
035    private static final String PREF_DOWNLOAD_ALONG_TRACK_DISTANCE = "downloadAlongTrack.distance";
036    private static final String PREF_DOWNLOAD_ALONG_TRACK_AREA = "downloadAlongTrack.area";
037    private static final String PREF_DOWNLOAD_ALONG_TRACK_NEAR = "downloadAlongTrack.near";
038
039    private final GpxData data;
040
041    /**
042     * Constructs a new {@code DownloadAlongTrackAction}
043     * @param data The GPX data used to download along
044     */
045    public DownloadAlongTrackAction(GpxData data) {
046        super(tr("Download from OSM along this track"), "downloadalongtrack", null, null, true);
047        this.data = data;
048    }
049
050    @Override
051    public void actionPerformed(ActionEvent e) {
052
053        final DownloadAlongPanel panel = new DownloadAlongPanel(
054                PREF_DOWNLOAD_ALONG_TRACK_OSM, PREF_DOWNLOAD_ALONG_TRACK_GPS,
055                PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, PREF_DOWNLOAD_ALONG_TRACK_AREA, PREF_DOWNLOAD_ALONG_TRACK_NEAR);
056
057        if (0 != panel.showInDownloadDialog(tr("Download from OSM along this track"), HelpUtil.ht("/Action/DownloadAlongTrack"))) {
058            return;
059        }
060
061        final int near = panel.getNear();
062
063        /*
064         * Find the average latitude for the data we're contemplating, so we can know how many
065         * metres per degree of longitude we have.
066         */
067        double latsum = 0;
068        int latcnt = 0;
069        if (near == NEAR_TRACK || near == NEAR_BOTH) {
070            for (GpxTrack trk : data.tracks) {
071                for (GpxTrackSegment segment : trk.getSegments()) {
072                    for (WayPoint p : segment.getWayPoints()) {
073                        latsum += p.getCoor().lat();
074                        latcnt++;
075                    }
076                }
077            }
078        }
079        if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
080            for (WayPoint p : data.waypoints) {
081                latsum += p.getCoor().lat();
082                latcnt++;
083            }
084        }
085        double avglat = latsum / latcnt;
086        double scale = Math.cos(Math.toRadians(avglat));
087        /*
088         * Compute buffer zone extents and maximum bounding box size. Note that the maximum we
089         * ever offer is a bbox area of 0.002, while the API theoretically supports 0.25, but as
090         * soon as you touch any built-up area, that kind of bounding box will download forever
091         * and then stop because it has more than 50k nodes.
092         */
093        final double buffer_dist = panel.getDistance();
094        final double max_area = panel.getArea() / 10000.0 / scale;
095        final double buffer_y = buffer_dist / 100000.0;
096        final double buffer_x = buffer_y / scale;
097        final int totalTicks = latcnt;
098        // guess if a progress bar might be useful.
099        final boolean displayProgress = totalTicks > 2000 && buffer_y < 0.01;
100
101        class CalculateDownloadArea extends PleaseWaitRunnable {
102
103            private Area a = new Area();
104            private boolean cancel = false;
105            private int ticks = 0;
106            private Rectangle2D r = new Rectangle2D.Double();
107
108            public CalculateDownloadArea() {
109                super(tr("Calculating Download Area"), displayProgress ? null : NullProgressMonitor.INSTANCE, false);
110            }
111
112            @Override
113            protected void cancel() {
114                cancel = true;
115            }
116
117            @Override
118            protected void finish() {
119            }
120
121            @Override
122            protected void afterFinish() {
123                if (cancel) {
124                    return;
125                }
126                confirmAndDownloadAreas(a, max_area, panel.isDownloadOsmData(), panel.isDownloadGpxData(),
127                        tr("Download from OSM along this track"), progressMonitor);
128            }
129
130            /**
131             * increase tick count by one, report progress every 100 ticks
132             */
133            private void tick() {
134                ticks++;
135                if (ticks % 100 == 0) {
136                    progressMonitor.worked(100);
137                }
138            }
139
140            /**
141             * calculate area for single, given way point and return new LatLon if the
142             * way point has been used to modify the area.
143             */
144            private LatLon calcAreaForWayPoint(WayPoint p, LatLon previous) {
145                tick();
146                LatLon c = p.getCoor();
147                if (previous == null || c.greatCircleDistance(previous) > buffer_dist) {
148                    // we add a buffer around the point.
149                    r.setRect(c.lon() - buffer_x, c.lat() - buffer_y, 2 * buffer_x, 2 * buffer_y);
150                    a.add(new Area(r));
151                    return c;
152                }
153                return previous;
154            }
155
156            @Override
157            protected void realRun() {
158                progressMonitor.setTicksCount(totalTicks);
159                /*
160                 * Collect the combined area of all gpx points plus buffer zones around them. We ignore
161                 * points that lie closer to the previous point than the given buffer size because
162                 * otherwise this operation takes ages.
163                 */
164                LatLon previous = null;
165                if (near == NEAR_TRACK || near == NEAR_BOTH) {
166                    for (GpxTrack trk : data.tracks) {
167                        for (GpxTrackSegment segment : trk.getSegments()) {
168                            for (WayPoint p : segment.getWayPoints()) {
169                                if (cancel) {
170                                    return;
171                                }
172                                previous = calcAreaForWayPoint(p, previous);
173                            }
174                        }
175                    }
176                }
177                if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
178                    for (WayPoint p : data.waypoints) {
179                        if (cancel) {
180                            return;
181                        }
182                        previous = calcAreaForWayPoint(p, previous);
183                    }
184                }
185            }
186        }
187        Main.worker.submit(new CalculateDownloadArea());
188    }
189}