source: josm/trunk/src/org/openstreetmap/josm/actions/DownloadAlongAction.java@ 17138

Last change on this file since 17138 was 16553, checked in by Don-vip, 4 years ago

see #19334 - javadoc fixes + protected constructors for abstract classes

  • Property svn:eol-style set to native
File size: 14.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.GraphicsEnvironment;
8import java.awt.GridBagLayout;
9import java.awt.event.ActionEvent;
10import java.awt.geom.Area;
11import java.awt.geom.Path2D;
12import java.awt.geom.PathIterator;
13import java.awt.geom.Rectangle2D;
14import java.text.MessageFormat;
15import java.util.ArrayList;
16import java.util.Collection;
17import java.util.List;
18import java.util.concurrent.Future;
19
20import javax.swing.JLabel;
21import javax.swing.JOptionPane;
22import javax.swing.JPanel;
23
24import org.openstreetmap.josm.actions.downloadtasks.DownloadTaskList;
25import org.openstreetmap.josm.data.coor.LatLon;
26import org.openstreetmap.josm.data.osm.DataSet;
27import org.openstreetmap.josm.gui.MainApplication;
28import org.openstreetmap.josm.gui.PleaseWaitRunnable;
29import org.openstreetmap.josm.gui.layer.gpx.DownloadAlongPanel;
30import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
31import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
32import org.openstreetmap.josm.spi.preferences.Config;
33import org.openstreetmap.josm.tools.GBC;
34import org.openstreetmap.josm.tools.Logging;
35import org.openstreetmap.josm.tools.Shortcut;
36import org.openstreetmap.josm.tools.Utils;
37
38/**
39 * Abstract superclass of DownloadAlongTrackAction and DownloadAlongWayAction
40 * @since 6054
41 */
42public abstract class DownloadAlongAction extends JosmAction {
43
44 /**
45 * Sub classes must override this method.
46 * @return the task to start or null if nothing to do
47 */
48 protected abstract PleaseWaitRunnable createTask();
49
50 /**
51 * Constructs a new {@code DownloadAlongAction}
52 * @param name the action's text as displayed in the menu
53 * @param iconName the filename of the icon to use
54 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note
55 * that html is not supported for menu actions on some platforms.
56 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
57 * do want a shortcut, remember you can always register it with group=none, so you
58 * won't be assigned a shortcut unless the user configures one. If you pass null here,
59 * the user CANNOT configure a shortcut for your action.
60 * @param registerInToolbar register this action for the toolbar preferences?
61 */
62 protected DownloadAlongAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean registerInToolbar) {
63 super(name, iconName, tooltip, shortcut, registerInToolbar);
64 }
65
66 protected static void addToDownload(Area a, Rectangle2D r, Collection<Rectangle2D> results, double maxArea) {
67 Area tmp = new Area(r);
68 // intersect with sought-after area
69 tmp.intersect(a);
70 if (tmp.isEmpty()) {
71 return;
72 }
73 Rectangle2D bounds = tmp.getBounds2D();
74 if (bounds.getWidth() * bounds.getHeight() > maxArea) {
75 // the rectangle gets too large; split it and make recursive call.
76 Rectangle2D r1;
77 Rectangle2D r2;
78 if (bounds.getWidth() > bounds.getHeight()) {
79 // rectangles that are wider than high are split into a left and right half,
80 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth() / 2, bounds.getHeight());
81 r2 = new Rectangle2D.Double(bounds.getX() + bounds.getWidth() / 2, bounds.getY(),
82 bounds.getWidth() / 2, bounds.getHeight());
83 } else {
84 // others into a top and bottom half.
85 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() / 2);
86 r2 = new Rectangle2D.Double(bounds.getX(), bounds.getY() + bounds.getHeight() / 2, bounds.getWidth(),
87 bounds.getHeight() / 2);
88 }
89 addToDownload(tmp, r1, results, maxArea);
90 addToDownload(tmp, r2, results, maxArea);
91 } else {
92 DataSet ds = MainApplication.getLayerManager().getEditDataSet();
93 if (ds != null) {
94 double p = LatLon.MAX_SERVER_PRECISION;
95 LatLon min = new LatLon(bounds.getY()+p, bounds.getX()+p);
96 LatLon max = new LatLon(bounds.getY()+bounds.getHeight()-p, bounds.getX()+bounds.getWidth()-p);
97 if (ds.getDataSourceBounds().stream().anyMatch(current -> (current.contains(min) && current.contains(max)))) {
98 return; // skip this one, already downloaded
99 }
100 }
101 results.add(bounds);
102 }
103 }
104
105 /**
106 * Area "a" contains the hull that we would like to download data for. however we
107 * can only download rectangles, so the following is an attempt at finding a number of
108 * rectangles to download.
109 *
110 * The idea is simply: Start out with the full bounding box. If it is too large, then
111 * split it in half and repeat recursively for each half until you arrive at something
112 * small enough to download. The algorithm is improved by always using the intersection
113 * between the rectangle and the actual desired area. For example, if you have a track
114 * that goes like this: +----+ | /| | / | | / | |/ | +----+ then we would first look at
115 * downloading the whole rectangle (assume it's too big), after that we split it in half
116 * (upper and lower half), but we do not request the full upper and lower rectangle, only
117 * the part of the upper/lower rectangle that actually has something in it.
118 *
119 * This functions calculates the rectangles, asks the user to continue and downloads
120 * the areas if applicable.
121 *
122 * @param a download area hull
123 * @param maxArea maximum area size for a single download
124 * @param osmDownload Set to true if OSM data should be downloaded
125 * @param gpxDownload Set to true if GPX data should be downloaded
126 * @param title the title string for the confirmation dialog
127 * @param newLayer Set to true if all areas should be put into a single new layer
128 */
129 protected static void confirmAndDownloadAreas(Area a, double maxArea, boolean osmDownload, boolean gpxDownload, String title,
130 boolean newLayer) {
131 List<Rectangle2D> toDownload = new ArrayList<>();
132 addToDownload(a, a.getBounds(), toDownload, maxArea);
133 if (toDownload.isEmpty()) {
134 return;
135 }
136 if (toDownload.size() > 1) {
137 JPanel msg = new JPanel(new GridBagLayout());
138 msg.add(new JLabel(trn(
139 "<html>This action will require {0} individual<br>download request. Do you wish<br>to continue?</html>",
140 "<html>This action will require {0} individual<br>download requests. Do you wish<br>to continue?</html>",
141 toDownload.size(), toDownload.size())), GBC.eol());
142 if (!GraphicsEnvironment.isHeadless() && JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog(
143 MainApplication.getMainFrame(), msg, title, JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE)) {
144 return;
145 }
146 }
147 final PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Download data"));
148 final Future<?> future = new DownloadTaskList(Config.getPref().getBoolean("download.along.zoom-after-download"))
149 .download(newLayer, toDownload, osmDownload, gpxDownload, monitor);
150 waitFuture(future, monitor);
151 }
152
153 /**
154 * Calculate list of points between two given points so that the distance between two consecutive points is below a limit.
155 * @param p1 first point or null
156 * @param p2 second point (must not be null)
157 * @param bufferDist the maximum distance
158 * @return a list of points with at least one point (p2) and maybe more.
159 */
160 protected static Collection<LatLon> calcBetweenPoints(LatLon p1, LatLon p2, double bufferDist) {
161 ArrayList<LatLon> intermediateNodes = new ArrayList<>();
162 intermediateNodes.add(p2);
163 if (p1 != null && p2.greatCircleDistance(p1) > bufferDist) {
164 double d = p2.greatCircleDistance(p1) / bufferDist;
165 int nbNodes = (int) d;
166 if (Logging.isDebugEnabled()) {
167 Logging.debug(MessageFormat.format("{0} intermediate nodes to download between {1} and {2}", nbNodes, p2, p1));
168 }
169 double latStep = (p2.lat() - p1.lat()) / (nbNodes + 1);
170 double lonStep = (p2.lon() - p1.lon()) / (nbNodes + 1);
171 for (int i = 1; i <= nbNodes; i++) {
172 LatLon intermediate = new LatLon(p1.lat() + i * latStep, p1.lon() + i * lonStep);
173 intermediateNodes.add(intermediate);
174 if (Logging.isTraceEnabled()) {
175 Logging.trace(tr(" adding {0} {1}", intermediate.lat(), intermediate.lon()));
176 }
177 }
178 }
179 return intermediateNodes;
180 }
181
182 /**
183 * Create task that downloads areas along the given path using the values specified in the panel.
184 * @param alongPath the path along which the areas are to be downloaded
185 * @param panel the panel that was displayed to the user and now contains his selections
186 * @param confirmTitle the title to display in the confirmation panel
187 * @param newLayer Set to true if all areas should be put into a single new layer
188 * @return the task or null if canceled by user
189 */
190 protected PleaseWaitRunnable createCalcTask(Path2D alongPath, DownloadAlongPanel panel, String confirmTitle, boolean newLayer) {
191 /*
192 * Find the average latitude for the data we're contemplating, so we can know how many
193 * metres per degree of longitude we have.
194 */
195 double latsum = 0;
196 int latcnt = 0;
197 final PathIterator pit = alongPath.getPathIterator(null);
198 final double[] res = new double[6];
199 while (!pit.isDone()) {
200 int type = pit.currentSegment(res);
201 if (type == PathIterator.SEG_LINETO || type == PathIterator.SEG_MOVETO) {
202 latsum += res[1];
203 latcnt++;
204 }
205 pit.next();
206 }
207 if (latcnt == 0) {
208 return null;
209 }
210 final double avglat = latsum / latcnt;
211 final double scale = Math.cos(Utils.toRadians(avglat));
212
213 /*
214 * Compute buffer zone extents and maximum bounding box size. Note that the maximum we
215 * ever offer is a bbox area of 0.002, while the API theoretically supports 0.25, but as
216 * soon as you touch any built-up area, that kind of bounding box will download forever
217 * and then stop because it has more than 50k nodes.
218 */
219 final double bufferDist = panel.getDistance();
220 final double maxArea = panel.getArea() / 10000.0 / scale;
221 final double bufferY = bufferDist / 100000.0;
222 final double bufferX = bufferY / scale;
223 final int totalTicks = latcnt;
224 // guess if a progress bar might be useful.
225 final boolean displayProgress = totalTicks > 20_000 && bufferY < 0.01;
226
227 class CalculateDownloadArea extends PleaseWaitRunnable {
228
229 private final Path2D downloadPath = new Path2D.Double();
230 private final boolean newLayer;
231 private boolean cancel;
232 private int ticks;
233 private final Rectangle2D r = new Rectangle2D.Double();
234
235 CalculateDownloadArea(boolean newLayer) {
236 super(tr("Calculating Download Area"), displayProgress ? null : NullProgressMonitor.INSTANCE, false);
237 this.newLayer = newLayer;
238 }
239
240 @Override
241 protected void cancel() {
242 cancel = true;
243 }
244
245 @Override
246 protected void finish() {
247 // Do nothing
248 }
249
250 @Override
251 protected void afterFinish() {
252 if (cancel) {
253 return;
254 }
255 confirmAndDownloadAreas(new Area(downloadPath), maxArea, panel.isDownloadOsmData(), panel.isDownloadGpxData(),
256 confirmTitle, newLayer);
257 }
258
259 /**
260 * increase tick count by one, report progress every 100 ticks
261 */
262 private void tick() {
263 ticks++;
264 if (ticks % 100 == 0) {
265 progressMonitor.worked(100);
266 }
267 }
268
269 /**
270 * calculate area enclosing a single point
271 */
272 private void calcAreaForWayPoint(LatLon c) {
273 r.setRect(c.lon() - bufferX, c.lat() - bufferY, 2 * bufferX, 2 * bufferY);
274 downloadPath.append(r, false);
275 }
276
277 @Override
278 protected void realRun() {
279 progressMonitor.setTicksCount(totalTicks);
280 PathIterator pit = alongPath.getPathIterator(null);
281 double[] res = new double[6];
282 LatLon previous = null;
283 while (!pit.isDone()) {
284 int type = pit.currentSegment(res);
285 LatLon c = new LatLon(res[1], res[0]);
286 if (type == PathIterator.SEG_LINETO) {
287 tick();
288 for (LatLon d : calcBetweenPoints(previous, c, bufferDist)) {
289 calcAreaForWayPoint(d);
290 }
291 previous = c;
292 } else if (type == PathIterator.SEG_MOVETO) {
293 previous = c;
294 tick();
295 calcAreaForWayPoint(c);
296 }
297 pit.next();
298 }
299 }
300 }
301
302 return new CalculateDownloadArea(newLayer);
303 }
304
305 @Override
306 public void actionPerformed(ActionEvent e) {
307 PleaseWaitRunnable task = createTask();
308 if (task != null) {
309 MainApplication.worker.submit(task);
310 }
311 }
312}
Note: See TracBrowser for help on using the repository browser.