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}