source: josm/trunk/src/org/openstreetmap/josm/gui/download/OverpassDownloadSource.java@ 16359

Last change on this file since 16359 was 16359, checked in by simon04, 4 years ago

see #18164 - Rename Overpass query wizard in dialogs

File size: 15.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.download;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Dimension;
8import java.awt.GridBagLayout;
9import java.awt.event.ActionEvent;
10import java.awt.event.FocusAdapter;
11import java.awt.event.FocusEvent;
12import java.util.Collection;
13import java.util.Objects;
14import java.util.concurrent.Future;
15import java.util.function.Consumer;
16
17import javax.swing.AbstractAction;
18import javax.swing.BorderFactory;
19import javax.swing.Icon;
20import javax.swing.JButton;
21import javax.swing.JLabel;
22import javax.swing.JOptionPane;
23import javax.swing.JPanel;
24import javax.swing.JScrollPane;
25import javax.swing.event.ListSelectionEvent;
26import javax.swing.event.ListSelectionListener;
27import javax.swing.plaf.basic.BasicArrowButton;
28
29import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
30import org.openstreetmap.josm.actions.downloadtasks.DownloadParams;
31import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
32import org.openstreetmap.josm.data.Bounds;
33import org.openstreetmap.josm.data.preferences.AbstractProperty;
34import org.openstreetmap.josm.data.preferences.BooleanProperty;
35import org.openstreetmap.josm.data.preferences.IntegerProperty;
36import org.openstreetmap.josm.data.preferences.StringProperty;
37import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
38import org.openstreetmap.josm.gui.MainApplication;
39import org.openstreetmap.josm.gui.download.DownloadSourceSizingPolicy.AdjustableDownloadSizePolicy;
40import org.openstreetmap.josm.gui.download.overpass.OverpassWizardRegistration;
41import org.openstreetmap.josm.gui.download.overpass.OverpassWizardRegistration.OverpassWizardCallbacks;
42import org.openstreetmap.josm.gui.util.GuiHelper;
43import org.openstreetmap.josm.gui.widgets.JosmTextArea;
44import org.openstreetmap.josm.io.OverpassDownloadReader;
45import org.openstreetmap.josm.tools.GBC;
46import org.openstreetmap.josm.tools.ImageProvider;
47
48/**
49 * Class defines the way data is fetched from Overpass API.
50 * @since 12652
51 */
52public class OverpassDownloadSource implements DownloadSource<OverpassDownloadSource.OverpassDownloadData> {
53 /** Overpass query to retrieve all nodes and related parent objects, */
54 public static final String FULL_DOWNLOAD_QUERY = "[out:xml]; \n"
55 + "(\n"
56 + " node({{bbox}});\n"
57 + "<;\n"
58 + ");\n"
59 + "(._;>;);"
60 + "out meta;";
61
62 @Override
63 public AbstractDownloadSourcePanel<OverpassDownloadData> createPanel(DownloadDialog dialog) {
64 return new OverpassDownloadSourcePanel(this);
65 }
66
67 @Override
68 public void doDownload(OverpassDownloadData data, DownloadSettings settings) {
69 /*
70 * In order to support queries generated by the Overpass Turbo Query Wizard tool
71 * which do not require the area to be specified.
72 */
73 Bounds area = settings.getDownloadBounds().orElse(new Bounds(0, 0, 0, 0));
74 DownloadOsmTask task = new DownloadOsmTask();
75 task.setZoomAfterDownload(settings.zoomToData());
76 Future<?> future = task.download(
77 new OverpassDownloadReader(area, OverpassDownloadReader.OVERPASS_SERVER.get(), data.getQuery()),
78 new DownloadParams().withNewLayer(settings.asNewLayer()), area, null);
79 MainApplication.worker.submit(new PostDownloadHandler(task, future, data.getErrorReporter()));
80 }
81
82 @Override
83 public String getLabel() {
84 return tr("Download from Overpass API");
85 }
86
87 @Override
88 public boolean onlyExpert() {
89 return true;
90 }
91
92 /**
93 * The GUI representation of the Overpass download source.
94 * @since 12652
95 */
96 public static class OverpassDownloadSourcePanel extends AbstractDownloadSourcePanel<OverpassDownloadData>
97 implements OverpassWizardCallbacks {
98
99 private static final String SIMPLE_NAME = "overpassdownloadpanel";
100 private static final AbstractProperty<Integer> PANEL_SIZE_PROPERTY =
101 new IntegerProperty(TAB_SPLIT_NAMESPACE + SIMPLE_NAME, 150).cached();
102 private static final BooleanProperty OVERPASS_QUERY_LIST_OPENED =
103 new BooleanProperty("download.overpass.query-list.opened", false);
104 private static final String ACTION_IMG_SUBDIR = "dialogs";
105
106 private static final StringProperty DOWNLOAD_QUERY = new StringProperty("download.overpass.query",
107 "/*\n" + tr("Place your Overpass query below or generate one using the query wizard") + "\n*/");
108
109 private final JosmTextArea overpassQuery;
110 private final UserQueryList overpassQueryList;
111
112 /**
113 * Create a new {@link OverpassDownloadSourcePanel}
114 * @param ds The download source to create the panel for
115 */
116 public OverpassDownloadSourcePanel(OverpassDownloadSource ds) {
117 super(ds);
118 setLayout(new BorderLayout());
119
120 this.overpassQuery = new JosmTextArea(DOWNLOAD_QUERY.get(), 8, 80);
121 this.overpassQuery.setFont(GuiHelper.getMonospacedFont(overpassQuery));
122 this.overpassQuery.addFocusListener(new FocusAdapter() {
123 @Override
124 public void focusGained(FocusEvent e) {
125 overpassQuery.selectAll();
126 }
127 });
128
129 this.overpassQueryList = new UserQueryList(this, this.overpassQuery, "download.overpass.queries");
130 this.overpassQueryList.setPreferredSize(new Dimension(350, 300));
131
132 EditSnippetAction edit = new EditSnippetAction();
133 RemoveSnippetAction remove = new RemoveSnippetAction();
134 this.overpassQueryList.addSelectionListener(edit);
135 this.overpassQueryList.addSelectionListener(remove);
136
137 JPanel listPanel = new JPanel(new GridBagLayout());
138 listPanel.add(new JLabel(tr("Your saved queries:")), GBC.eol().insets(2).anchor(GBC.CENTER));
139 listPanel.add(this.overpassQueryList, GBC.eol().fill(GBC.BOTH));
140 listPanel.add(new JButton(new AddSnippetAction()), GBC.std().fill(GBC.HORIZONTAL));
141 listPanel.add(new JButton(edit), GBC.std().fill(GBC.HORIZONTAL));
142 listPanel.add(new JButton(remove), GBC.std().fill(GBC.HORIZONTAL));
143 listPanel.setVisible(OVERPASS_QUERY_LIST_OPENED.get());
144
145 JScrollPane scrollPane = new JScrollPane(overpassQuery);
146 BasicArrowButton arrowButton = new BasicArrowButton(listPanel.isVisible()
147 ? BasicArrowButton.EAST
148 : BasicArrowButton.WEST);
149 arrowButton.setToolTipText(tr("Show/hide Overpass snippet list"));
150 arrowButton.addActionListener(e -> {
151 if (listPanel.isVisible()) {
152 listPanel.setVisible(false);
153 arrowButton.setDirection(BasicArrowButton.WEST);
154 OVERPASS_QUERY_LIST_OPENED.put(Boolean.FALSE);
155 } else {
156 listPanel.setVisible(true);
157 arrowButton.setDirection(BasicArrowButton.EAST);
158 OVERPASS_QUERY_LIST_OPENED.put(Boolean.TRUE);
159 }
160 });
161
162 JPanel innerPanel = new JPanel(new BorderLayout());
163 innerPanel.add(scrollPane, BorderLayout.CENTER);
164 innerPanel.add(arrowButton, BorderLayout.EAST);
165
166 JPanel leftPanel = new JPanel(new GridBagLayout());
167 leftPanel.add(new JLabel(tr("Overpass query:")), GBC.eol().insets(5, 1, 5, 1).anchor(GBC.NORTHWEST));
168 leftPanel.add(new JLabel(), GBC.eol().fill(GBC.VERTICAL));
169 OverpassWizardRegistration.getWizards(this)
170 .stream()
171 .map(JButton::new)
172 .forEach(button -> leftPanel.add(button, GBC.eol().anchor(GBC.CENTER)));
173 leftPanel.add(new JLabel(), GBC.eol().fill(GBC.VERTICAL));
174 leftPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
175
176 add(leftPanel, BorderLayout.WEST);
177 add(innerPanel, BorderLayout.CENTER);
178 add(listPanel, BorderLayout.EAST);
179
180 setMinimumSize(new Dimension(450, 240));
181 }
182
183 @Override
184 public OverpassDownloadData getData() {
185 String query = overpassQuery.getText();
186 /*
187 * A callback that is passed to PostDownloadReporter that is called once the download task
188 * has finished. According to the number of errors happened, their type we decide whether we
189 * want to save the last query in OverpassQueryList.
190 */
191 Consumer<Collection<Object>> errorReporter = errors -> {
192
193 boolean onlyNoDataError = errors.size() == 1 &&
194 errors.contains("No data found in this area.");
195
196 if (errors.isEmpty() || onlyNoDataError) {
197 overpassQueryList.saveHistoricItem(query);
198 }
199 };
200
201 return new OverpassDownloadData(OverpassDownloadReader.fixQuery(query), errorReporter);
202 }
203
204 @Override
205 public void rememberSettings() {
206 DOWNLOAD_QUERY.put(overpassQuery.getText());
207 }
208
209 @Override
210 public void restoreSettings() {
211 overpassQuery.setText(DOWNLOAD_QUERY.get());
212 }
213
214 @Override
215 public boolean checkDownload(DownloadSettings settings) {
216 String query = getData().getQuery();
217
218 /*
219 * Absence of the selected area can be justified only if the overpass query
220 * is not restricted to bbox.
221 */
222 if (!settings.getDownloadBounds().isPresent() && query.contains("{{bbox}}")) {
223 JOptionPane.showMessageDialog(
224 this.getParent(),
225 tr("Please select a download area first."),
226 tr("Error"),
227 JOptionPane.ERROR_MESSAGE
228 );
229 return false;
230 }
231
232 /*
233 * Check for an empty query. User might want to download everything, if so validation is passed,
234 * otherwise return false.
235 */
236 if (query.matches("(/\\*(\\*[^/]|[^\\*/])*\\*/|\\s)*")) {
237 boolean doFix = ConditionalOptionPaneUtil.showConfirmationDialog(
238 "download.overpass.fix.emptytoall",
239 this,
240 tr("You entered an empty query. Do you want to download all data in this area instead?"),
241 tr("Download all data?"),
242 JOptionPane.YES_NO_OPTION,
243 JOptionPane.QUESTION_MESSAGE,
244 JOptionPane.YES_OPTION);
245 if (doFix) {
246 this.overpassQuery.setText(FULL_DOWNLOAD_QUERY);
247 } else {
248 return false;
249 }
250 }
251
252 return true;
253 }
254
255 /**
256 * Sets query to the query text field.
257 * @param query The query to set.
258 */
259 public void setOverpassQuery(String query) {
260 Objects.requireNonNull(query, "query");
261 this.overpassQuery.setText(query);
262 }
263
264 @Override
265 public Icon getIcon() {
266 return ImageProvider.get("download-overpass");
267 }
268
269 @Override
270 public String getSimpleName() {
271 return SIMPLE_NAME;
272 }
273
274 @Override
275 public DownloadSourceSizingPolicy getSizingPolicy() {
276 return new AdjustableDownloadSizePolicy(PANEL_SIZE_PROPERTY, () -> 50);
277 }
278
279 /**
280 * Action that delegates snippet creation to {@link UserQueryList#createNewItem()}.
281 */
282 private class AddSnippetAction extends AbstractAction {
283
284 /**
285 * Constructs a new {@code AddSnippetAction}.
286 */
287 AddSnippetAction() {
288 new ImageProvider(ACTION_IMG_SUBDIR, "add").getResource().attachImageIcon(this, true);
289 putValue(SHORT_DESCRIPTION, tr("Add new snippet"));
290 }
291
292 @Override
293 public void actionPerformed(ActionEvent e) {
294 overpassQueryList.createNewItem();
295 }
296 }
297
298 /**
299 * Action that delegates snippet removal to {@link UserQueryList#removeSelectedItem()}.
300 */
301 private class RemoveSnippetAction extends AbstractAction implements ListSelectionListener {
302
303 /**
304 * Constructs a new {@code RemoveSnippetAction}.
305 */
306 RemoveSnippetAction() {
307 new ImageProvider(ACTION_IMG_SUBDIR, "delete").getResource().attachImageIcon(this, true);
308 putValue(SHORT_DESCRIPTION, tr("Delete selected snippet"));
309 checkEnabled();
310 }
311
312 @Override
313 public void actionPerformed(ActionEvent e) {
314 overpassQueryList.removeSelectedItem();
315 }
316
317 /**
318 * Disables the action if no items are selected.
319 */
320 void checkEnabled() {
321 setEnabled(overpassQueryList.getSelectedItem().isPresent());
322 }
323
324 @Override
325 public void valueChanged(ListSelectionEvent e) {
326 checkEnabled();
327 }
328 }
329
330 /**
331 * Action that delegates snippet edit to {@link UserQueryList#editSelectedItem()}.
332 */
333 private class EditSnippetAction extends AbstractAction implements ListSelectionListener {
334
335 /**
336 * Constructs a new {@code EditSnippetAction}.
337 */
338 EditSnippetAction() {
339 super();
340 new ImageProvider(ACTION_IMG_SUBDIR, "edit").getResource().attachImageIcon(this, true);
341 putValue(SHORT_DESCRIPTION, tr("Edit selected snippet"));
342 checkEnabled();
343 }
344
345 @Override
346 public void actionPerformed(ActionEvent e) {
347 overpassQueryList.editSelectedItem();
348 }
349
350 /**
351 * Disables the action if no items are selected.
352 */
353 void checkEnabled() {
354 setEnabled(overpassQueryList.getSelectedItem().isPresent());
355 }
356
357 @Override
358 public void valueChanged(ListSelectionEvent e) {
359 checkEnabled();
360 }
361 }
362
363 @Override
364 public void submitWizardResult(String resultingQuery) {
365 setOverpassQuery(resultingQuery);
366 }
367 }
368
369 /**
370 * Encapsulates data that is required to preform download from Overpass API.
371 */
372 static class OverpassDownloadData {
373 private final String query;
374 private final Consumer<Collection<Object>> errorReporter;
375
376 OverpassDownloadData(String query, Consumer<Collection<Object>> errorReporter) {
377 this.query = query;
378 this.errorReporter = errorReporter;
379 }
380
381 String getQuery() {
382 return this.query;
383 }
384
385 Consumer<Collection<Object>> getErrorReporter() {
386 return this.errorReporter;
387 }
388 }
389
390}
Note: See TracBrowser for help on using the repository browser.