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}