001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.io.IOException; 005import java.io.InputStreamReader; 006import java.io.Reader; 007import java.nio.charset.StandardCharsets; 008import java.util.regex.Pattern; 009 010import javax.script.Invocable; 011import javax.script.ScriptEngine; 012import javax.script.ScriptEngineManager; 013import javax.script.ScriptException; 014 015/** 016 * Uses <a href="https://github.com/tyrasd/overpass-turbo/">Overpass Turbo</a> query wizard code 017 * to build an Overpass QL from a {@link org.openstreetmap.josm.actions.search.SearchAction} like query. 018 * 019 * Requires a JavaScript {@link ScriptEngine}. 020 * @since 8744 021 */ 022public final class OverpassTurboQueryWizard { 023 024 private static OverpassTurboQueryWizard instance; 025 private final ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript"); 026 027 /** 028 * An exception to indicate a failed parse. 029 */ 030 public static class ParseException extends RuntimeException { 031 } 032 033 /** 034 * Replies the unique instance of this class. 035 * 036 * @return the unique instance of this class 037 */ 038 public static synchronized OverpassTurboQueryWizard getInstance() { 039 if (instance == null) { 040 instance = new OverpassTurboQueryWizard(); 041 } 042 return instance; 043 } 044 045 private OverpassTurboQueryWizard() { 046 // overpass-turbo is MIT Licensed 047 048 try (final Reader reader = new InputStreamReader( 049 getClass().getResourceAsStream("/data/overpass-turbo-ffs.js"), StandardCharsets.UTF_8)) { 050 engine.eval("var console = {log: function(){}};"); 051 engine.eval(reader); 052 engine.eval("var construct_query = turbo.ffs().construct_query;"); 053 } catch (ScriptException | IOException ex) { 054 throw new RuntimeException("Failed to initialize OverpassTurboQueryWizard", ex); 055 } 056 } 057 058 /** 059 * Builds an Overpass QL from a {@link org.openstreetmap.josm.actions.search.SearchAction} like query. 060 * @param search the {@link org.openstreetmap.josm.actions.search.SearchAction} like query 061 * @return an Overpass QL query 062 * @throws ParseException when the parsing fails 063 */ 064 public String constructQuery(String search) throws ParseException { 065 try { 066 final Object result = ((Invocable) engine).invokeFunction("construct_query", search); 067 if (result == Boolean.FALSE) { 068 throw new ParseException(); 069 } 070 String query = (String) result; 071 query = Pattern.compile("^.*\\[out:json\\]", Pattern.DOTALL).matcher(query).replaceFirst(""); 072 query = Pattern.compile("^out.*", Pattern.MULTILINE).matcher(query).replaceAll("out meta;"); 073 query = query.replace("({{bbox}})", ""); 074 return query; 075 } catch (NoSuchMethodException e) { 076 throw new IllegalStateException(); 077 } catch (ScriptException e) { 078 throw new RuntimeException("Failed to execute OverpassTurboQueryWizard", e); 079 } 080 } 081 082}