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