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