source: josm/trunk/src/org/openstreetmap/josm/gui/layer/gpx/DownloadAlongTrackAction.java@ 5717

Last change on this file since 5717 was 5715, checked in by akks, 11 years ago

see #8416. GpxLayer refactoring: inner classes goes to org.openstreetmap.josm.gui.layer.gpx
Any change of behavior is a bug!

File size: 12.9 KB
Line 
1package org.openstreetmap.josm.gui.layer.gpx;
2
3import java.awt.GridBagLayout;
4import java.awt.event.ActionEvent;
5import java.awt.geom.Area;
6import java.awt.geom.Rectangle2D;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.List;
10import java.util.concurrent.Future;
11import javax.swing.AbstractAction;
12import javax.swing.JLabel;
13import javax.swing.JList;
14import javax.swing.JOptionPane;
15import javax.swing.JPanel;
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTaskList;
18import org.openstreetmap.josm.data.coor.LatLon;
19import org.openstreetmap.josm.data.gpx.GpxData;
20import org.openstreetmap.josm.data.gpx.GpxTrack;
21import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
22import org.openstreetmap.josm.data.gpx.WayPoint;
23import org.openstreetmap.josm.gui.PleaseWaitRunnable;
24import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
25import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
26import org.openstreetmap.josm.gui.progress.ProgressMonitor;
27import org.openstreetmap.josm.tools.GBC;
28import static org.openstreetmap.josm.tools.I18n.tr;
29import org.openstreetmap.josm.tools.ImageProvider;
30
31/**
32 * Action that issues a series of download requests to the API, following the GPX track.
33 *
34 * @author fred
35 */
36public class DownloadAlongTrackAction extends AbstractAction {
37 static final int NEAR_TRACK = 0;
38 static final int NEAR_WAYPOINTS = 1;
39 static final int NEAR_BOTH = 2;
40
41 private static final String PREF_DOWNLOAD_ALONG_TRACK_DISTANCE = "gpxLayer.downloadAlongTrack.distance";
42 private static final String PREF_DOWNLOAD_ALONG_TRACK_AREA = "gpxLayer.downloadAlongTrack.area";
43 private static final String PREF_DOWNLOAD_ALONG_TRACK_NEAR = "gpxLayer.downloadAlongTrack.near";
44
45
46 private final Integer[] dist = {5000, 500, 50};
47 private final Integer[] area = {20, 10, 5, 1};
48
49 private final GpxData data;
50
51 public DownloadAlongTrackAction(GpxData data) {
52 super(tr("Download from OSM along this track"), ImageProvider.get("downloadalongtrack"));
53 this.data = data;
54 }
55
56 @Override
57 public void actionPerformed(ActionEvent e) {
58 /*
59 * build selection dialog
60 */
61 JPanel msg = new JPanel(new GridBagLayout());
62 msg.add(new JLabel(tr("Download everything within:")), GBC.eol());
63 String[] s = new String[dist.length];
64 for (int i = 0; i < dist.length; ++i) {
65 s[i] = tr("{0} meters", dist[i]);
66 }
67 JList buffer = new JList(s);
68 buffer.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, 0));
69 msg.add(buffer, GBC.eol());
70 msg.add(new JLabel(tr("Maximum area per request:")), GBC.eol());
71 s = new String[area.length];
72 for (int i = 0; i < area.length; ++i) {
73 s[i] = tr("{0} sq km", area[i]);
74 }
75 JList maxRect = new JList(s);
76 maxRect.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, 0));
77 msg.add(maxRect, GBC.eol());
78 msg.add(new JLabel(tr("Download near:")), GBC.eol());
79 JList downloadNear = new JList(new String[]{tr("track only"), tr("waypoints only"), tr("track and waypoints")});
80 downloadNear.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, 0));
81 msg.add(downloadNear, GBC.eol());
82 int ret = JOptionPane.showConfirmDialog(Main.parent, msg, tr("Download from OSM along this track"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
83 switch (ret) {
84 case JOptionPane.CANCEL_OPTION:
85 case JOptionPane.CLOSED_OPTION:
86 return;
87 default:
88 // continue
89 }
90 Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, buffer.getSelectedIndex());
91 Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, maxRect.getSelectedIndex());
92 final int near = downloadNear.getSelectedIndex();
93 Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, near);
94 /*
95 * Find the average latitude for the data we're contemplating, so we can know how many
96 * metres per degree of longitude we have.
97 */
98 double latsum = 0;
99 int latcnt = 0;
100 if (near == NEAR_TRACK || near == NEAR_BOTH) {
101 for (GpxTrack trk : data.tracks) {
102 for (GpxTrackSegment segment : trk.getSegments()) {
103 for (WayPoint p : segment.getWayPoints()) {
104 latsum += p.getCoor().lat();
105 latcnt++;
106 }
107 }
108 }
109 }
110 if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
111 for (WayPoint p : data.waypoints) {
112 latsum += p.getCoor().lat();
113 latcnt++;
114 }
115 }
116 double avglat = latsum / latcnt;
117 double scale = Math.cos(Math.toRadians(avglat));
118 /*
119 * Compute buffer zone extents and maximum bounding box size. Note that the maximum we
120 * ever offer is a bbox area of 0.002, while the API theoretically supports 0.25, but as
121 * soon as you touch any built-up area, that kind of bounding box will download forever
122 * and then stop because it has more than 50k nodes.
123 */
124 Integer i = buffer.getSelectedIndex();
125 final int buffer_dist = dist[i < 0 ? 0 : i];
126 i = maxRect.getSelectedIndex();
127 final double max_area = area[i < 0 ? 0 : i] / 10000.0 / scale;
128 final double buffer_y = buffer_dist / 100000.0;
129 final double buffer_x = buffer_y / scale;
130 final int totalTicks = latcnt;
131 // guess if a progress bar might be useful.
132 final boolean displayProgress = totalTicks > 2000 && buffer_y < 0.01;
133
134 class CalculateDownloadArea extends PleaseWaitRunnable {
135
136 private Area a = new Area();
137 private boolean cancel = false;
138 private int ticks = 0;
139 private Rectangle2D r = new Rectangle2D.Double();
140
141 public CalculateDownloadArea() {
142 super(tr("Calculating Download Area"), displayProgress ? null : NullProgressMonitor.INSTANCE, false);
143 }
144
145 @Override
146 protected void cancel() {
147 cancel = true;
148 }
149
150 @Override
151 protected void finish() {
152 }
153
154 @Override
155 protected void afterFinish() {
156 if (cancel) {
157 return;
158 }
159 confirmAndDownloadAreas(a, max_area, progressMonitor);
160 }
161
162 /**
163 * increase tick count by one, report progress every 100 ticks
164 */
165 private void tick() {
166 ticks++;
167 if (ticks % 100 == 0) {
168 progressMonitor.worked(100);
169 }
170 }
171
172 /**
173 * calculate area for single, given way point and return new LatLon if the
174 * way point has been used to modify the area.
175 */
176 private LatLon calcAreaForWayPoint(WayPoint p, LatLon previous) {
177 tick();
178 LatLon c = p.getCoor();
179 if (previous == null || c.greatCircleDistance(previous) > buffer_dist) {
180 // we add a buffer around the point.
181 r.setRect(c.lon() - buffer_x, c.lat() - buffer_y, 2 * buffer_x, 2 * buffer_y);
182 a.add(new Area(r));
183 return c;
184 }
185 return previous;
186 }
187
188 @Override
189 protected void realRun() {
190 progressMonitor.setTicksCount(totalTicks);
191 /*
192 * Collect the combined area of all gpx points plus buffer zones around them. We ignore
193 * points that lie closer to the previous point than the given buffer size because
194 * otherwise this operation takes ages.
195 */
196 LatLon previous = null;
197 if (near == NEAR_TRACK || near == NEAR_BOTH) {
198 for (GpxTrack trk : data.tracks) {
199 for (GpxTrackSegment segment : trk.getSegments()) {
200 for (WayPoint p : segment.getWayPoints()) {
201 if (cancel) {
202 return;
203 }
204 previous = calcAreaForWayPoint(p, previous);
205 }
206 }
207 }
208 }
209 if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
210 for (WayPoint p : data.waypoints) {
211 if (cancel) {
212 return;
213 }
214 previous = calcAreaForWayPoint(p, previous);
215 }
216 }
217 }
218 }
219 Main.worker.submit(new CalculateDownloadArea());
220 }
221
222 /**
223 * Area "a" contains the hull that we would like to download data for. however we
224 * can only download rectangles, so the following is an attempt at finding a number of
225 * rectangles to download.
226 *
227 * The idea is simply: Start out with the full bounding box. If it is too large, then
228 * split it in half and repeat recursively for each half until you arrive at something
229 * small enough to download. The algorithm is improved by always using the intersection
230 * between the rectangle and the actual desired area. For example, if you have a track
231 * that goes like this: +----+ | /| | / | | / | |/ | +----+ then we would first look at
232 * downloading the whole rectangle (assume it's too big), after that we split it in half
233 * (upper and lower half), but we donot request the full upper and lower rectangle, only
234 * the part of the upper/lower rectangle that actually has something in it.
235 *
236 * This functions calculates the rectangles, asks the user to continue and downloads
237 * the areas if applicable.
238 */
239 private void confirmAndDownloadAreas(Area a, double max_area, ProgressMonitor progressMonitor) {
240 List<Rectangle2D> toDownload = new ArrayList<Rectangle2D>();
241 addToDownload(a, a.getBounds(), toDownload, max_area);
242 if (toDownload.isEmpty()) {
243 return;
244 }
245 JPanel msg = new JPanel(new GridBagLayout());
246 msg.add(new JLabel(tr("<html>This action will require {0} individual<br>" + "download requests. Do you wish<br>to continue?</html>", toDownload.size())), GBC.eol());
247 if (toDownload.size() > 1) {
248 int ret = JOptionPane.showConfirmDialog(Main.parent, msg, tr("Download from OSM along this track"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
249 switch (ret) {
250 case JOptionPane.CANCEL_OPTION:
251 case JOptionPane.CLOSED_OPTION:
252 return;
253 default:
254 // continue
255 }
256 }
257 final PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Download data"));
258 final Future<?> future = new DownloadOsmTaskList().download(false, toDownload, monitor);
259 Main.worker.submit(new Runnable() {
260 @Override
261 public void run() {
262 try {
263 future.get();
264 } catch (Exception e) {
265 e.printStackTrace();
266 return;
267 }
268 monitor.close();
269 }
270 });
271 }
272
273 private static void addToDownload(Area a, Rectangle2D r, Collection<Rectangle2D> results, double max_area) {
274 Area tmp = new Area(r);
275 // intersect with sought-after area
276 tmp.intersect(a);
277 if (tmp.isEmpty()) {
278 return;
279 }
280 Rectangle2D bounds = tmp.getBounds2D();
281 if (bounds.getWidth() * bounds.getHeight() > max_area) {
282 // the rectangle gets too large; split it and make recursive call.
283 Rectangle2D r1;
284 Rectangle2D r2;
285 if (bounds.getWidth() > bounds.getHeight()) {
286 // rectangles that are wider than high are split into a left and right half,
287 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth() / 2, bounds.getHeight());
288 r2 = new Rectangle2D.Double(bounds.getX() + bounds.getWidth() / 2, bounds.getY(),
289 bounds.getWidth() / 2, bounds.getHeight());
290 } else {
291 // others into a top and bottom half.
292 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() / 2);
293 r2 = new Rectangle2D.Double(bounds.getX(), bounds.getY() + bounds.getHeight() / 2, bounds.getWidth(),
294 bounds.getHeight() / 2);
295 }
296 addToDownload(a, r1, results, max_area);
297 addToDownload(a, r2, results, max_area);
298 } else {
299 results.add(bounds);
300 }
301 }
302
303}
Note: See TracBrowser for help on using the repository browser.