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

Last change on this file since 12574 was 12574, checked in by michael2402, 7 years ago

Apply #15057: Improve the over pass turbo dialog

Adds the ability to add favorites and a new wizard dialog with examples.

File size: 20.8 KB
RevLine 
[8684]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
[8756]7import java.awt.BorderLayout;
[8684]8import java.awt.Component;
[12574]9import java.awt.Dimension;
10import java.awt.GridBagLayout;
[8684]11import java.awt.event.ActionEvent;
[8932]12import java.awt.event.FocusEvent;
13import java.awt.event.FocusListener;
[8712]14import java.awt.event.KeyEvent;
[8684]15import java.util.ArrayList;
[12574]16import java.util.Arrays;
[8756]17import java.util.Collection;
[8684]18import java.util.Collections;
[12574]19import java.util.Optional;
[8684]20import java.util.concurrent.Future;
[12574]21import java.util.function.Consumer;
[8684]22
[8685]23import javax.swing.AbstractAction;
[8932]24import javax.swing.Action;
25import javax.swing.ActionMap;
[8685]26import javax.swing.JButton;
[12574]27import javax.swing.JEditorPane;
[8685]28import javax.swing.JLabel;
29import javax.swing.JOptionPane;
30import javax.swing.JPanel;
31import javax.swing.JScrollPane;
[12574]32import javax.swing.event.HyperlinkEvent;
[8756]33import javax.swing.plaf.basic.BasicArrowButton;
[12574]34import javax.swing.text.JTextComponent;
[8684]35
36import org.openstreetmap.josm.Main;
37import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
38import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
39import org.openstreetmap.josm.data.Bounds;
[12574]40import org.openstreetmap.josm.data.preferences.BooleanProperty;
[8684]41import org.openstreetmap.josm.data.preferences.CollectionProperty;
[12574]42import org.openstreetmap.josm.gui.ExtendedDialog;
[8684]43import org.openstreetmap.josm.gui.download.DownloadDialog;
[12574]44import org.openstreetmap.josm.gui.download.OverpassQueryList;
[9242]45import org.openstreetmap.josm.gui.preferences.server.OverpassServerPreference;
[8711]46import org.openstreetmap.josm.gui.util.GuiHelper;
[8684]47import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
[8716]48import org.openstreetmap.josm.gui.widgets.JosmTextArea;
[8744]49import org.openstreetmap.josm.io.OverpassDownloadReader;
[8684]50import org.openstreetmap.josm.tools.GBC;
[12574]51import org.openstreetmap.josm.tools.OpenBrowser;
[8744]52import org.openstreetmap.josm.tools.OverpassTurboQueryWizard;
[8712]53import org.openstreetmap.josm.tools.Shortcut;
[9385]54import org.openstreetmap.josm.tools.UncheckedParseException;
[8684]55import org.openstreetmap.josm.tools.Utils;
56
[8685]57/**
58 * Download map data from Overpass API server.
59 * @since 8684
60 */
[8684]61public class OverpassDownloadAction extends JosmAction {
62
[8685]63 /**
64 * Constructs a new {@code OverpassDownloadAction}.
65 */
[8684]66 public OverpassDownloadAction() {
67 super(tr("Download from Overpass API ..."), "download-overpass", tr("Download map data from Overpass API server."),
[8727]68 // CHECKSTYLE.OFF: LineLength
[8712]69 Shortcut.registerShortcut("file:download-overpass", tr("File: {0}", tr("Download from Overpass API ...")), KeyEvent.VK_DOWN, Shortcut.ALT_SHIFT),
[8727]70 // CHECKSTYLE.ON: LineLength
[8712]71 true, "overpassdownload/download", true);
[8684]72 putValue("help", ht("/Action/OverpassDownload"));
73 }
74
75 @Override
76 public void actionPerformed(ActionEvent e) {
77 OverpassDownloadDialog dialog = OverpassDownloadDialog.getInstance();
78 dialog.restoreSettings();
79 dialog.setVisible(true);
[12574]80
81 if (dialog.isCanceled()) {
82 return;
[8684]83 }
[12574]84
85 dialog.rememberSettings();
86 Optional<Bounds> selectedArea = dialog.getSelectedDownloadArea();
87 String overpassQuery = dialog.getOverpassQuery();
88
89 /*
90 * Absence of the selected area can be justified only if the overpass query
91 * is not restricted to bbox.
92 */
93 if (!selectedArea.isPresent() && overpassQuery.contains("{{bbox}}")) {
94 JOptionPane.showMessageDialog(
95 dialog,
96 tr("Please select a download area first."),
97 tr("Error"),
98 JOptionPane.ERROR_MESSAGE
99 );
100 return;
101 }
102
103 /*
104 * A callback that is passed to PostDownloadReporter that is called once the download task
105 * has finished. According to the number of errors happened, their type we decide whether we
106 * want to save the last query in OverpassQueryList.
107 */
108 Consumer<Collection> errorReporter = (errors) -> {
109
110 boolean onlyNoDataError = errors.size() == 1 &&
111 errors.contains("No data found in this area.");
112
113 if (errors.isEmpty() || onlyNoDataError) {
114 dialog.saveHistoricItemOnSuccess();
115 }
116 };
117
118 /*
119 * In order to support queries generated by the Overpass Turbo Query Wizard tool
120 * which do not require the area to be specified.
121 */
122 Bounds area = selectedArea.orElseGet(() -> new Bounds(0, 0, 0, 0));
123 DownloadOsmTask task = new DownloadOsmTask();
124 task.setZoomAfterDownload(dialog.isZoomToDownloadedDataRequired());
125 Future<?> future = task.download(
126 new OverpassDownloadReader(area, OverpassServerPreference.getOverpassServer(), dialog.getOverpassQuery()),
127 dialog.isNewLayerRequired(), area, null);
128 Main.worker.submit(new PostDownloadHandler(task, future, errorReporter));
[8684]129 }
130
[8932]131 private static final class DisableActionsFocusListener implements FocusListener {
[8684]132
[8932]133 private final ActionMap actionMap;
134
135 private DisableActionsFocusListener(ActionMap actionMap) {
136 this.actionMap = actionMap;
137 }
138
139 @Override
140 public void focusGained(FocusEvent e) {
141 enableActions(false);
142 }
143
144 @Override
145 public void focusLost(FocusEvent e) {
146 enableActions(true);
147 }
148
149 private void enableActions(boolean enabled) {
[10131]150 Object[] allKeys = actionMap.allKeys();
151 if (allKeys != null) {
152 for (Object key : allKeys) {
153 Action action = actionMap.get(key);
154 if (action != null) {
155 action.setEnabled(enabled);
156 }
[8932]157 }
158 }
159 }
160 }
161
162 private static final class OverpassDownloadDialog extends DownloadDialog {
163
[10252]164 private JosmTextArea overpassQuery;
[12574]165 private OverpassQueryList overpassQueryList;
[8684]166 private static OverpassDownloadDialog instance;
[12574]167 private static final BooleanProperty OVERPASS_QUERY_LIST_OPENED =
168 new BooleanProperty("download.overpass.query-list.opened", false);
[8684]169
170 private OverpassDownloadDialog(Component parent) {
[8713]171 super(parent, ht("/Action/OverpassDownload"));
[8684]172 cbDownloadOsmData.setEnabled(false);
173 cbDownloadOsmData.setSelected(false);
174 cbDownloadGpxData.setVisible(false);
175 cbDownloadNotes.setVisible(false);
176 cbStartup.setVisible(false);
177 }
178
[8685]179 public static OverpassDownloadDialog getInstance() {
[8684]180 if (instance == null) {
181 instance = new OverpassDownloadDialog(Main.parent);
182 }
183 return instance;
184 }
185
186 @Override
187 protected void buildMainPanelAboveDownloadSelections(JPanel pnl) {
[12574]188 // needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes
189 pnl.add(new JLabel(), GBC.eol());
[8684]190
[8932]191 DisableActionsFocusListener disableActionsFocusListener =
192 new DisableActionsFocusListener(slippyMapChooser.getNavigationComponentActionMap());
193
[12574]194 String tooltip = tr("Build an Overpass query using the Overpass Turbo Query Wizard tool");
195 Action queryWizardAction = new AbstractAction() {
[8684]196 @Override
197 public void actionPerformed(ActionEvent e) {
[12574]198 QueryWizardDialog.getInstance().showDialog();
[8684]199 }
[10782]200 };
[8684]201
[12574]202 JButton openQueryWizard = new JButton("Query Wizard");
203 openQueryWizard.setToolTipText(tooltip);
204 openQueryWizard.addActionListener(queryWizardAction);
205
206 // CHECKSTYLE.OFF: LineLength
207 this.overpassQuery = new JosmTextArea(
208 "/*\n" +
209 tr("Place your Overpass query below or generate one using the Overpass Turbo Query Wizard")
210 + "\n*/",
211 8, 80);
212 // CHECKSTYLE.ON: LineLength
213 this.overpassQuery.setFont(GuiHelper.getMonospacedFont(overpassQuery));
214 this.overpassQuery.addFocusListener(disableActionsFocusListener);
215 this.overpassQuery.addFocusListener(new FocusListener() {
[8756]216 @Override
[12574]217 public void focusGained(FocusEvent e) {
218 overpassQuery.selectAll();
[8756]219 }
[12574]220
221 @Override
222 public void focusLost(FocusEvent e) {
223
224 }
[8756]225 });
[12574]226
227 this.overpassQueryList = new OverpassQueryList(this, this.overpassQuery);
228 overpassQueryList.setToolTipText(tr("Show/hide Overpass snippet list"));
229 overpassQueryList.setVisible(OVERPASS_QUERY_LIST_OPENED.get());
230 overpassQueryList.setPreferredSize(new Dimension(350, 300));
231 JScrollPane scrollPane = new JScrollPane(overpassQuery);
232 BasicArrowButton arrowButton = new BasicArrowButton(overpassQueryList.isVisible()
233 ? BasicArrowButton.EAST
234 : BasicArrowButton.WEST);
235 arrowButton.addActionListener(e -> {
236 if (overpassQueryList.isVisible()) {
237 overpassQueryList.setVisible(false);
238 arrowButton.setDirection(BasicArrowButton.WEST);
239 OVERPASS_QUERY_LIST_OPENED.put(false);
240 } else {
241 overpassQueryList.setVisible(true);
242 arrowButton.setDirection(BasicArrowButton.EAST);
243 OVERPASS_QUERY_LIST_OPENED.put(false);
244 }
245 });
246
247 JPanel innerPanel = new JPanel(new BorderLayout());
248 innerPanel.add(scrollPane, BorderLayout.CENTER);
249 innerPanel.add(arrowButton, BorderLayout.EAST);
250
251 JPanel pane = new JPanel(new BorderLayout());
252 pane.add(innerPanel, BorderLayout.CENTER);
253 pane.add(overpassQueryList, BorderLayout.EAST);
254
255 GBC gbc = GBC.eol().fill(GBC.HORIZONTAL); gbc.ipady = 200;
256 pnl.add(openQueryWizard, GBC.std().insets(5, 5, 5, 5));
[8756]257 pnl.add(pane, gbc);
[8684]258 }
259
[12574]260 String getOverpassQuery() {
[8684]261 return overpassQuery.getText();
262 }
263
[12574]264 void setOverpassQuery(String text) {
[8756]265 overpassQuery.setText(text);
266 }
267
[12574]268 /**
269 * Adds the current query to {@link OverpassQueryList}.
270 */
271 void saveHistoricItemOnSuccess() {
272 overpassQueryList.saveHistoricItem(overpassQuery.getText());
[8684]273 }
274
275 @Override
[10629]276 protected void updateSizeCheck() {
277 displaySizeCheckResult(false);
278 }
[12574]279
280 /**
281 * Triggers the download action to fire.
282 */
283 private void triggerDownload() {
284 super.btnDownload.doClick();
285 }
[8684]286 }
287
[12574]288 private static final class QueryWizardDialog extends ExtendedDialog {
[8756]289
[12574]290 private static QueryWizardDialog dialog;
291 private final HistoryComboBox queryWizard;
292 private final OverpassTurboQueryWizard overpassQueryBuilder;
293 private static final CollectionProperty OVERPASS_WIZARD_HISTORY =
294 new CollectionProperty("download.overpass.wizard", new ArrayList<String>());
[8756]295
[12574]296 // dialog buttons
297 private static final int BUILD_QUERY = 0;
298 private static final int BUILD_AN_EXECUTE_QUERY = 1;
299 private static final int CANCEL = 2;
300
301 /**
302 * Get an instance of {@link QueryWizardDialog}.
303 * @return The instance
304 */
305 public static QueryWizardDialog getInstance() {
306 if (dialog == null) {
307 dialog = new QueryWizardDialog();
[8756]308 }
[12574]309
310 return dialog;
[8756]311 }
312
[12574]313 private static final String DESCRIPTION_STYLE =
314 "<style type=\"text/css\">\n"
315 + "table { border-spacing: 0pt;}\n"
316 + "h3 {text-align: center; padding: 8px;}\n"
317 + "td {border: 1px solid #dddddd; text-align: left; padding: 8px;}\n"
318 + "#desc {width: 350px;}"
319 + "</style>\n";
320
321 private QueryWizardDialog() {
322 super(OverpassDownloadDialog.getInstance(), tr("Overpass Turbo Query Wizard"),
323 tr("Build query"), tr("Build query and execute"), tr("Cancel"));
324
325 this.queryWizard = new HistoryComboBox();
326 this.overpassQueryBuilder = OverpassTurboQueryWizard.getInstance();
327
328 JPanel panel = new JPanel(new GridBagLayout());
329
330 JLabel searchLabel = new JLabel(tr("Search :"));
331 JTextComponent descPane = this.buildDescriptionSection();
332 JScrollPane scroll = GuiHelper.embedInVerticalScrollPane(descPane);
333 scroll.getVerticalScrollBar().setUnitIncrement(10); // make scrolling smooth
334
335 panel.add(searchLabel, GBC.std().insets(0, 0, 0, 20).anchor(GBC.SOUTHEAST));
336 panel.add(queryWizard, GBC.eol().insets(0, 0, 0, 15).fill(GBC.HORIZONTAL).anchor(GBC.SOUTH));
337 panel.add(scroll, GBC.eol().fill(GBC.BOTH).anchor(GBC.CENTER));
338
339 queryWizard.setPossibleItems(OVERPASS_WIZARD_HISTORY.get());
340
341 setCancelButton(CANCEL);
342 setDefaultButton(BUILD_AN_EXECUTE_QUERY + 1); // Build and execute button
343 setContent(panel, false);
[8756]344 }
345
[12574]346 @Override
347 public void buttonAction(int buttonIndex, ActionEvent evt) {
348 switch (buttonIndex) {
349 case BUILD_QUERY:
350 if (this.buildQueryAction()) {
351 this.saveHistory();
352 super.buttonAction(BUILD_QUERY, evt);
353 }
354 break;
355 case BUILD_AN_EXECUTE_QUERY:
356 if (this.buildQueryAction()) {
357 this.saveHistory();
358 super.buttonAction(BUILD_AN_EXECUTE_QUERY, evt);
359
360 OverpassDownloadDialog.getInstance().triggerDownload();
361 }
362 break;
363 default:
364 super.buttonAction(buttonIndex, evt);
365
[8756]366 }
[12574]367 }
368
369 /**
370 * Saves the latest, successfully parsed search term.
371 */
372 private void saveHistory() {
373 queryWizard.addCurrentItemToHistory();
374 OVERPASS_WIZARD_HISTORY.put(queryWizard.getHistory());
375 }
376
377 /**
378 * Tries to process a search term using {@link OverpassTurboQueryWizard}. If the term cannot
379 * be parsed, the the corresponding dialog is shown.
380 * @param searchTerm The search term to parse.
381 * @return {@link Optional#empty()} if an exception was thrown when parsing, meaning
382 * that the term cannot be processed, or non-empty {@link Optional} containing the result
383 * of parsing.
384 */
385 private Optional<String> tryParseSearchTerm(String searchTerm) {
386 try {
387 String query = this.overpassQueryBuilder.constructQuery(searchTerm);
388
389 return Optional.of(query);
390 } catch (UncheckedParseException ex) {
391 Main.error(ex);
392 JOptionPane.showMessageDialog(
393 OverpassDownloadDialog.getInstance(),
394 "<html>" +
395 tr("The Overpass wizard could not parse the following query:") +
396 Utils.joinAsHtmlUnorderedList(Collections.singleton(searchTerm)) +
397 "</html>",
398 tr("Parse error"),
399 JOptionPane.ERROR_MESSAGE
400 );
401
402 return Optional.empty();
[8756]403 }
404 }
405
[12574]406 /**
407 * Builds an Overpass query out from {@link QueryWizardDialog#queryWizard} contents.
408 * @return {@code true} if the query successfully built, {@code false} otherwise.
409 */
410 private boolean buildQueryAction() {
411 final String wizardSearchTerm = this.queryWizard.getText();
[8756]412
[12574]413 Optional<String> q = this.tryParseSearchTerm(wizardSearchTerm);
414 if (q.isPresent()) {
415 String query = q.get();
416 OverpassDownloadDialog.getInstance().setOverpassQuery(query);
[8756]417
[12574]418 return true;
419 }
420
421 return false;
[8756]422 }
423
[12574]424 private JTextComponent buildDescriptionSection() {
425 JEditorPane descriptionSection = new JEditorPane("text/html", this.getDescriptionContent());
426 descriptionSection.setEditable(false);
427 descriptionSection.addHyperlinkListener(e -> {
428 if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) {
429 OpenBrowser.displayUrl(e.getURL().toString());
430 }
431 });
432
433 return descriptionSection;
[8756]434 }
[12574]435
436 private String getDescriptionContent() {
437 return new StringBuilder("<html>")
438 .append(DESCRIPTION_STYLE)
439 .append("<body>")
440 .append("<h3>")
441 .append(tr("Query Wizard"))
442 .append("</h3>")
443 .append("<p>")
444 .append(tr("Allows you to interact with <i>Overpass API</i> by writing declarative, human-readable terms."))
445 .append(tr("The <i>Query Wizard</i> tool will transform those to a valid overpass query."))
446 .append(tr("For more detailed description see "))
447 .append(tr("<a href=\"{0}\">OSM Wiki</a>.", Main.getOSMWebsite() + "/wiki/Overpass_turbo/Wizard"))
448 .append("</p>")
449 .append("<h3>").append(tr("Hints")).append("</h3>")
450 .append("<table>").append("<tr>").append("<td>")
451 .append(Utils.joinAsHtmlUnorderedList(Arrays.asList("<i>type:node</i>", "<i>type:relation</i>", "<i>type:way</i>")))
452 .append("</td>").append("<td>")
453 .append("<span>").append(tr("Download objects of a certain type.")).append("</span>")
454 .append("</td>").append("</tr>")
455 .append("<tr>").append("<td>")
456 .append(Utils.joinAsHtmlUnorderedList(
457 Arrays.asList("<i>key=value in <u>location</u></i>",
458 "<i>key=value around <u>location</u></i>",
459 "<i>key=value in bbox</i>")))
460 .append("</td>").append("<td>")
461 .append(tr("Download object by specifying a specific location. For example,"))
462 .append(Utils.joinAsHtmlUnorderedList(Arrays.asList(
463 tr("{0} all objects having {1} as attribute are downloaded.", "<i>tourism=hotel in Berlin</i> -", "'tourism=hotel'"),
464 tr("{0} all object with the corresponding key/value pair located around Berlin. Note, the default value for radius "+
465 "is set to 1000m, but it can be changed in the generated query.", "<i>tourism=hotel around Berlin</i> -"),
466 tr("{0} all objects within the current selection that have {1} as attribute.", "<i>tourism=hotel in bbox</i> -",
467 "'tourism=hotel'"))))
468 .append("<span>")
469 .append(tr("Instead of <i>location</i> any valid place name can be used like address, city, etc."))
470 .append("</span>")
471 .append("</td>").append("</tr>")
472 .append("<tr>").append("<td>")
473 .append(Utils.joinAsHtmlUnorderedList(Arrays.asList("<i>key=value</i>", "<i>key=*</i>", "<i>key~regex</i>",
474 "<i>key!=value</i>", "<i>key!~regex</i>", "<i>key=\"combined value\"</i>")))
475 .append("</td>").append("<td>")
476 .append(tr("<span>Download objects that have some concrete key/value pair, only the key with any contents for the value, " +
477 "the value matching some regular expression. 'Not equal' operators are supported as well.</span>"))
478 .append("</td>").append("</tr>")
479 .append("<tr>").append("<td>")
480 .append(Utils.joinAsHtmlUnorderedList(Arrays.asList(
481 tr("<i>expression1 {0} expression2</i>", "or"),
482 tr("<i>expression1 {0} expression2</i>", "and"))))
483 .append("</td>").append("<td>")
484 .append("<span>")
485 .append(tr("Basic logical operators can be used to create more sophisticated queries. Instead of 'or' - '|', '||' " +
486 "can be used, and instead of 'and' - '&', '&&'."))
487 .append("</span>")
488 .append("</td>").append("</tr>").append("</table>")
489 .append("</body>")
490 .append("</html>")
491 .toString();
492 }
[8756]493 }
[8684]494}
Note: See TracBrowser for help on using the repository browser.