Index: /trunk/data/overpass-turbo-ffs.js
===================================================================
--- /trunk/data/overpass-turbo-ffs.js	(revision 8684)
+++ /trunk/data/overpass-turbo-ffs.js	(revision 8684)
@@ -0,0 +1,3 @@
+"undefined"==typeof turbo&&(turbo={}),turbo.ffs=function(){function e(e){function r(e){if(!e.logical)return[{logical:"and",queries:[e]}];if("and"===e.logical){for(var t=r(e.queries[0]),n=r(e.queries[1]),a=[],u=0;u<t.length;u++)for(var i=0;i<n.length;i++)a.push({logical:"and",queries:t[u].queries.concat(n[i].queries)});return a}if("or"===e.logical){var t=r(e.queries[0]),n=r(e.queries[1]);return[].concat(t,n)}alert("unsupported boolean operator: "+e.logical)}var t={logical:"or",queries:[]};return t.queries=r(e),t}function r(e){return e.replace(/([()[{*+.$^\\|?])/g,"\\$1")}var t,n={};return n.construct_query=function(a,u){function i(e){return e.replace(/\*\//g,"[…]").replace(/\n/g,"\\n")}function s(e){function t(e){return"string"==typeof e?e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\t/g,"\\t").replace(/\n/g,"\\n"):void 0}var n=t(e.key),a=t(e.val);switch("substr"===e.query&&(e.query="like",e.val={regex:r(e.val)}),""===a&&("eq"===e.query?(e.query="like",e.val={regex:"^$"}):"neq"===e.query&&(e.query="notlike",e.val={regex:"^$"})),""===n&&("key"===e.query?(e.query="likelike",n="^$",e.val={regex:".*"}):"eq"===e.query?(e.query="likelike",n="^$",e.val={regex:"^"+r(e.val)+"$"}):"like"===e.query&&(e.query="likelike",n="^$")),e.query){case"key":return'["'+n+'"]';case"nokey":return'["'+n+'"!~".*"]';case"eq":return'["'+n+'"="'+a+'"]';case"neq":return'["'+n+'"!="'+a+'"]';case"like":return'["'+n+'"~"'+t(e.val.regex)+'"'+("i"===e.val.modifier?",i":"")+"]";case"likelike":return'[~"'+n+'"~"'+t(e.val.regex)+'"'+("i"===e.val.modifier?",i":"")+"]";case"notlike":return'["'+n+'"!~"'+t(e.val.regex)+'"'+("i"===e.val.modifier?",i":"")+"]";case"meta":switch(e.meta){case"id":return"("+a+")";case"newer":return e.val.match(/^-?\d+ ?(seconds?|minutes?|hours?|days?|weeks?|months?|years?)?$/)?'(newer:"{{date:'+a+'}}")':'(newer:"'+a+'")';case"user":return'(user:"'+a+'")';case"uid":return"(uid:"+a+")";default:return console.log("unknown query type: meta/"+e.meta),!1}case"free form":default:return console.log("unknown query type: "+e.query),!1}}function o(e){function r(e){return null===e.match(/^[a-zA-Z0-9_]+$/)?'"'+e.replace(/"/g,'\\"')+'"':e}function t(e){return null===e.regex.match(/^[a-zA-Z0-9_]+$/)||e.modifier?"/"+e.regex.replace(/\//g,"\\/")+"/"+(e.modifier||""):e.regex}switch(e.query){case"key":return i(r(e.key)+"=*");case"nokey":return i(r(e.key)+"!=*");case"eq":return i(r(e.key)+"="+r(e.val));case"neq":return i(r(e.key)+"!="+r(e.val));case"like":return i(r(e.key)+"~"+t(e.val));case"likelike":return i("~"+r(e.key)+"~"+t(e.val));case"notlike":return i(r(e.key)+"!~"+t(e.val));case"substr":return i(r(e.key)+":"+r(e.val));case"meta":switch(e.meta){case"id":return i("id:"+r(e.val));case"newer":return i("newer:"+r(e.val));case"user":return i("user:"+r(e.val));case"uid":return i("uid:"+r(e.val));default:return""}case"free form":return i(r(e.free));default:return""}}try{n=turbo.ffs.parser.parse(a)}catch(l){return console.log("ffs parse error"),!1}var c,p=[];switch(p.push("/*"),u?p.push(u):(p.push("This has been generated by the overpass-turbo wizard."),p.push("The original search was:"),p.push("“"+i(a)+"”")),p.push("*/"),p.push("[out:json][timeout:25];"),n.bounds){case"area":p.push("// fetch area “"+n.area+"” to search in"),p.push("{{geocodeArea:"+n.area+"}}->.searchArea;"),c="(area.searchArea)";break;case"around":p.push("// adjust the search radius (in meters) here"),p.push("{{radius=1000}}"),c="(around:{{radius}},{{geocodeCoords:"+n.area+"}})";break;case"bbox":c="({{bbox}})";break;case"global":c=void 0;break;default:return alert("unknown bounds condition: "+n.bounds),!1}n.query=e(n.query),p.push("// gather results"),p.push("(");for(var $=0;$<n.query.queries.length;$++){for(var f=n.query.queries[$],y=["node","way","relation"],d=[],h=[],v=0;v<f.queries.length;v++){var g=f.queries[v];if("free form"===g.query){t||(t=turbo.ffs.free());var k=t.get_query_clause(g);if(k===!1)return!1;y=y.filter(function(e){return-1!=k.types.indexOf(e)}),h.push(o(g)),d=d.concat(k.conditions.map(function(e){return s(e)}))}else if("type"===g.query)y=-1!=y.indexOf(g.type)?[g.type]:[];else{h.push(o(g));var b=s(g);if(b===!1)return!1;d.push(b)}}h=h.join(" and "),p.push("  // query part for: “"+h+"”");for(var m=0;m<y.length;m++){for(var q="  "+y[m],x=0;x<d.length;x++)q+=d[x];c&&(q+=c),q+=";",p.push(q)}}return p.push(");"),p.push("// print results"),p.push("out body;"),p.push(">;"),p.push("out skel qt;"),p.join("\n")},n.repair_search=function(a){function u(e){return null===e.match(/^[a-zA-Z0-9_]+$/)?'"'+e.replace(/"/g,'\\"')+'"':e}function i(e){if("free form"===e.query){t||(t=turbo.ffs.free());var n=t.get_query_clause(e);if(n===!1){var i=t.fuzzy_search(e),s=null;try{s=new RegExp("['\"]?"+r(e.free)+"['\"]?")}catch(c){}if(i&&a.match(s)){o=o.concat(a.split(s)),a=o.pop();var p=u(i);o.push(p),l=!0}}}}try{n=turbo.ffs.parser.parse(a)}catch(s){return!1}var o=[],l=!1;return n.query=e(n.query),n.query.queries.forEach(function(e){e.queries.forEach(i)}),o.push(a),l?o:!1},n.invalidateCache=function(){t=void 0},n},"undefined"==typeof turbo&&(turbo={}),turbo.ffs.free=function(){var e={},r={};return function(){if("undefined"!=typeof $){var e="data/iD_presets.json";try{$.ajax(e,{async:!1,dataType:"json"}).success(function(e){r=e,Object.keys(r).map(function(e){var t=r[e];t.nameCased=t.name,t.name=t.name.toLowerCase(),t.terms=t.terms?t.terms.map(function(e){return e.toLowerCase()}):[]})}).error(function(){throw new Error})}catch(t){console.log("failed to load presets file",e,t)}}}(),function(){if("undefined"!=typeof $&&"undefined"!=typeof i18n){var e=i18n.getLanguage();if("en"!=e){var t="data/iD_presets_"+e+".json";try{$.ajax(t,{async:!1,dataType:"json"}).success(function(e){Object.keys(e).map(function(t){var n=e[t];t=r[t],t.translated=!0;var a=t.name;t.nameCased=n.name,t.name=n.name.toLowerCase(),n.terms&&(t.terms=n.terms.split(",").map(function(e){return e.trim().toLowerCase()}).concat(t.terms)),t.terms.unshift(a)})}).error(function(){throw new Error})}catch(n){console.log("failed to load preset translations file: "+t)}}}}(),e.get_query_clause=function(e){function t(e,r,t){return t.indexOf(e)===r}var n=e.free.toLowerCase(),a=Object.keys(r).map(function(e){return r[e]}).filter(function(e){return e.searchable===!1?!1:e.name===n?!0:(e._termsIndex=e.terms.indexOf(n),-1!=e._termsIndex)});if(0===a.length)return!1;a.sort(function(e,r){return e.name===n?-1:r.name===n?1:e._termsIndex-r._termsIndex});var u=a[0],i=[];return u.geometry.forEach(function(e){switch(e){case"point":case"vertex":i.push("node");break;case"line":i.push("way");break;case"area":i.push("way"),i.push("relation");break;case"relation":i.push("relation");break;default:console.log("unknown geometry type "+e+" of preset "+u.name)}}),{types:i.filter(t),conditions:Object.keys(u.tags).map(function(e){var r=u.tags[e];return{query:"*"===r?"key":"eq",key:e,val:r}})}},e.fuzzy_search=function(e){function t(e){return levenshteinDistance(e,a)<=u}function n(e){return[e.name].concat(e.terms).map(function(e,r){return levenshteinDistance(e,a)}).reduce(function(e,r){return r>=e?e:r})}var a=e.free.toLowerCase(),u=2+Math.floor(a.length/7),i=Object.keys(r).map(function(e){return r[e]}).filter(function(e){return e.searchable===!1?!1:t(e.name)?!0:e.terms.some(t)});if(0===i.length)return!1;i.sort(function(e,r){return n(e)-n(r)});var s=i[0];return s.nameCased},e},turbo.ffs.parser=function(){function e(e,r){function t(){this.constructor=e}t.prototype=r.prototype,e.prototype=new t}function r(e,r,t,n,a,u){this.message=e,this.expected=r,this.found=t,this.offset=n,this.line=a,this.column=u,this.name="SyntaxError"}function t(e){function t(r){function t(r,t,n){var a,u;for(a=t;n>a;a++)u=e.charAt(a),"\n"===u?(r.seenCR||r.line++,r.column=1,r.seenCR=!1):"\r"===u||"\u2028"===u||"\u2029"===u?(r.line++,r.column=1,r.seenCR=!0):(r.column++,r.seenCR=!1)}return h!==r&&(h>r&&(h=0,v={line:1,column:1,seenCR:!1}),t(v,h,r),h=r),v}function n(e){g>y||(y>g&&(g=y,k=[]),k.push(e))}function a(n,a,u){function i(e){var r=1;for(e.sort(function(e,r){return e.description<r.description?-1:e.description>r.description?1:0});r<e.length;)e[r-1]===e[r]?e.splice(r,1):r++}function s(e,r){function t(e){function r(e){return e.charCodeAt(0).toString(16).toUpperCase()}return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\x08/g,"\\b").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\f/g,"\\f").replace(/\r/g,"\\r").replace(/[\x00-\x07\x0B\x0E\x0F]/g,function(e){return"\\x0"+r(e)}).replace(/[\x10-\x1F\x80-\xFF]/g,function(e){return"\\x"+r(e)}).replace(/[\u0180-\u0FFF]/g,function(e){return"\\u0"+r(e)}).replace(/[\u1080-\uFFFF]/g,function(e){return"\\u"+r(e)})}var n,a,u,i=new Array(e.length);for(u=0;u<e.length;u++)i[u]=e[u].description;return n=e.length>1?i.slice(0,-1).join(", ")+" or "+i[e.length-1]:i[0],a=r?'"'+t(r)+'"':"end of input","Expected "+n+" but "+a+" found."}var o=t(u),l=u<e.length?e.charAt(u):null;return null!==a&&i(a),new r(null!==n?n:s(a,l),a,l,u,o.line,o.column)}function u(e){var r,t=new Array(e.length);for(r=0;r<e.length;r++)t[r]=e.charCodeAt(r)-32;return t}function i(r){function t(e){return"[object Array]"===Object.prototype.toString.apply(e)?[]:e}for(var a,u,s=f[r],o=0,c=[],p=s.length,h=[],v=[];;){for(;p>o;)switch(s[o]){case 0:v.push(t($[s[o+1]])),o+=2;break;case 1:v.push(y),o++;break;case 2:v.pop(),o++;break;case 3:y=v.pop(),o++;break;case 4:v.length-=s[o+1],o+=2;break;case 5:v.splice(-2,1),o++;break;case 6:v[v.length-2].push(v.pop()),o++;break;case 7:v.push(v.splice(v.length-s[o+1],s[o+1])),o+=2;break;case 8:v.pop(),v.push(e.substring(v[v.length-1],y)),o++;break;case 9:h.push(p),c.push(o+3+s[o+1]+s[o+2]),v[v.length-1]?(p=o+3+s[o+1],o+=3):(p=o+3+s[o+1]+s[o+2],o+=3+s[o+1]);break;case 10:h.push(p),c.push(o+3+s[o+1]+s[o+2]),v[v.length-1]===l?(p=o+3+s[o+1],o+=3):(p=o+3+s[o+1]+s[o+2],o+=3+s[o+1]);break;case 11:h.push(p),c.push(o+3+s[o+1]+s[o+2]),v[v.length-1]!==l?(p=o+3+s[o+1],o+=3):(p=o+3+s[o+1]+s[o+2],o+=3+s[o+1]);break;case 12:v[v.length-1]!==l?(h.push(p),c.push(o),p=o+2+s[o+1],o+=2):o+=2+s[o+1];break;case 13:h.push(p),c.push(o+3+s[o+1]+s[o+2]),e.length>y?(p=o+3+s[o+1],o+=3):(p=o+3+s[o+1]+s[o+2],o+=3+s[o+1]);break;case 14:h.push(p),c.push(o+4+s[o+2]+s[o+3]),e.substr(y,$[s[o+1]].length)===$[s[o+1]]?(p=o+4+s[o+2],o+=4):(p=o+4+s[o+2]+s[o+3],o+=4+s[o+2]);break;case 15:h.push(p),c.push(o+4+s[o+2]+s[o+3]),e.substr(y,$[s[o+1]].length).toLowerCase()===$[s[o+1]]?(p=o+4+s[o+2],o+=4):(p=o+4+s[o+2]+s[o+3],o+=4+s[o+2]);break;case 16:h.push(p),c.push(o+4+s[o+2]+s[o+3]),$[s[o+1]].test(e.charAt(y))?(p=o+4+s[o+2],o+=4):(p=o+4+s[o+2]+s[o+3],o+=4+s[o+2]);break;case 17:v.push(e.substr(y,s[o+1])),y+=s[o+1],o+=2;break;case 18:v.push($[s[o+1]]),y+=$[s[o+1]].length,o+=2;break;case 19:v.push(l),0===b&&n($[s[o+1]]),o+=2;break;case 20:d=v[v.length-1-s[o+1]],o+=2;break;case 21:d=y,o++;break;case 22:for(a=s.slice(o+4,o+4+s[o+3]),u=0;u<s[o+3];u++)a[u]=v[v.length-1-a[u]];v.splice(v.length-s[o+2],s[o+2],$[s[o+1]].apply(null,a)),o+=4+s[o+3];break;case 23:v.push(i(s[o+1])),o+=2;break;case 24:b++,o++;break;case 25:b--,o++;break;default:throw new Error("Invalid opcode: "+s[o]+".")}if(!(h.length>0))break;p=h.pop(),o=c.pop()}return v[0]}var s,o=arguments.length>1?arguments[1]:{},l={},c={start:0},p=0,$=[l,function(e){return e},[],"in bbox",{type:"literal",value:"in bbox",description:'"in bbox"'},"IN BBOX",{type:"literal",value:"IN BBOX",description:'"IN BBOX"'},function(e){return{bounds:"bbox",query:e}},"in",{type:"literal",value:"in",description:'"in"'},"IN",{type:"literal",value:"IN",description:'"IN"'},function(e,r){return{bounds:"area",query:e,area:r}},"around",{type:"literal",value:"around",description:'"around"'},"AROUND",{type:"literal",value:"AROUND",description:'"AROUND"'},function(e,r){return{bounds:"around",query:e,area:r}},"global",{type:"literal",value:"global",description:'"global"'},"GLOBAL",{type:"literal",value:"GLOBAL",description:'"GLOBAL"'},function(e){return{bounds:"global",query:e}},"or",{type:"literal",value:"or",description:'"or"'},"OR",{type:"literal",value:"OR",description:'"OR"'},"||",{type:"literal",value:"||",description:'"||"'},"|",{type:"literal",value:"|",description:'"|"'},function(e,r){return{logical:"or",queries:[e,r]}},"and",{type:"literal",value:"and",description:'"and"'},"AND",{type:"literal",value:"AND",description:'"AND"'},"&&",{type:"literal",value:"&&",description:'"&&"'},"&",{type:"literal",value:"&",description:'"&"'},function(e,r){return{logical:"and",queries:[e,r]}},"(",{type:"literal",value:"(",description:'"("'},")",{type:"literal",value:")",description:'")"'},function(e){return e},"=",{type:"literal",value:"=",description:'"="'},"==",{type:"literal",value:"==",description:'"=="'},function(e,r){return{query:"eq",key:e,val:r}},"!=",{type:"literal",value:"!=",description:'"!="'},"<>",{type:"literal",value:"<>",description:'"<>"'},function(e,r){return{query:"neq",key:e,val:r}},"*",{type:"literal",value:"*",description:'"*"'},function(e){return{query:"key",key:e}},"is",{type:"literal",value:"is",description:'"is"'},"not",{type:"literal",value:"not",description:'"not"'},"null",{type:"literal",value:"null",description:'"null"'},"IS",{type:"literal",value:"IS",description:'"IS"'},"NOT",{type:"literal",value:"NOT",description:'"NOT"'},"NULL",{type:"literal",value:"NULL",description:'"NULL"'},function(e){return{query:"nokey",key:e}},"~=",{type:"literal",value:"~=",description:'"~="'},"~",{type:"literal",value:"~",description:'"~"'},"=~",{type:"literal",value:"=~",description:'"=~"'},function(e,r){return{query:"like",key:e,val:r.regex?r:{regex:r}}},"like",{type:"literal",value:"like",description:'"like"'},"LIKE",{type:"literal",value:"LIKE",description:'"LIKE"'},function(e,r){return{query:"likelike",key:e,val:r.regex?r:{regex:r}}},"!~",{type:"literal",value:"!~",description:'"!~"'},function(e,r){return{query:"notlike",key:e,val:r.regex?r:{regex:r}}},":",{type:"literal",value:":",description:'":"'},function(e,r){return{query:"substr",key:e,val:r}},"type",{type:"literal",value:"type",description:'"type"'},function(e){return{query:"type",type:e}},"user",{type:"literal",value:"user",description:'"user"'},"uid",{type:"literal",value:"uid",description:'"uid"'},"newer",{type:"literal",value:"newer",description:'"newer"'},"id",{type:"literal",value:"id",description:'"id"'},function(e,r){return{query:"meta",meta:e,val:r}},function(e){return{query:"free form",free:e}},{type:"other",description:"Key"},/^[a-zA-Z0-9_:\-]/,{type:"class",value:"[a-zA-Z0-9_:\\-]",description:"[a-zA-Z0-9_:\\-]"},function(e){return e.join("")},'"',{type:"literal",value:'"',description:'"\\""'},"'",{type:"literal",value:"'",description:'"\'"'},function(e){return e[1]},{type:"other",description:"string"},/^[^'" ()~=!*\/:<>&|[\]{}#+@$%?\^.,]/,{type:"class",value:"[^'\" ()~=!*\\/:<>&|[\\]{}#+@$%?\\^.,]",description:"[^'\" ()~=!*\\/:<>&|[\\]{}#+@$%?\\^.,]"},function(e){return e.join("")},void 0,"\\",{type:"literal",value:"\\",description:'"\\\\"'},{type:"any",description:"any character"},function(e){return e},function(e){return e},/^['"\\bfnrtv]/,{type:"class",value:"['\"\\\\bfnrtv]",description:"['\"\\\\bfnrtv]"},function(e){return e.replace("b","\b").replace("f","\f").replace("n","\n").replace("r","\r").replace("t","	").replace("v","
+")},"/",{type:"literal",value:"/",description:'"/"'},null,"i",{type:"literal",value:"i",description:'"i"'},"",function(e){return{regex:e[1],modifier:e[3]}},"\\/",{type:"literal",value:"\\/",description:'"\\\\/"'},function(){return"/"},{type:"other",description:"whitespace"},/^[ \t\n\r]/,{type:"class",value:"[ \\t\\n\\r]",description:"[ \\t\\n\\r]"}],f=[u('!7;+<$7!+2%7;+(%4#6!#!!%$##  $"#  "#  '),u('!7"+]$ "7<+&$,#&7<"""  +D%.#""2#3$*) ".%""2%3&+(%4#6\'#!"%$##  $"#  "#  *Ř "!7"+$ "7<+&$,#&7<"""  +h%.(""2(3)*) ".*""2*3++L% "7<+&$,#&7<"""  +3%72+)%4%6,%"$ %$%#  $$#  $##  $"#  "#  *é "!7"+$ "7<+&$,#&7<"""  +h%.-""2-3.*) "./""2/30+L% "7<+&$,#&7<"""  +3%72+)%4%61%"$ %$%#  $$#  $##  $"#  "#  *z "!7"+]$ "7<+&$,#&7<"""  +D%.2""2233*) ".4""2435+(%4#66#!"%$##  $"#  "#  */ "!7"+\' 4!6\'!! %'),u('!7#+$ "7<+&$,#&7<"""  +%.7""2738*A ".9""293:*5 ".;""2;3<*) ".=""2=3>+L% "7<+&$,#&7<"""  +3%7"+)%4%6?%"$ %$%#  $$#  $##  $"#  "#  *# "7#'),u('!7$+$ "7<+&$,#&7<"""  +%.@""2@3A*A ".B""2B3C*5 ".D""2D3E*) ".F""2F3G+L% "7<+&$,#&7<"""  +3%7#+)%4%6H%"$ %$%#  $$#  $##  $"#  "#  *# "7$'),u('7%*g "!.I""2I3J+V$7;+L%7"+B%7;+8%.K""2K3L+(%4%6M%!"%$%#  $$#  $##  $"#  "#  '),u('7.*Y "7/*S "7&*M "7\'*G "7(*A "7)*; "7**5 "7+*/ "7,*) "7-*# "70'),u('!71+c$7;+Y%.N""2N3O*) ".P""2P3Q+=%7;+3%72+)%4%6R%"$ %$%#  $$#  $##  $"#  "#  '),u('!71+c$7;+Y%.S""2S3T*) ".U""2U3V+=%7;+3%72+)%4%6W%"$ %$%#  $$#  $##  $"#  "#  '),u('!71+h$7;+^%.N""2N3O*) ".P""2P3Q+B%7;+8%.X""2X3Y+(%4%6Z%!$%$%#  $$#  $##  $"#  "#  *Ģ "!72+ė$ "7<+&$,#&7<"""  +þ%!.[""2[3\\+u$ "7<+&$,#&7<"""  +\\%.]""2]3^+L% "7<+&$,#&7<"""  +3%._""2_3`+#%\'%%$%#  $$#  $##  $"#  "#  * "!.a""2a3b+u$ "7<+&$,#&7<"""  +\\%.c""2c3d+L% "7<+&$,#&7<"""  +3%.e""2e3f+#%\'%%$%#  $$#  $##  $"#  "#  +(%4#6Z#!"%$##  $"#  "#  '),u('!71+h$7;+^%.S""2S3T*) ".U""2U3V+B%7;+8%.X""2X3Y+(%4%6g%!$%$%#  $$#  $##  $"#  "#  *Ð "!72+Å$ "7<+&$,#&7<"""  +¬%!.[""2[3\\+L$ "7<+&$,#&7<"""  +3%._""2_3`+#%\'#%$##  $"#  "#  *] "!.a""2a3b+L$ "7<+&$,#&7<"""  +3%.e""2e3f+#%\'#%$##  $"#  "#  +(%4#6g#!"%$##  $"#  "#  '),u('!71+u$7;+k%.h""2h3i*5 ".j""2j3k*) ".l""2l3m+C%7;+9%72*# "78+)%4%6n%"$ %$%#  $$#  $##  $"#  "#  * "!72+$ "7<+&$,#&7<"""  +n%.o""2o3p*) ".q""2q3r+R% "7<+&$,#&7<"""  +9%72*# "78+)%4%6n%"$ %$%#  $$#  $##  $"#  "#  '),u('!.j""2j3k+$7;+%72+u%7;+k%.h""2h3i*5 ".j""2j3k*) ".l""2l3m+C%7;+9%72*# "78+)%4\'6s\'"$ %$\'#  $&#  $%#  $$#  $##  $"#  "#  '),u('!71+]$7;+S%.t""2t3u+C%7;+9%72*# "78+)%4%6v%"$ %$%#  $$#  $##  $"#  "#  *ú "!72+ï$ "7<+&$,#&7<"""  +Ö%!.]""2]3^+L$ "7<+&$,#&7<"""  +3%.o""2o3p+#%\'#%$##  $"#  "#  *] "!.c""2c3d+L$ "7<+&$,#&7<"""  +3%.q""2q3r+#%\'#%$##  $"#  "#  +R% "7<+&$,#&7<"""  +9%72*# "78+)%4%6v%"$ %$%#  $$#  $##  $"#  "#  '),u('!72+W$7;+M%.w""2w3x+=%7;+3%72+)%4%6y%"$ %$%#  $$#  $##  $"#  "#  '),u('!.z""2z3{+V$7;+L%.w""2w3x+<%7;+2%72+(%4%6|%! %$%#  $$#  $##  $"#  "#  '),u('!.}""2}3~*A ".""23*5 ".""23*) ".""23+W$7;+M%.w""2w3x+=%7;+3%72+)%4%6
+%"$ %$%#  $$#  $##  $"#  "#  '),u("!72+' 4!6!! %"),u('8! "0""1!3+,$,)&0""1!3"""  +\' 4!6!! %* "!!.""23+=$73+3%.""23+#%\'#%$##  $"#  "#  *N "!.""23+=$74+3%.""23+#%\'#%$##  $"#  "#  +\' 4!6!! %9*" 3'),u('8! "0""1!3+,$,)&0""1!3"""  +\' 4!6!! %* "!!.""23+=$73+3%.""23+#%\'#%$##  $"#  "#  *N "!.""23+=$74+3%.""23+#%\'#%$##  $"#  "#  +\' 4!6!! %9*" 3'),u('! "75,#&75"+\' 4!6!! %'),u('! "76,#&76"+\' 4!6!! %'),u('!!8.""23*) ".""239*$$"" "#  +7$-""1!3+(%4"6"! %$"#  "#  *C "!.""23+2$77+(%4"6"! %$"#  "#  '),u('!!8.""23*) ".""239*$$"" "#  +7$-""1!3+(%4"6"! %$"#  "#  *C "!.""23+2$77+(%4"6"! %$"#  "#  '),u('!0""1!3+\' 4!6!! %'),u('8!!.""23+Y$79+O%.""23+?%. ""2 3¡*# " ¢*# " +#%\'$%$$#  $##  $"#  "#  +\' 4!6£!! %9*" 3'),u('! "7:+&$,#&7:"""  +\' 4!6!! %'),u('!!8.""23*) ".¤""2¤3¥9*$$"" "#  +7$-""1!3+(%4"6"! %$"#  "#  *4 "!.¤""2¤3¥+& 4!6¦! %'),u('8 "7<,#&7<"9*" 3§'),u('80¨""1!3©9*" 3§')],y=0,d=0,h=0,v={line:1,column:1,seenCR:!1},g=0,k=[],b=0;if("startRule"in o){if(!(o.startRule in c))throw new Error("Can't start parsing from rule \""+o.startRule+'".');p=c[o.startRule]}if(s=i(p),s!==l&&y===e.length)return s;throw s!==l&&y<e.length&&n({type:"end",description:"end of input"}),a(null,k,g)}return e(r,Error),{SyntaxError:r,parse:t}}();
Index: /trunk/src/org/openstreetmap/josm/actions/OverpassDownloadAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/OverpassDownloadAction.java	(revision 8684)
+++ /trunk/src/org/openstreetmap/josm/actions/OverpassDownloadAction.java	(revision 8684)
@@ -0,0 +1,225 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.Future;
+
+import javax.swing.*;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
+import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.DataSource;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.preferences.CollectionProperty;
+import org.openstreetmap.josm.data.preferences.StringProperty;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.download.DownloadDialog;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
+import org.openstreetmap.josm.io.BoundingBoxDownloader;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.Utils;
+
+public class OverpassDownloadAction extends JosmAction {
+
+    public OverpassDownloadAction() {
+        super(tr("Download from Overpass API ..."), "download-overpass", tr("Download map data from Overpass API server."),
+                null, true, "overpassdownload/download", true);
+        putValue("help", ht("/Action/OverpassDownload"));
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        OverpassDownloadDialog dialog = OverpassDownloadDialog.getInstance();
+        dialog.restoreSettings();
+        dialog.setVisible(true);
+        if (!dialog.isCanceled()) {
+            dialog.rememberSettings();
+            Bounds area = dialog.getSelectedDownloadArea();
+            DownloadOsmTask task = new DownloadOsmTask();
+            Future<?> future = task.download(
+                    new OverpassDownloadReader(area, dialog.getOverpassQuery()),
+                    dialog.isNewLayerRequired(), area, null);
+            Main.worker.submit(new PostDownloadHandler(task, future));
+        }
+    }
+
+    static class OverpassDownloadDialog extends DownloadDialog {
+
+        protected HistoryComboBox overpassWizard;
+        protected JTextArea overpassQuery;
+        private static OverpassDownloadDialog instance;
+        static final CollectionProperty OVERPASS_WIZARD_HISTORY = new CollectionProperty("download.overpass.wizard", new ArrayList<String>());
+
+        private OverpassDownloadDialog(Component parent) {
+            super(parent);
+            cbDownloadOsmData.setEnabled(false);
+            cbDownloadOsmData.setSelected(false);
+            cbDownloadGpxData.setVisible(false);
+            cbDownloadNotes.setVisible(false);
+            cbStartup.setVisible(false);
+        }
+
+        static public OverpassDownloadDialog getInstance() {
+            if (instance == null) {
+                instance = new OverpassDownloadDialog(Main.parent);
+            }
+            return instance;
+        }
+
+        @Override
+        protected void buildMainPanelAboveDownloadSelections(JPanel pnl) {
+
+            pnl.add(new JLabel(), GBC.eol()); // needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes
+
+            final String tooltip = tr("Builds an Overpass query using the Overpass Turbo query wizard");
+            overpassWizard = new HistoryComboBox();
+            overpassWizard.setToolTipText(tooltip);
+            final JButton buildQuery = new JButton(tr("Build query"));
+            buildQuery.addActionListener(new AbstractAction() {
+                @Override
+                public void actionPerformed(ActionEvent e) {
+                    final String overpassWizardText = overpassWizard.getText();
+                    try {
+                        overpassQuery.setText(OverpassTurboQueryWizard.getInstance().constructQuery(overpassWizardText));
+                    } catch (OverpassTurboQueryWizard.ParseException ex) {
+                        HelpAwareOptionPane.showOptionDialog(
+                                Main.parent,
+                                tr("<html>The Overpass wizard could not parse the following query:"
+                                        + Utils.joinAsHtmlUnorderedList(Collections.singleton(overpassWizardText))),
+                                tr("Parse error"),
+                                JOptionPane.ERROR_MESSAGE,
+                                null
+                        );
+                    }
+                }
+            });
+            buildQuery.setToolTipText(tooltip);
+            pnl.add(buildQuery, GBC.std().insets(5, 5, 5, 5));
+            pnl.add(overpassWizard, GBC.eol().fill(GBC.HORIZONTAL));
+
+            overpassQuery = new JTextArea("[timeout:15];", 8, 80);
+            JScrollPane scrollPane = new JScrollPane(overpassQuery);
+            pnl.add(new JLabel(tr("Overpass query: ")), GBC.std().insets(5, 5, 5, 5));
+            GBC gbc = GBC.eol().fill(GBC.HORIZONTAL);
+            gbc.ipady = 200;
+            pnl.add(scrollPane, gbc);
+        }
+
+        public String getOverpassQuery() {
+            return overpassQuery.getText();
+        }
+
+        @Override
+        public void restoreSettings() {
+            super.restoreSettings();
+            overpassWizard.setPossibleItems(OVERPASS_WIZARD_HISTORY.get());
+        }
+
+        @Override
+        public void rememberSettings() {
+            super.rememberSettings();
+            overpassWizard.addCurrentItemToHistory();
+            OVERPASS_WIZARD_HISTORY.put(overpassWizard.getHistory());
+        }
+
+    }
+
+    static class OverpassDownloadReader extends BoundingBoxDownloader {
+
+        final String overpassQuery;
+        static final StringProperty OVERPASS_URL = new StringProperty("download.overpass.url", "https://overpass-api.de/api/");
+
+        public OverpassDownloadReader(Bounds downloadArea, String overpassQuery) {
+            super(downloadArea);
+            this.overpassQuery = overpassQuery.trim();
+        }
+
+        @Override
+        protected String getBaseUrl() {
+            return OVERPASS_URL.get();
+        }
+
+        @Override
+        protected String getRequestForBbox(double lon1, double lat1, double lon2, double lat2) {
+            if (overpassQuery.isEmpty())
+                return super.getRequestForBbox(lon1, lat1, lon2, lat2);
+            else {
+                String realQuery = completeOverpassQuery(overpassQuery);
+                try {
+                    return "interpreter?data=" + URLEncoder.encode(realQuery, "UTF-8") + "&bbox=" + lon1 + "," + lat1 + "," + lon2 + "," + lat2;
+                } catch (UnsupportedEncodingException e) {
+                    throw new IllegalStateException();
+                }
+            }
+        }
+
+        private String completeOverpassQuery(String query) {
+            int firstColon = query.indexOf(";");
+            if (firstColon == -1) {
+                return "[bbox];" + query;
+            }
+            int bboxPos = query.indexOf("[bbox");
+            if (bboxPos > -1 && bboxPos < firstColon) {
+                return query;
+            }
+
+            int bracketCount = 0;
+            int pos = 0;
+            for (; pos < firstColon; ++pos) {
+                if (query.charAt(pos) == '[')
+                    ++bracketCount;
+                else if (query.charAt(pos) == '[')
+                    --bracketCount;
+                else if (bracketCount == 0) {
+                    if (!Character.isWhitespace(query.charAt(pos)))
+                        break;
+                }
+            }
+
+            if (pos < firstColon) {
+                // We start with a statement, not with declarations
+                return "[bbox];" + query;
+            }
+
+            // We start with declarations. Add just one more declaration in this case.
+            return "[bbox]" + query;
+        }
+
+        @Override
+        public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException {
+
+            DataSet ds = super.parseOsm(progressMonitor);
+
+            // add bounds if necessary (note that Overpass API does not return bounds in the response XML)
+            if (ds != null && ds.dataSources.isEmpty()) {
+                if (crosses180th) {
+                    Bounds bounds = new Bounds(lat1, lon1, lat2, 180.0);
+                    DataSource src = new DataSource(bounds, getBaseUrl());
+                    ds.dataSources.add(src);
+
+                    bounds = new Bounds(lat1, -180.0, lat2, lon2);
+                    src = new DataSource(bounds, getBaseUrl());
+                    ds.dataSources.add(src);
+                } else {
+                    Bounds bounds = new Bounds(lat1, lon1, lat2, lon2);
+                    DataSource src = new DataSource(bounds, getBaseUrl());
+                    ds.dataSources.add(src);
+                }
+            }
+
+            return ds;
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/actions/OverpassTurboQueryWizard.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/OverpassTurboQueryWizard.java	(revision 8684)
+++ /trunk/src/org/openstreetmap/josm/actions/OverpassTurboQueryWizard.java	(revision 8684)
@@ -0,0 +1,81 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions;
+
+import javax.script.Invocable;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Pattern;
+
+/**
+ * Uses <a href="https://github.com/tyrasd/overpass-turbo/">Overpass Turbo</a> query wizard code
+ * to build an Overpass QL from a {@link org.openstreetmap.josm.actions.search.SearchAction} like query.
+ *
+ * Requires a JavaScript {@link ScriptEngine}.
+ */
+public class OverpassTurboQueryWizard {
+
+    private static OverpassTurboQueryWizard instance;
+    private final ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
+
+    /**
+     * An exception to indicate a failed parse.
+     */
+    public static class ParseException extends RuntimeException {
+    }
+
+    /**
+     * Replies the unique instance of this class.
+     *
+     * @return the unique instance of this class
+     */
+    public static synchronized OverpassTurboQueryWizard getInstance() {
+        if (instance == null) {
+            instance = new OverpassTurboQueryWizard();
+        }
+        return instance;
+    }
+
+    private OverpassTurboQueryWizard() {
+        // overpass-turbo is MIT Licensed
+
+        try (final Reader reader = new InputStreamReader(
+                getClass().getResourceAsStream("/data/overpass-turbo-ffs.js"), StandardCharsets.UTF_8)) {
+            //engine.eval("var turbo = {ffs: {noPresets: true}};");
+            engine.eval("var console = {log: function(){}};");
+            engine.eval(reader);
+            engine.eval("var construct_query = turbo.ffs().construct_query;");
+        } catch (ScriptException | IOException ex) {
+            throw new RuntimeException("Failed to initialize OverpassTurboQueryWizard", ex);
+        }
+    }
+
+    /**
+     * Builds an Overpass QL from a {@link org.openstreetmap.josm.actions.search.SearchAction} like query.
+     * @param search the {@link org.openstreetmap.josm.actions.search.SearchAction} like query
+     * @return an Overpass QL query
+     * @throws ParseException when the parsing fails
+     */
+    public String constructQuery(String search) throws ParseException {
+        try {
+            final Object result = ((Invocable) engine).invokeFunction("construct_query", search);
+            if (result == Boolean.FALSE) {
+                throw new ParseException();
+            }
+            String query = (String) result;
+            query = Pattern.compile("^.*\\[out:json\\]", Pattern.DOTALL).matcher(query).replaceFirst("");
+            query = Pattern.compile("^out.*", Pattern.MULTILINE).matcher(query).replaceAll("out meta;");
+            query = query.replace("({{bbox}})", "");
+            return query;
+        } catch (NoSuchMethodException e) {
+            throw new IllegalStateException();
+        } catch (ScriptException e) {
+            throw new RuntimeException("Failed to execute OverpassTurboQueryWizard", e);
+        }
+    }
+
+}
Index: /trunk/src/org/openstreetmap/josm/gui/MainMenu.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 8683)
+++ /trunk/src/org/openstreetmap/josm/gui/MainMenu.java	(revision 8684)
@@ -82,4 +82,5 @@
 import org.openstreetmap.josm.actions.OrthogonalizeAction;
 import org.openstreetmap.josm.actions.OrthogonalizeAction.Undo;
+import org.openstreetmap.josm.actions.OverpassDownloadAction;
 import org.openstreetmap.josm.actions.PasteAction;
 import org.openstreetmap.josm.actions.PasteTagsAction;
@@ -169,4 +170,6 @@
     /** File / Download from OSM... **/
     public final DownloadAction download = new DownloadAction();
+    /** File / Download from Overpass API... **/
+    public final OverpassDownloadAction overpassDownload = new OverpassDownloadAction();
     /** File / Download object... **/
     public final DownloadPrimitiveAction downloadPrimitive = new DownloadPrimitiveAction();
@@ -642,4 +645,5 @@
         fileMenu.addSeparator();
         add(fileMenu, download);
+        add(fileMenu, overpassDownload);
         add(fileMenu, downloadPrimitive);
         add(fileMenu, searchNotes);
Index: /trunk/test/unit/org/openstreetmap/josm/actions/OverpassTurboQueryWizardTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/actions/OverpassTurboQueryWizardTest.java	(revision 8684)
+++ /trunk/test/unit/org/openstreetmap/josm/actions/OverpassTurboQueryWizardTest.java	(revision 8684)
@@ -0,0 +1,44 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openstreetmap.josm.JOSMFixture;
+
+public class OverpassTurboQueryWizardTest {
+
+    /**
+     * Setup test.
+     */
+    @BeforeClass
+    public static void setUp() {
+        JOSMFixture.createUnitTestFixture().init(true);
+        OverpassTurboQueryWizard.getInstance();
+    }
+
+    @Test
+    public void testKeyValue() throws Exception {
+        final String query = OverpassTurboQueryWizard.getInstance().constructQuery("amenity=drinking_water");
+        assertThat(query, is("" +
+                "[timeout:25];\n" +
+                "// gather results\n" +
+                "(\n" +
+                "  // query part for: “amenity=drinking_water”\n" +
+                "  node[\"amenity\"=\"drinking_water\"];\n" +
+                "  way[\"amenity\"=\"drinking_water\"];\n" +
+                "  relation[\"amenity\"=\"drinking_water\"];\n" +
+                ");\n" +
+                "// print results\n" +
+                "out meta;\n" +
+                ">;\n" +
+                "out meta;"));
+    }
+
+    @Test(expected = OverpassTurboQueryWizard.ParseException.class)
+    public void testErroneous() throws Exception {
+        OverpassTurboQueryWizard.getInstance().constructQuery("foo");
+    }
+}
