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