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}