Ticket #11428: 0001-Add-Overpass-download-dialog-to-core-provide-Overpas.patch

File 0001-Add-Overpass-download-dialog-to-core-provide-Overpas.patch, 17.6 KB (added by simon04, 10 years ago)
  • new file 2001

    From 420b0f011976a6daa6065d79b16f40cdbf7324e4 Mon Sep 17 00:00:00 2001
    From: Simon Legner <Simon.Legner@gmail.com>
    Date: Thu, 16 Apr 2015 18:15:53 +0200
    Subject: [PATCH] Add Overpass download dialog to core, provide Overpass Turbo
     wizard
    
    ---
     images/download-overpass.png                       | Bin 0 -> 1516 bytes
     .../josm/actions/OverpassDownloadAction.java       | 224 +++++++++++++++++++++
     .../josm/actions/OverpassTurboQueryWizard.java     |  91 +++++++++
     src/org/openstreetmap/josm/gui/MainMenu.java       |   4 +
     4 files changed, 319 insertions(+)
     create mode 100644 images/download-overpass.png
     create mode 100644 src/org/openstreetmap/josm/actions/OverpassDownloadAction.java
     create mode 100644 src/org/openstreetmap/josm/actions/OverpassTurboQueryWizard.java
    
    diff --git a/images/download-overpass.png b/images/download-overpass.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..179567687ba96428c80763cd34e115454fba04f5
    GIT binary patch
    literal 1516
    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
    Sc(Ew}0000<MNUMnLSTX<pybT}
  • new file src/org/openstreetmap/josm/actions/OverpassDownloadAction.java

    literal 0
    HcmV?d00001
    
    diff --git a/src/org/openstreetmap/josm/actions/OverpassDownloadAction.java b/src/org/openstreetmap/josm/actions/OverpassDownloadAction.java
    new file mode 100644
    index 0000000..af63d7d
    - +  
     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.Component;
     8import java.awt.GridBagConstraints;
     9import java.awt.event.ActionEvent;
     10import java.io.UnsupportedEncodingException;
     11import java.net.URLEncoder;
     12import java.util.ArrayList;
     13import java.util.Collections;
     14import java.util.concurrent.Future;
     15
     16import javax.swing.*;
     17
     18import org.openstreetmap.josm.Main;
     19import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
     20import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
     21import org.openstreetmap.josm.data.Bounds;
     22import org.openstreetmap.josm.data.DataSource;
     23import org.openstreetmap.josm.data.osm.DataSet;
     24import org.openstreetmap.josm.gui.HelpAwareOptionPane;
     25import org.openstreetmap.josm.gui.download.DownloadDialog;
     26import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     27import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
     28import org.openstreetmap.josm.io.BoundingBoxDownloader;
     29import org.openstreetmap.josm.io.OsmTransferException;
     30import org.openstreetmap.josm.tools.GBC;
     31import org.openstreetmap.josm.tools.Utils;
     32
     33public class OverpassDownloadAction extends JosmAction {
     34
     35    public OverpassDownloadAction() {
     36        super(tr("Download from Overpass API ..."), "download-overpass", tr("Download map data from Overpass API server."),
     37                null, true, "overpassdownload/download", true);
     38        putValue("help", ht("/Action/OverpassDownload"));
     39    }
     40
     41    @Override
     42    public void actionPerformed(ActionEvent e) {
     43        OverpassDownloadDialog dialog = OverpassDownloadDialog.getInstance();
     44        dialog.restoreSettings();
     45        dialog.setVisible(true);
     46        if (!dialog.isCanceled()) {
     47            dialog.rememberSettings();
     48            Bounds area = dialog.getSelectedDownloadArea();
     49            DownloadOsmTask task = new DownloadOsmTask();
     50            Future<?> future = task.download(
     51                    new OverpassDownloadReader(area, dialog.getOverpassQuery()),
     52                    dialog.isNewLayerRequired(), area, null);
     53            Main.worker.submit(new PostDownloadHandler(task, future));
     54        }
     55    }
     56
     57    static class OverpassDownloadDialog extends DownloadDialog {
     58
     59        protected HistoryComboBox overpassWizard;
     60        protected JTextArea overpassQuery;
     61        private static OverpassDownloadDialog instance;
     62        static final String OVERPASS_WIZARD_HISTORY_KEY = "download.overpass.wizard";
     63
     64        private OverpassDownloadDialog(Component parent) {
     65            super(parent);
     66            cbDownloadOsmData.setEnabled(false);
     67            cbDownloadOsmData.setSelected(false);
     68            cbDownloadGpxData.setVisible(false);
     69            cbDownloadNotes.setVisible(false);
     70            cbStartup.setVisible(false);
     71        }
     72
     73        static public OverpassDownloadDialog getInstance() {
     74            if (instance == null) {
     75                instance = new OverpassDownloadDialog(Main.parent);
     76            }
     77            return instance;
     78        }
     79
     80        @Override
     81        protected void buildMainPanelAboveDownloadSelections(JPanel pnl) {
     82
     83            pnl.add(new JLabel(), GBC.eol()); // needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes
     84
     85            final String tooltip = tr("Builds an Overpass query using the Overpass Turbo query wizard");
     86            overpassWizard = new HistoryComboBox();
     87            overpassWizard.setToolTipText(tooltip);
     88            final JButton buildQuery = new JButton(tr("Build query"));
     89            buildQuery.addActionListener(new AbstractAction() {
     90                @Override
     91                public void actionPerformed(ActionEvent e) {
     92                    final String overpassWizardText = overpassWizard.getText();
     93                    try {
     94                        overpassQuery.setText(OverpassTurboQueryWizard.getInstance().constructQuery(overpassWizardText));
     95                    } catch (OverpassTurboQueryWizard.ParseException ex) {
     96                        HelpAwareOptionPane.showOptionDialog(
     97                                Main.parent,
     98                                tr("<html>The Overpass wizard could not parse the following query:"
     99                                        + Utils.joinAsHtmlUnorderedList(Collections.singleton(overpassWizardText))),
     100                                tr("Parse error"),
     101                                JOptionPane.ERROR_MESSAGE,
     102                                null
     103                        );
     104                    }
     105                }
     106            });
     107            buildQuery.setToolTipText(tooltip);
     108            pnl.add(buildQuery, GBC.std().insets(5, 5, 5, 5));
     109            pnl.add(overpassWizard, GBC.eol().fill());
     110
     111            overpassQuery = new JTextArea("[timeout:15];", 8, 80);
     112            JScrollPane scrollPane = new JScrollPane(overpassQuery);
     113            pnl.add(new JLabel(tr("Overpass query: ")), GBC.std().insets(5, 5, 5, 5));
     114            GridBagConstraints gbc = GBC.eol().fill();
     115            gbc.ipady = 200;
     116            pnl.add(scrollPane, gbc);
     117        }
     118
     119        public String getOverpassQuery() {
     120            return overpassQuery.getText();
     121        }
     122
     123        @Override
     124        public void restoreSettings() {
     125            super.restoreSettings();
     126            overpassWizard.setPossibleItems(
     127                    Main.pref.getCollection(OVERPASS_WIZARD_HISTORY_KEY, new ArrayList<String>()));
     128        }
     129
     130        @Override
     131        public void rememberSettings() {
     132            super.rememberSettings();
     133            overpassWizard.addCurrentItemToHistory();
     134            Main.pref.putCollection(OVERPASS_WIZARD_HISTORY_KEY, overpassWizard.getHistory());
     135        }
     136
     137    }
     138
     139    static class OverpassDownloadReader extends BoundingBoxDownloader {
     140
     141        final String overpassQuery;
     142
     143        public OverpassDownloadReader(Bounds downloadArea, String overpassQuery) {
     144            super(downloadArea);
     145            this.overpassQuery = overpassQuery.trim();
     146        }
     147
     148        @Override
     149        protected String getBaseUrl() {
     150            return "https://overpass-api.de/api/";
     151        }
     152
     153        @Override
     154        protected String getRequestForBbox(double lon1, double lat1, double lon2, double lat2) {
     155            if (overpassQuery.isEmpty())
     156                return super.getRequestForBbox(lon1, lat1, lon2, lat2);
     157            else {
     158                String realQuery = completeOverpassQuery(overpassQuery);
     159                try {
     160                    return "interpreter?data=" + URLEncoder.encode(realQuery, "UTF-8") + "&bbox=" + lon1 + "," + lat1 + "," + lon2 + "," + lat2;
     161                } catch (UnsupportedEncodingException e) {
     162                    throw new IllegalStateException();
     163                }
     164            }
     165        }
     166
     167        private String completeOverpassQuery(String query) {
     168            int firstColon = query.indexOf(";");
     169            if (firstColon == -1) {
     170                return "[bbox];" + query;
     171            }
     172            int bboxPos = query.indexOf("[bbox");
     173            if (bboxPos > -1 && bboxPos < firstColon) {
     174                return query;
     175            }
     176
     177            int bracketCount = 0;
     178            int pos = 0;
     179            for (; pos < firstColon; ++pos) {
     180                if (query.charAt(pos) == '[')
     181                    ++bracketCount;
     182                else if (query.charAt(pos) == '[')
     183                    --bracketCount;
     184                else if (bracketCount == 0) {
     185                    if (!Character.isWhitespace(query.charAt(pos)))
     186                        break;
     187                }
     188            }
     189
     190            if (pos < firstColon) {
     191                // We start with a statement, not with declarations
     192                return "[bbox];" + query;
     193            }
     194
     195            // We start with declarations. Add just one more declaration in this case.
     196            return "[bbox]" + query;
     197        }
     198
     199        @Override
     200        public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException {
     201
     202            DataSet ds = super.parseOsm(progressMonitor);
     203
     204            // add bounds if necessary (note that Overpass API does not return bounds in the response XML)
     205            if (ds != null && ds.dataSources.isEmpty()) {
     206                if (crosses180th) {
     207                    Bounds bounds = new Bounds(lat1, lon1, lat2, 180.0);
     208                    DataSource src = new DataSource(bounds, getBaseUrl());
     209                    ds.dataSources.add(src);
     210
     211                    bounds = new Bounds(lat1, -180.0, lat2, lon2);
     212                    src = new DataSource(bounds, getBaseUrl());
     213                    ds.dataSources.add(src);
     214                } else {
     215                    Bounds bounds = new Bounds(lat1, lon1, lat2, lon2);
     216                    DataSource src = new DataSource(bounds, getBaseUrl());
     217                    ds.dataSources.add(src);
     218                }
     219            }
     220
     221            return ds;
     222        }
     223    }
     224}
  • new file src/org/openstreetmap/josm/actions/OverpassTurboQueryWizard.java

    diff --git a/src/org/openstreetmap/josm/actions/OverpassTurboQueryWizard.java b/src/org/openstreetmap/josm/actions/OverpassTurboQueryWizard.java
    new file mode 100644
    index 0000000..22b440e
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.actions;
     3
     4import org.openstreetmap.josm.io.CachedFile;
     5
     6import javax.script.Invocable;
     7import javax.script.ScriptEngine;
     8import javax.script.ScriptEngineManager;
     9import javax.script.ScriptException;
     10import java.io.IOException;
     11import java.io.InputStreamReader;
     12import java.io.Reader;
     13import java.nio.charset.StandardCharsets;
     14import java.util.regex.Pattern;
     15
     16/**
     17 * Uses <a href="https://github.com/tyrasd/overpass-turbo/">Overpass Turbo</a> query wizard code
     18 * to build an Overpass QL from a {@link org.openstreetmap.josm.actions.search.SearchAction} like query.
     19 *
     20 * Requires a JavaScript {@link ScriptEngine}.
     21 */
     22public class OverpassTurboQueryWizard {
     23
     24    private static OverpassTurboQueryWizard instance;
     25    private final ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
     26
     27    /**
     28     * An exception to indicate a failed parse.
     29     */
     30    public static class ParseException extends RuntimeException {
     31    }
     32
     33    /**
     34     * Replies the unique instance of this class.
     35     *
     36     * @return the unique instance of this class
     37     */
     38    public static synchronized OverpassTurboQueryWizard getInstance() {
     39        if (instance == null) {
     40            instance = new OverpassTurboQueryWizard();
     41        }
     42        return instance;
     43    }
     44
     45    private OverpassTurboQueryWizard() {
     46        try {
     47            engine.eval("var console = {log: function(){}};");
     48            final String baseUrl = "https://raw.githubusercontent.com/tyrasd/overpass-turbo/b0ef5ebbdd353c1bc7e45da527cab03498b4cbef/";
     49            // lodash is MIT Licensed
     50            initEngine(baseUrl + "/libs/lodash/lodash-2.4.1.js");
     51            // overpass-turbo is MIT Licensed
     52            initEngine(baseUrl + "/js/ffs.js");
     53            initEngine(baseUrl + "/js/ffs/free.js");
     54            initEngine(baseUrl + "/js/ffs/parser.js");
     55            engine.eval("var construct_query = turbo.ffs().construct_query;");
     56        } catch (ScriptException | IOException ex) {
     57            throw new RuntimeException("Failed to initialize OverpassTurboQueryWizard", ex);
     58        }
     59    }
     60
     61    private void initEngine(String url) throws ScriptException, IOException {
     62        try (Reader reader = new InputStreamReader(new CachedFile(url).getInputStream(), StandardCharsets.UTF_8)) {
     63            engine.eval(reader);
     64        }
     65    }
     66
     67    /**
     68     * Builds an Overpass QL from a {@link org.openstreetmap.josm.actions.search.SearchAction} like query.
     69     * @param search the {@link org.openstreetmap.josm.actions.search.SearchAction} like query
     70     * @return an Overpass QL query
     71     * @throws ParseException when the parsing fails
     72     */
     73    public String constructQuery(String search) throws ParseException {
     74        try {
     75            final Object result = ((Invocable) engine).invokeFunction("construct_query", search);
     76            if (result == Boolean.FALSE) {
     77                throw new ParseException();
     78            }
     79            String query = (String) result;
     80            query = Pattern.compile("^.*\\[out:json\\]", Pattern.DOTALL).matcher(query).replaceFirst("");
     81            query = Pattern.compile("^out.*", Pattern.MULTILINE).matcher(query).replaceAll("out meta;");
     82            query = query.replace("({{bbox}})", "");
     83            return query;
     84        } catch (NoSuchMethodException e) {
     85            throw new IllegalStateException();
     86        } catch (ScriptException e) {
     87            throw new RuntimeException("Failed to execute OverpassTurboQueryWizard", e);
     88        }
     89    }
     90
     91}
  • src/org/openstreetmap/josm/gui/MainMenu.java

    diff --git a/src/org/openstreetmap/josm/gui/MainMenu.java b/src/org/openstreetmap/josm/gui/MainMenu.java
    index 62cce67..06186b0 100644
    a b import org.openstreetmap.josm.actions.OpenFileAction;  
    8080import org.openstreetmap.josm.actions.OpenLocationAction;
    8181import org.openstreetmap.josm.actions.OrthogonalizeAction;
    8282import org.openstreetmap.josm.actions.OrthogonalizeAction.Undo;
     83import org.openstreetmap.josm.actions.OverpassDownloadAction;
    8384import org.openstreetmap.josm.actions.PasteAction;
    8485import org.openstreetmap.josm.actions.PasteTagsAction;
    8586import org.openstreetmap.josm.actions.PreferenceToggleAction;
    public class MainMenu extends JMenuBar {  
    165166    public final GpxExportAction gpxExport = new GpxExportAction();
    166167    /** File / Download from OSM... **/
    167168    public final DownloadAction download = new DownloadAction();
     169    /** File / Download from Overpass API... **/
     170    public final OverpassDownloadAction overpassDownload = new OverpassDownloadAction();
    168171    /** File / Download object... **/
    169172    public final DownloadPrimitiveAction downloadPrimitive = new DownloadPrimitiveAction();
    170173    /** File / Download notes in current view **/
    public class MainMenu extends JMenuBar {  
    638641        add(fileMenu, gpxExport, true);
    639642        fileMenu.addSeparator();
    640643        add(fileMenu, download);
     644        add(fileMenu, overpassDownload);
    641645        add(fileMenu, downloadPrimitive);
    642646        add(fileMenu, searchNotes);
    643647        add(fileMenu, downloadNotesInView);