Ticket #11428: 0001-Add-Overpass-download-dialog-to-core-provide-Overpas.v2.patch
File 0001-Add-Overpass-download-dialog-to-core-provide-Overpas.v2.patch, 17.7 KB (added by , 9 years ago) |
---|
-
new file 2001
From d7a889711ae3a92940f484704c072fc7ffae2ae6 Mon Sep 17 00:00:00 2001 From: Simon Legner <Simon.Legner@gmail.com> Date: Thu, 11 Jun 2015 20:46:06 +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 | 225 +++++++++++++++++++++ .../josm/actions/OverpassTurboQueryWizard.java | 91 +++++++++ src/org/openstreetmap/josm/gui/MainMenu.java | 4 + 4 files changed, 320 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 zcmV<I1rz#-P)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru-vki~7AEnVC!7EP1$jwC zK~zY`m6l&@6xS8Te|L8F|IVy;*BkG8Sv$7Z4#wDP92197CuyL6LR(d7<5r3)UQk3; z5tK@46jf@=dlf3Rl3)7J@K&i+ksn$mD9}(y06`#XWQ>iSVy|O^|1Mtd&d$v4+&p-3 za6_VS-_DuQ`R={veBXBkU%kA51;~4e7iEhEyat>Wh*61C_`A4eeOUqJD~cz9I$#Ho zx|eheSrUk}K%52sD^UJ0BwK?I1JF>*KMLFdrhp=_7ijxO;8qnlRXbTDkpG+br1&rZ zJ|F^I0Xl#(PyyZt)`7iC7mbU+0`L*=%8T;RP9AbS^*+?U0ngXsXMo3n_FCMz|HIS^ z07J6%?2v4o`o2ti9tJ=R!Vl)*U=TWM&E5|9f#>dR0uV#8rE5sG8bh-6&krnge{lTx zaX-R&&$7+_wbi_`_k)Z4r)eAEt{tqbtfK20E|-gig+-Fd1e23fWHN2!p1Z(O&p4fL zz2Jn)@k=he-#mKss0pCn4`5fzhqK+;p25Lq6e%UScTTa-C~;0ocK`ec*RPL}&Gs-q zKaZ-axZQ4w#UjG{V}7k3pzC0h;yIP-y)kj)5HS3OCVM^JeVI(VuTrrQLQs9IgY{&R zY}sUSagju#fy<XKW7{@UQ&WV)VJyp{v3?u7tKVi+T%ftF$?s9!`yMz&4o+8HU1am- zT3T9|@CS&DjL;a5(|G<oPfkzc^?I3`o5L^+gb);qMY6`T%(!lVUt-y2cWoMdn*hg- z9aAcHWm_Py!I_e=G1^i}jNknh^~4EeG>WXRr}3ZvVuw@V%)d{wuylv<Km8Hy?O6)0 zRZ1V6C7)kw1`<GG6CfCLHHSi)ttbKn1pFI7mSy5<Xh6ndOsCUKr_&INqHW)fq9_O< zkli~eX%U1Y@rWwA7IOAKzds6;Hvw$v>P)3lj&0jGj`ILYDj)<%364W5nc6&|8I58^ zBS=MH4h#^9#;WO5V`fVagp{3$hNRE7EmoG7Hr37J@lwsrA{G{C&gE#%<zO|>^5`i3 zx&X^-3)r@e&4%6;^96KW_o^;;=awFLz3#qveZ5*Pmnj-Wa<jLnR;v^W1zf#ZDlfl` zSXx5l^UQtk02kKQ_@DL>#o!uEO=*_(906OWE)=1oy+dtoZfXT?e||-FWsk0hDVB<y zKX;Dx^>t34IYlg9$Ly!G%)I(bW;LBl=`^NkqG=j?x_gP19$_WF!p-{2c;_N0wn8G2 zKvmU&Ej^G@rb3|*h4nRd_3a|8hcU`!OtZ|Hk&{@-IPcwE0;S5t<QT!Y!ulv!6^l}( zz+~(SkJ)|b{wR@141s9c$^(IL9EV-mex7{d8~A-8zVodE_&gyFWgjPCOJ4fv&)73C zNK11&PxpV9%9@`a{qQHuJ$((2@bOaS2<>ehNGXxE+M8PRi&u~QK9xwk=28TX<B*%l zk=m9*-kf4zZkBfjpF-Dl#>Q@t$+U6%_8oTa>>?*`GInr;)F(S>80p_2tmDXu@yX%0 z{&M2T{bJYK)BnV^upVS??l!@YhIA^tzPQM6G>Q`l;P-jy-`&TDSFV!n?cw9=*GUfF zrR2Ly{!kNr-B~VOxysCRZgF~Q@-T4nezDU5%0`)ZJce6Uu`H95?HK}Y6_49Z#kQH5 z$<dwd-tg2n*D+J8G`zW&REI{{Gyw=lqVbx<f3Bv9#>T2;S(j{E45+F~sklrqq%mO> zkd8yyFi_oY?kwMBJa>!qa2{`4mHFRjjGr0X!b#IKuL9Hm?+1_6{5T4DfLLt^qJROc z*8UfD{T^-4UmFh3o-Drm@vl~{0S-_F?3!1v*8KcazQ(gfJm|xaZ1H~`Yy1rGfhYUb 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..1a246e1
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.actions; 3 4 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 5 import static org.openstreetmap.josm.tools.I18n.tr; 6 7 import java.awt.Component; 8 import java.awt.event.ActionEvent; 9 import java.io.UnsupportedEncodingException; 10 import java.net.URLEncoder; 11 import java.util.ArrayList; 12 import java.util.Collections; 13 import java.util.concurrent.Future; 14 15 import javax.swing.*; 16 17 import org.openstreetmap.josm.Main; 18 import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask; 19 import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler; 20 import org.openstreetmap.josm.data.Bounds; 21 import org.openstreetmap.josm.data.DataSource; 22 import org.openstreetmap.josm.data.osm.DataSet; 23 import org.openstreetmap.josm.data.preferences.CollectionProperty; 24 import org.openstreetmap.josm.data.preferences.StringProperty; 25 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 26 import org.openstreetmap.josm.gui.download.DownloadDialog; 27 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 28 import org.openstreetmap.josm.gui.widgets.HistoryComboBox; 29 import org.openstreetmap.josm.io.BoundingBoxDownloader; 30 import org.openstreetmap.josm.io.OsmTransferException; 31 import org.openstreetmap.josm.tools.GBC; 32 import org.openstreetmap.josm.tools.Utils; 33 34 public class OverpassDownloadAction extends JosmAction { 35 36 public OverpassDownloadAction() { 37 super(tr("Download from Overpass API ..."), "download-overpass", tr("Download map data from Overpass API server."), 38 null, true, "overpassdownload/download", true); 39 putValue("help", ht("/Action/OverpassDownload")); 40 } 41 42 @Override 43 public void actionPerformed(ActionEvent e) { 44 OverpassDownloadDialog dialog = OverpassDownloadDialog.getInstance(); 45 dialog.restoreSettings(); 46 dialog.setVisible(true); 47 if (!dialog.isCanceled()) { 48 dialog.rememberSettings(); 49 Bounds area = dialog.getSelectedDownloadArea(); 50 DownloadOsmTask task = new DownloadOsmTask(); 51 Future<?> future = task.download( 52 new OverpassDownloadReader(area, dialog.getOverpassQuery()), 53 dialog.isNewLayerRequired(), area, null); 54 Main.worker.submit(new PostDownloadHandler(task, future)); 55 } 56 } 57 58 static class OverpassDownloadDialog extends DownloadDialog { 59 60 protected HistoryComboBox overpassWizard; 61 protected JTextArea overpassQuery; 62 private static OverpassDownloadDialog instance; 63 static final CollectionProperty OVERPASS_WIZARD_HISTORY = new CollectionProperty("download.overpass.wizard", new ArrayList<String>()); 64 65 private OverpassDownloadDialog(Component parent) { 66 super(parent); 67 cbDownloadOsmData.setEnabled(false); 68 cbDownloadOsmData.setSelected(false); 69 cbDownloadGpxData.setVisible(false); 70 cbDownloadNotes.setVisible(false); 71 cbStartup.setVisible(false); 72 } 73 74 static public OverpassDownloadDialog getInstance() { 75 if (instance == null) { 76 instance = new OverpassDownloadDialog(Main.parent); 77 } 78 return instance; 79 } 80 81 @Override 82 protected void buildMainPanelAboveDownloadSelections(JPanel pnl) { 83 84 pnl.add(new JLabel(), GBC.eol()); // needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes 85 86 final String tooltip = tr("Builds an Overpass query using the Overpass Turbo query wizard"); 87 overpassWizard = new HistoryComboBox(); 88 overpassWizard.setToolTipText(tooltip); 89 final JButton buildQuery = new JButton(tr("Build query")); 90 buildQuery.addActionListener(new AbstractAction() { 91 @Override 92 public void actionPerformed(ActionEvent e) { 93 final String overpassWizardText = overpassWizard.getText(); 94 try { 95 overpassQuery.setText(OverpassTurboQueryWizard.getInstance().constructQuery(overpassWizardText)); 96 } catch (OverpassTurboQueryWizard.ParseException ex) { 97 HelpAwareOptionPane.showOptionDialog( 98 Main.parent, 99 tr("<html>The Overpass wizard could not parse the following query:" 100 + Utils.joinAsHtmlUnorderedList(Collections.singleton(overpassWizardText))), 101 tr("Parse error"), 102 JOptionPane.ERROR_MESSAGE, 103 null 104 ); 105 } 106 } 107 }); 108 buildQuery.setToolTipText(tooltip); 109 pnl.add(buildQuery, GBC.std().insets(5, 5, 5, 5)); 110 pnl.add(overpassWizard, GBC.eol().fill(GBC.HORIZONTAL)); 111 112 overpassQuery = new JTextArea("[timeout:15];", 8, 80); 113 JScrollPane scrollPane = new JScrollPane(overpassQuery); 114 pnl.add(new JLabel(tr("Overpass query: ")), GBC.std().insets(5, 5, 5, 5)); 115 GBC gbc = GBC.eol().fill(GBC.HORIZONTAL); 116 gbc.ipady = 200; 117 pnl.add(scrollPane, gbc); 118 } 119 120 public String getOverpassQuery() { 121 return overpassQuery.getText(); 122 } 123 124 @Override 125 public void restoreSettings() { 126 super.restoreSettings(); 127 overpassWizard.setPossibleItems(OVERPASS_WIZARD_HISTORY.get()); 128 } 129 130 @Override 131 public void rememberSettings() { 132 super.rememberSettings(); 133 overpassWizard.addCurrentItemToHistory(); 134 OVERPASS_WIZARD_HISTORY.put(overpassWizard.getHistory()); 135 } 136 137 } 138 139 static class OverpassDownloadReader extends BoundingBoxDownloader { 140 141 final String overpassQuery; 142 static final StringProperty OVERPASS_URL = new StringProperty("download.overpass.url", "https://overpass-api.de/api/"); 143 144 public OverpassDownloadReader(Bounds downloadArea, String overpassQuery) { 145 super(downloadArea); 146 this.overpassQuery = overpassQuery.trim(); 147 } 148 149 @Override 150 protected String getBaseUrl() { 151 return OVERPASS_URL.get(); 152 } 153 154 @Override 155 protected String getRequestForBbox(double lon1, double lat1, double lon2, double lat2) { 156 if (overpassQuery.isEmpty()) 157 return super.getRequestForBbox(lon1, lat1, lon2, lat2); 158 else { 159 String realQuery = completeOverpassQuery(overpassQuery); 160 try { 161 return "interpreter?data=" + URLEncoder.encode(realQuery, "UTF-8") + "&bbox=" + lon1 + "," + lat1 + "," + lon2 + "," + lat2; 162 } catch (UnsupportedEncodingException e) { 163 throw new IllegalStateException(); 164 } 165 } 166 } 167 168 private String completeOverpassQuery(String query) { 169 int firstColon = query.indexOf(";"); 170 if (firstColon == -1) { 171 return "[bbox];" + query; 172 } 173 int bboxPos = query.indexOf("[bbox"); 174 if (bboxPos > -1 && bboxPos < firstColon) { 175 return query; 176 } 177 178 int bracketCount = 0; 179 int pos = 0; 180 for (; pos < firstColon; ++pos) { 181 if (query.charAt(pos) == '[') 182 ++bracketCount; 183 else if (query.charAt(pos) == '[') 184 --bracketCount; 185 else if (bracketCount == 0) { 186 if (!Character.isWhitespace(query.charAt(pos))) 187 break; 188 } 189 } 190 191 if (pos < firstColon) { 192 // We start with a statement, not with declarations 193 return "[bbox];" + query; 194 } 195 196 // We start with declarations. Add just one more declaration in this case. 197 return "[bbox]" + query; 198 } 199 200 @Override 201 public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException { 202 203 DataSet ds = super.parseOsm(progressMonitor); 204 205 // add bounds if necessary (note that Overpass API does not return bounds in the response XML) 206 if (ds != null && ds.dataSources.isEmpty()) { 207 if (crosses180th) { 208 Bounds bounds = new Bounds(lat1, lon1, lat2, 180.0); 209 DataSource src = new DataSource(bounds, getBaseUrl()); 210 ds.dataSources.add(src); 211 212 bounds = new Bounds(lat1, -180.0, lat2, lon2); 213 src = new DataSource(bounds, getBaseUrl()); 214 ds.dataSources.add(src); 215 } else { 216 Bounds bounds = new Bounds(lat1, lon1, lat2, lon2); 217 DataSource src = new DataSource(bounds, getBaseUrl()); 218 ds.dataSources.add(src); 219 } 220 } 221 222 return ds; 223 } 224 } 225 } -
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. 2 package org.openstreetmap.josm.actions; 3 4 import org.openstreetmap.josm.io.CachedFile; 5 6 import javax.script.Invocable; 7 import javax.script.ScriptEngine; 8 import javax.script.ScriptEngineManager; 9 import javax.script.ScriptException; 10 import java.io.IOException; 11 import java.io.InputStreamReader; 12 import java.io.Reader; 13 import java.nio.charset.StandardCharsets; 14 import 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 */ 22 public 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 304f536..a9befbf 100644
a b 81 81 import org.openstreetmap.josm.actions.OpenLocationAction; 82 82 import org.openstreetmap.josm.actions.OrthogonalizeAction; 83 83 import org.openstreetmap.josm.actions.OrthogonalizeAction.Undo; 84 import org.openstreetmap.josm.actions.OverpassDownloadAction; 84 85 import org.openstreetmap.josm.actions.PasteAction; 85 86 import org.openstreetmap.josm.actions.PasteTagsAction; 86 87 import org.openstreetmap.josm.actions.PreferenceToggleAction; … … 166 167 public final GpxExportAction gpxExport = new GpxExportAction(); 167 168 /** File / Download from OSM... **/ 168 169 public final DownloadAction download = new DownloadAction(); 170 /** File / Download from Overpass API... **/ 171 public final OverpassDownloadAction overpassDownload = new OverpassDownloadAction(); 169 172 /** File / Download object... **/ 170 173 public final DownloadPrimitiveAction downloadPrimitive = new DownloadPrimitiveAction(); 171 174 /** File / Download notes in current view **/ … … public MainMenu() { 639 642 add(fileMenu, gpxExport, true); 640 643 fileMenu.addSeparator(); 641 644 add(fileMenu, download); 645 add(fileMenu, overpassDownload); 642 646 add(fileMenu, downloadPrimitive); 643 647 add(fileMenu, searchNotes); 644 648 add(fileMenu, downloadNotesInView);