source: josm/trunk/src/org/openstreetmap/josm/actions/OverpassDownloadAction.java@ 12643

Last change on this file since 12643 was 12634, checked in by Don-vip, 7 years ago

see #15182 - deprecate Main.worker, replace it by gui.MainApplication.worker + code refactoring to make sure only editor packages use it

File size: 15.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.BorderLayout;
8import java.awt.Component;
9import java.awt.Dimension;
10import java.awt.GridBagLayout;
11import java.awt.event.ActionEvent;
12import java.awt.event.FocusEvent;
13import java.awt.event.FocusListener;
14import java.awt.event.KeyEvent;
15import java.util.Collection;
16import java.util.Optional;
17import java.util.concurrent.Future;
18import java.util.function.Consumer;
19
20import javax.swing.AbstractAction;
21import javax.swing.Action;
22import javax.swing.ActionMap;
23import javax.swing.JButton;
24import javax.swing.JLabel;
25import javax.swing.JOptionPane;
26import javax.swing.JPanel;
27import javax.swing.JScrollPane;
28import javax.swing.event.ListSelectionEvent;
29import javax.swing.event.ListSelectionListener;
30import javax.swing.plaf.basic.BasicArrowButton;
31
32import org.openstreetmap.josm.Main;
33import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
34import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
35import org.openstreetmap.josm.data.Bounds;
36import org.openstreetmap.josm.data.preferences.BooleanProperty;
37import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
38import org.openstreetmap.josm.gui.MainApplication;
39import org.openstreetmap.josm.gui.download.DownloadDialog;
40import org.openstreetmap.josm.gui.download.OverpassQueryList;
41import org.openstreetmap.josm.gui.download.OverpassQueryWizardDialog;
42import org.openstreetmap.josm.gui.preferences.server.OverpassServerPreference;
43import org.openstreetmap.josm.gui.util.GuiHelper;
44import org.openstreetmap.josm.gui.widgets.JosmTextArea;
45import org.openstreetmap.josm.io.OverpassDownloadReader;
46import org.openstreetmap.josm.tools.GBC;
47import org.openstreetmap.josm.tools.ImageProvider;
48import org.openstreetmap.josm.tools.Shortcut;
49
50/**
51 * Download map data from Overpass API server.
52 * @since 8684
53 */
54public class OverpassDownloadAction extends JosmAction {
55
56 /**
57 * Constructs a new {@code OverpassDownloadAction}.
58 */
59 public OverpassDownloadAction() {
60 super(tr("Download from Overpass API ..."), "download-overpass", tr("Download map data from Overpass API server."),
61 // CHECKSTYLE.OFF: LineLength
62 Shortcut.registerShortcut("file:download-overpass", tr("File: {0}", tr("Download from Overpass API ...")), KeyEvent.VK_DOWN, Shortcut.ALT_SHIFT),
63 // CHECKSTYLE.ON: LineLength
64 true, "overpassdownload/download", true);
65 putValue("help", ht("/Action/OverpassDownload"));
66 }
67
68 @Override
69 public void actionPerformed(ActionEvent e) {
70 OverpassDownloadDialog dialog = OverpassDownloadDialog.getInstance();
71 dialog.restoreSettings();
72 dialog.setVisible(true);
73
74 if (dialog.isCanceled()) {
75 return;
76 }
77
78 dialog.rememberSettings();
79 Optional<Bounds> selectedArea = dialog.getSelectedDownloadArea();
80 String overpassQuery = dialog.getRepairedOverpassQuery();
81
82 /*
83 * Absence of the selected area can be justified only if the overpass query
84 * is not restricted to bbox.
85 */
86 if (!selectedArea.isPresent() && overpassQuery.contains("{{bbox}}")) {
87 JOptionPane.showMessageDialog(
88 dialog,
89 tr("Please select a download area first."),
90 tr("Error"),
91 JOptionPane.ERROR_MESSAGE
92 );
93 return;
94 }
95
96 /*
97 * A callback that is passed to PostDownloadReporter that is called once the download task
98 * has finished. According to the number of errors happened, their type we decide whether we
99 * want to save the last query in OverpassQueryList.
100 */
101 Consumer<Collection<Object>> errorReporter = errors -> {
102
103 boolean onlyNoDataError = errors.size() == 1 &&
104 errors.contains("No data found in this area.");
105
106 if (errors.isEmpty() || onlyNoDataError) {
107 dialog.saveHistoricItemOnSuccess(overpassQuery);
108 }
109 };
110
111 /*
112 * In order to support queries generated by the Overpass Turbo Query Wizard tool
113 * which do not require the area to be specified.
114 */
115 Bounds area = selectedArea.orElseGet(() -> new Bounds(0, 0, 0, 0));
116 DownloadOsmTask task = new DownloadOsmTask();
117 task.setZoomAfterDownload(dialog.isZoomToDownloadedDataRequired());
118 Future<?> future = task.download(
119 new OverpassDownloadReader(area, OverpassServerPreference.getOverpassServer(), overpassQuery),
120 dialog.isNewLayerRequired(), area, null);
121 MainApplication.worker.submit(new PostDownloadHandler(task, future, errorReporter));
122 }
123
124 private static final class DisableActionsFocusListener implements FocusListener {
125
126 private final ActionMap actionMap;
127
128 private DisableActionsFocusListener(ActionMap actionMap) {
129 this.actionMap = actionMap;
130 }
131
132 @Override
133 public void focusGained(FocusEvent e) {
134 enableActions(false);
135 }
136
137 @Override
138 public void focusLost(FocusEvent e) {
139 enableActions(true);
140 }
141
142 private void enableActions(boolean enabled) {
143 Object[] allKeys = actionMap.allKeys();
144 if (allKeys != null) {
145 for (Object key : allKeys) {
146 Action action = actionMap.get(key);
147 if (action != null) {
148 action.setEnabled(enabled);
149 }
150 }
151 }
152 }
153 }
154
155 /**
156 * The download dialog that overpass uses.
157 * @since 12576 public
158 */
159 public static final class OverpassDownloadDialog extends DownloadDialog {
160
161 private JosmTextArea overpassQuery;
162 private OverpassQueryList overpassQueryList;
163 private static OverpassDownloadDialog instance;
164 private static final BooleanProperty OVERPASS_QUERY_LIST_OPENED =
165 new BooleanProperty("download.overpass.query-list.opened", false);
166 private static final String ACTION_IMG_SUBDIR = "dialogs";
167
168 private OverpassDownloadDialog(Component parent) {
169 super(parent, ht("/Action/OverpassDownload"));
170 cbDownloadOsmData.setEnabled(false);
171 cbDownloadOsmData.setSelected(false);
172 cbDownloadGpxData.setVisible(false);
173 cbDownloadNotes.setVisible(false);
174 cbStartup.setVisible(false);
175 }
176
177 public static OverpassDownloadDialog getInstance() {
178 if (instance == null) {
179 instance = new OverpassDownloadDialog(Main.parent);
180 }
181 return instance;
182 }
183
184 @Override
185 protected void buildMainPanelAboveDownloadSelections(JPanel pnl) {
186 DisableActionsFocusListener disableActionsFocusListener =
187 new DisableActionsFocusListener(slippyMapChooser.getNavigationComponentActionMap());
188
189 String tooltip = tr("Build an Overpass query using the Overpass Turbo Query Wizard tool");
190 Action queryWizardAction = new AbstractAction() {
191 @Override
192 public void actionPerformed(ActionEvent e) {
193 new OverpassQueryWizardDialog(instance).showDialog();
194 }
195 };
196
197 JButton openQueryWizard = new JButton(tr("Query Wizard"));
198 openQueryWizard.setToolTipText(tooltip);
199 openQueryWizard.addActionListener(queryWizardAction);
200
201 // use eol() that is needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes
202 pnl.add(openQueryWizard, GBC.eol());
203 pnl.add(new JLabel(tr("Overpass query:")), GBC.std().insets(5, 5, 0, 0).anchor(GBC.NORTHWEST));
204
205 // CHECKSTYLE.OFF: LineLength
206 this.overpassQuery = new JosmTextArea(
207 "/*\n" +
208 tr("Place your Overpass query below or generate one using the Overpass Turbo Query Wizard")
209 + "\n*/",
210 8, 80);
211 // CHECKSTYLE.ON: LineLength
212 this.overpassQuery.setFont(GuiHelper.getMonospacedFont(overpassQuery));
213 this.overpassQuery.addFocusListener(disableActionsFocusListener);
214 this.overpassQuery.addFocusListener(new FocusListener() {
215 @Override
216 public void focusGained(FocusEvent e) {
217 overpassQuery.selectAll();
218 }
219
220 @Override
221 public void focusLost(FocusEvent e) {
222 // ignored
223 }
224 });
225
226
227 this.overpassQueryList = new OverpassQueryList(this, this.overpassQuery);
228 this.overpassQueryList.setPreferredSize(new Dimension(350, 300));
229
230 EditSnippetAction edit = new EditSnippetAction();
231 RemoveSnippetAction remove = new RemoveSnippetAction();
232 this.overpassQueryList.addSelectionListener(edit);
233 this.overpassQueryList.addSelectionListener(remove);
234
235 JPanel listPanel = new JPanel(new GridBagLayout());
236 listPanel.add(new JLabel(tr("Your saved queries:")), GBC.eol().insets(2).anchor(GBC.CENTER));
237 listPanel.add(this.overpassQueryList, GBC.eol().fill(GBC.BOTH));
238 listPanel.add(new JButton(new AddSnippetAction()), GBC.std().fill(GBC.HORIZONTAL));
239 listPanel.add(new JButton(edit), GBC.std().fill(GBC.HORIZONTAL));
240 listPanel.add(new JButton(remove), GBC.std().fill(GBC.HORIZONTAL));
241 listPanel.setVisible(OVERPASS_QUERY_LIST_OPENED.get());
242
243 JScrollPane scrollPane = new JScrollPane(overpassQuery);
244 BasicArrowButton arrowButton = new BasicArrowButton(listPanel.isVisible()
245 ? BasicArrowButton.EAST
246 : BasicArrowButton.WEST);
247 arrowButton.setToolTipText(tr("Show/hide Overpass snippet list"));
248 arrowButton.addActionListener(e -> {
249 if (listPanel.isVisible()) {
250 listPanel.setVisible(false);
251 arrowButton.setDirection(BasicArrowButton.WEST);
252 OVERPASS_QUERY_LIST_OPENED.put(Boolean.FALSE);
253 } else {
254 listPanel.setVisible(true);
255 arrowButton.setDirection(BasicArrowButton.EAST);
256 OVERPASS_QUERY_LIST_OPENED.put(Boolean.TRUE);
257 }
258 });
259
260 JPanel innerPanel = new JPanel(new BorderLayout());
261 innerPanel.add(scrollPane, BorderLayout.CENTER);
262 innerPanel.add(arrowButton, BorderLayout.EAST);
263
264 JPanel pane = new JPanel(new BorderLayout());
265 pane.add(innerPanel, BorderLayout.CENTER);
266 pane.add(listPanel, BorderLayout.EAST);
267
268 GBC gbc = GBC.eol().fill(GBC.HORIZONTAL); gbc.ipady = 200;
269 pnl.add(pane, gbc);
270 }
271
272 public String getOverpassQuery() {
273 return overpassQuery.getText();
274 }
275
276 String getRepairedOverpassQuery() {
277 String query = getOverpassQuery();
278 if (query.matches("(/\\*(\\*[^/]|[^\\*/])*\\*/|\\s)*")) {
279 // Empty query. User might want to download everything
280 boolean doFix = ConditionalOptionPaneUtil.showConfirmationDialog(
281 "download.overpass.fix.emptytoall",
282 this,
283 tr("You entered an empty query. Do you want to download all data in this area instead?"),
284 tr("Download all data?"),
285 JOptionPane.YES_NO_OPTION,
286 JOptionPane.QUESTION_MESSAGE,
287 JOptionPane.YES_OPTION);
288 if (doFix) {
289 return "[out:xml]; \n"
290 + query + "\n"
291 + "(\n"
292 + " node({{bbox}});\n"
293 + "<;\n"
294 + ");\n"
295 + "(._;>;);"
296 + "out meta;";
297 }
298 }
299 // Note: We can add more repairs here. We might e.g. want to intercept missing 'out meta'.
300 return query;
301 }
302
303 /**
304 * Sets the query that is displayed
305 * @param text The multiline query text.
306 * @since 12576 public
307 */
308 public void setOverpassQuery(String text) {
309 overpassQuery.setText(text);
310 }
311
312 /**
313 * Adds the current query to {@link OverpassQueryList}.
314 * @param overpassQueryToSave The query to save
315 */
316 void saveHistoricItemOnSuccess(String overpassQueryToSave) {
317 overpassQueryList.saveHistoricItem(overpassQueryToSave);
318 }
319
320 @Override
321 protected void updateSizeCheck() {
322 displaySizeCheckResult(false);
323 }
324
325 /**
326 * Triggers the download action to fire.
327 * @since 12576 public
328 */
329 public void triggerDownload() {
330 super.btnDownload.doClick();
331 }
332
333 /**
334 * Action that delegates snippet creation to {@link OverpassQueryList#createNewItem()}.
335 */
336 class AddSnippetAction extends AbstractAction {
337
338 /**
339 * Constructs a new {@code AddSnippetAction}.
340 */
341 AddSnippetAction() {
342 super();
343 putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "add"));
344 putValue(SHORT_DESCRIPTION, tr("Add new snippet"));
345 }
346
347 @Override
348 public void actionPerformed(ActionEvent e) {
349 overpassQueryList.createNewItem();
350 }
351 }
352
353 /**
354 * Action that delegates snippet removal to {@link OverpassQueryList#removeSelectedItem()}.
355 */
356 class RemoveSnippetAction extends AbstractAction implements ListSelectionListener {
357
358 /**
359 * Constructs a new {@code RemoveSnippetAction}.
360 */
361 RemoveSnippetAction() {
362 super();
363 putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "delete"));
364 putValue(SHORT_DESCRIPTION, tr("Delete selected snippet"));
365 checkEnabled();
366 }
367
368 @Override
369 public void actionPerformed(ActionEvent e) {
370 overpassQueryList.removeSelectedItem();
371 }
372
373 /**
374 * Disables the action if no items are selected.
375 */
376 void checkEnabled() {
377 setEnabled(overpassQueryList.getSelectedItem().isPresent());
378 }
379
380 @Override
381 public void valueChanged(ListSelectionEvent e) {
382 checkEnabled();
383 }
384 }
385
386 /**
387 * Action that delegates snippet edit to {@link OverpassQueryList#editSelectedItem()}.
388 */
389 class EditSnippetAction extends AbstractAction implements ListSelectionListener {
390
391 /**
392 * Constructs a new {@code EditSnippetAction}.
393 */
394 EditSnippetAction() {
395 super();
396 putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "edit"));
397 putValue(SHORT_DESCRIPTION, tr("Edit selected snippet"));
398 checkEnabled();
399 }
400
401 @Override
402 public void actionPerformed(ActionEvent e) {
403 overpassQueryList.editSelectedItem();
404 }
405
406 /**
407 * Disables the action if no items are selected.
408 */
409 void checkEnabled() {
410 setEnabled(overpassQueryList.getSelectedItem().isPresent());
411 }
412
413 @Override
414 public void valueChanged(ListSelectionEvent e) {
415 checkEnabled();
416 }
417 }
418 }
419}
Note: See TracBrowser for help on using the repository browser.