[josm] 09/10: Update missing-sources for overpass-wizard.

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Tue Mar 1 21:39:11 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository josm.

commit 75738ba5b4d35e8c45dbfa814d9a78810b36509a
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Tue Mar 1 09:50:09 2016 +0100

    Update missing-sources for overpass-wizard.
---
 debian/changelog                             |    1 +
 debian/copyright                             |    2 +-
 debian/missing-sources/overpass-turbo-ffs.js | 1170 ----------------------
 debian/missing-sources/overpass-wizard.js    | 1358 ++++++++++++++++++++++++++
 4 files changed, 1360 insertions(+), 1171 deletions(-)

diff --git a/debian/changelog b/debian/changelog
index fe20626..3e0ed86 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -8,6 +8,7 @@ josm (0.0.svn9900+dfsg-1) UNRELEASED; urgency=medium
     - Drop © character from copyright statements
   * Refresh patches.
   * Bump minimum required jmapviewer version to 1.15.
+  * Update missing-sources for overpass-wizard.
 
  -- Bas Couwenberg <sebastic at debian.org>  Tue, 01 Mar 2016 06:34:35 +0100
 
diff --git a/debian/copyright b/debian/copyright
index 2715071..b7dda90 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -732,5 +732,5 @@ License: CDDL-1.1
  of California (excluding conflict-of-law provisions). Any litigation relating
  to this License shall be subject to the jurisdiction of the Federal Courts of
  the Northern District of California and the state courts of the State of
- California, with venue lying in Santa Clara County, California. 
+ California, with venue lying in Santa Clara County, California.
 
diff --git a/debian/missing-sources/overpass-turbo-ffs.js b/debian/missing-sources/overpass-turbo-ffs.js
deleted file mode 100644
index 399fa45..0000000
--- a/debian/missing-sources/overpass-turbo-ffs.js
+++ /dev/null
@@ -1,1170 +0,0 @@
-// ffs/wizard module
-if (typeof turbo === "undefined") turbo={};
-turbo.ffs = function() {
-  var ffs = {};
-  var freeFormQuery;
-
-  /* this converts a random boolean expression into a normalized form:
-   * A∧B∧… ∨ C∧D∧… ∨ …
-   * for example: A∧(B∨C) ⇔ (A∧B)∨(A∧C)
-   */
-  function normalize(query) {
-    var normalized_query = {
-      logical:"or",
-      queries:[]
-    };
-    function normalize_recursive(rem_query) {
-      if (!rem_query.logical) {
-        return [{
-          logical: "and",
-          queries: [rem_query]
-        }];
-      } else if (rem_query.logical === "and") {
-        var c1 = normalize_recursive( rem_query.queries[0] );
-        var c2 = normalize_recursive( rem_query.queries[1] );
-        // return cross product of c1 and c2
-        var c = [];
-        for (var i=0; i<c1.length; i++)
-          for (var j=0; j<c2.length; j++) {
-            c.push({
-              logical: "and",
-              queries: c1[i].queries.concat(c2[j].queries)
-            });
-          }
-        return c;
-      } else if (rem_query.logical === "or") {
-        var c1 = normalize_recursive( rem_query.queries[0] );
-        var c2 = normalize_recursive( rem_query.queries[1] );
-        return [].concat(c1,c2);
-
-      } else {
-        alert("unsupported boolean operator: "+rem_query.logical);
-      }
-    }
-    normalized_query.queries = normalize_recursive(query);
-    return normalized_query;
-  }
-
-  function escRegexp(str) {
-    return str.replace(/([()[{*+.$^\\|?])/g, '\\$1');
-  }
-
-  ffs.construct_query = function(search, comment) {
-    function quote_comment_str(s) {
-      // quote strings that are to be used within c-style comments
-      // replace any comment-ending sequences in these strings that would break the resulting query
-      return s.replace(/\*\//g,'[…]').replace(/\n/g,'\\n');
-    }
-
-    try {
-      ffs = turbo.ffs.parser.parse(search);
-    } catch(e) {
-      console.log("ffs parse error");
-      return false;
-    }
-
-    var query_parts = [];
-    var bounds_part;
-
-    query_parts.push('/*');
-    if (comment) {
-      query_parts.push(comment)
-    } else {
-      query_parts.push('This has been generated by the overpass-turbo wizard.');
-      query_parts.push('The original search was:');
-      query_parts.push('“'+quote_comment_str(search)+'”');
-    }
-    query_parts.push('*/');
-    query_parts.push('[out:json][timeout:25];');
-
-    switch(ffs.bounds) {
-      case "area":
-        query_parts.push('// fetch area “'+ffs.area+'” to search in');
-        query_parts.push('{{geocodeArea:'+ffs.area+'}}->.searchArea;');
-        bounds_part = '(area.searchArea)';
-      break;
-      case "around":
-        query_parts.push('// adjust the search radius (in meters) here');
-        query_parts.push('{{radius=1000}}');
-        bounds_part = '(around:{{radius}},{{geocodeCoords:'+ffs.area+'}})';
-      break;
-      case "bbox":
-        bounds_part = '({{bbox}})';
-      break;
-      case "global":
-        bounds_part = undefined;
-      break;
-      default:
-        alert("unknown bounds condition: "+ffs.bounds);
-        return false;
-      break;
-    }
-
-    function get_query_clause(condition) {
-      function esc(str) {
-        if (typeof str !== "string") return;
-        // see http://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#Escaping
-        return str.replace(/\\/g,"\\\\").replace(/"/g,"\\\"") // need to escape those
-                  .replace(/\t/g,"\\t").replace(/\n/g,"\\n"); // also escape newlines an tabs for better readability of the query
-      }
-      var key = esc(condition.key);
-      var val = esc(condition.val);
-      // convert substring searches into matching regexp ones
-      if (condition.query === "substr") {
-        condition.query = "like";
-        condition.val={regex:escRegexp(condition.val)};
-      }
-      // special case for empty values
-      // see https://github.com/drolbr/Overpass-API/issues/53
-      if (val === '') {
-        if (condition.query === "eq") {
-          condition.query = "like";
-          condition.val={regex:'^$'};
-        } else if (condition.query === "neq") {
-          condition.query = "notlike";
-          condition.val={regex:'^$'};
-        }
-      }
-      // special case for empty keys
-      // see https://github.com/drolbr/Overpass-API/issues/53#issuecomment-26325122
-      if (key === '') {
-        if (condition.query === "key") {
-          condition.query = "likelike";
-          key='^$';
-          condition.val={regex: '.*'};
-        } else if (condition.query === "eq") {
-          condition.query = "likelike";
-          key='^$';
-          condition.val={regex: '^'+escRegexp(condition.val)+'$'};
-        } else if (condition.query === "like") {
-          condition.query = "likelike";
-          key='^$';
-        }
-      }
-      // construct the query clause
-      switch(condition.query) {
-        case "key":
-          return '["'+key+'"]';
-        case "nokey":
-          return '["'+key+'"!~".*"]';
-        case "eq":
-          return '["'+key+'"="'+val+'"]';
-        case "neq":
-          return '["'+key+'"!="'+val+'"]';
-        case "like":
-          return '["'+key+'"~"'+esc(condition.val.regex)+'"'
-                 +(condition.val.modifier==="i"?',i':'')
-                 +']';
-        case "likelike":
-          return '[~"'+key+'"~"'+esc(condition.val.regex)+'"'
-                 +(condition.val.modifier==="i"?',i':'')
-                 +']';
-        case "notlike":
-          return '["'+key+'"!~"'+esc(condition.val.regex)+'"'
-                 +(condition.val.modifier==="i"?',i':'')
-                 +']';
-        case "meta":
-          switch(condition.meta) {
-            case "id":
-              return '('+val+')';
-            case "newer":
-              if (condition.val.match(/^-?\d+ ?(seconds?|minutes?|hours?|days?|weeks?|months?|years?)?$/))
-                return '(newer:"{{date:'+val+'}}")';
-              return '(newer:"'+val+'")';
-            case "user":
-              return '(user:"'+val+'")';
-            case "uid":
-              return '(uid:'+val+')';
-            default:
-              console.log("unknown query type: meta/"+condition.meta);
-              return false;
-          }
-        case "free form":
-          // own module, special cased below
-        default:
-          console.log("unknown query type: "+condition.query);
-          return false;
-      }
-    }
-    function get_query_clause_str(condition) {
-      function quotes(s) {
-        if (s.match(/^[a-zA-Z0-9_]+$/) === null)
-          return '"'+s.replace(/"/g,'\\"')+'"';
-        return s;
-      }
-      function quoteRegex(s) {
-        if (s.regex.match(/^[a-zA-Z0-9_]+$/) === null || s.modifier)
-          return '/'+s.regex.replace(/\//g,'\\/')+'/'+(s.modifier||'');
-        return s.regex;
-      }
-      switch(condition.query) {
-        case "key":
-          return quote_comment_str(quotes(condition.key)+'=*');
-        case "nokey":
-          return quote_comment_str(quotes(condition.key)+'!=*');
-        case "eq":
-          return quote_comment_str(quotes(condition.key)+'='+quotes(condition.val));
-        case "neq":
-          return quote_comment_str(quotes(condition.key)+'!='+quotes(condition.val));
-        case "like":
-          return quote_comment_str(quotes(condition.key)+'~'+quoteRegex(condition.val));
-        case "likelike":
-          return quote_comment_str('~'+quotes(condition.key)+'~'+quoteRegex(condition.val));
-        case "notlike":
-          return quote_comment_str(quotes(condition.key)+'!~'+quoteRegex(condition.val));
-        case "substr":
-          return quote_comment_str(quotes(condition.key)+':'+quotes(condition.val));
-        case "meta":
-          switch(condition.meta) {
-            case "id":
-              return quote_comment_str('id:'+quotes(condition.val));
-            case "newer":
-              return quote_comment_str('newer:'+quotes(condition.val));
-            case "user":
-              return quote_comment_str('user:'+quotes(condition.val));
-            case "uid":
-              return quote_comment_str('uid:'+quotes(condition.val));
-            default:
-              return '';
-          }
-        case "free form":
-          return quote_comment_str(quotes(condition.free));
-        default:
-          return '';
-      }
-    }
-
-    ffs.query = normalize(ffs.query);
-
-    query_parts.push('// gather results');
-    query_parts.push('(');
-    for (var i=0; i<ffs.query.queries.length; i++) {
-      var and_query = ffs.query.queries[i];
-
-      var types = ['node','way','relation'];
-      var clauses = [];
-      var clauses_str = [];
-      for (var j=0; j<and_query.queries.length; j++) {
-        var cond_query = and_query.queries[j];
-        // todo: looks like some code duplication here could be reduced by refactoring
-        if (cond_query.query === "free form") {
-          // eventually load free form query module
-          if (!freeFormQuery) freeFormQuery = turbo.ffs.free();
-          var ffs_clause = freeFormQuery.get_query_clause(cond_query);
-          if (ffs_clause === false)
-            return false;
-          // restrict possible data types
-          types = types.filter(function(t) {
-            return ffs_clause.types.indexOf(t) != -1;
-          });
-          // add clauses
-          clauses_str.push(get_query_clause_str(cond_query));
-          clauses = clauses.concat(ffs_clause.conditions.map(function(condition) {
-            return get_query_clause(condition);
-          }));
-        } else if (cond_query.query === "type") {
-          // restrict possible data types
-          types = types.indexOf(cond_query.type) != -1 ? [cond_query.type] : [];
-        } else {
-          // add another query clause
-          clauses_str.push(get_query_clause_str(cond_query));
-          var clause = get_query_clause(cond_query);
-          if (clause === false) return false;
-          clauses.push(clause);
-        }
-      }
-      clauses_str = clauses_str.join(' and ');
-
-      // construct query
-      query_parts.push('  // query part for: “'+clauses_str+'”')
-      for (var t=0; t<types.length; t++) {
-        var buffer = '  '+types[t];
-        for (var c=0; c<clauses.length; c++)
-          buffer += clauses[c];
-        if (bounds_part)
-          buffer += bounds_part;
-        buffer += ';';
-        query_parts.push(buffer);
-      }
-    }
-    query_parts.push(');');
-
-    query_parts.push('// print results');
-    query_parts.push('out body;');
-    query_parts.push('>;');
-    query_parts.push('out skel qt;');
-
-    return query_parts.join('\n');
-  }
-
-  // this is a "did you mean …" mechanism against typos in preset names
-  ffs.repair_search = function(search) {
-    try {
-      ffs = turbo.ffs.parser.parse(search);
-    } catch(e) {
-      return false;
-    }
-
-    function quotes(s) {
-      if (s.match(/^[a-zA-Z0-9_]+$/) === null)
-        return '"'+s.replace(/"/g,'\\"')+'"';
-      return s;
-    }
-
-    var search_parts = [];
-    var repaired = false;
-
-    ffs.query = normalize(ffs.query);
-    ffs.query.queries.forEach(function (q) {
-      q.queries.forEach(validateQuery);
-    });
-    function validateQuery(cond_query) {
-      if (cond_query.query === "free form") {
-        // eventually load free form query module
-        if (!freeFormQuery) freeFormQuery = turbo.ffs.free();
-        var ffs_clause = freeFormQuery.get_query_clause(cond_query);
-        if (ffs_clause === false) {
-          // try to find suggestions for occasional typos
-          var fuzzy = freeFormQuery.fuzzy_search(cond_query);
-          var free_regex = null;
-          try { free_regex = new RegExp("['\"]?"+escRegexp(cond_query.free)+"['\"]?"); } catch(e) {}
-          if (fuzzy && search.match(free_regex)) {
-            search_parts = search_parts.concat(search.split(free_regex));
-            search = search_parts.pop();
-            var replacement = quotes(fuzzy);
-            search_parts.push(replacement);
-            repaired = true;
-          }
-        }
-      }
-    }
-    search_parts.push(search);
-
-    if (!repaired)
-      return false;
-    return search_parts;
-  }
-
-  ffs.invalidateCache = function() {
-    freeFormQuery = undefined;
-  }
-
-  return ffs;
-};
-// ffs/wizard module
-if (typeof turbo === "undefined") turbo={};
-
-turbo.ffs.free = function() {
-
-  var freeFormQuery = {};
-  var presets = {};
-
-  // load presets
-  (function loadPresets() {
-    if (typeof $ === "undefined") return;
-    var presets_file = "data/iD_presets.json";
-    try {
-      $.ajax(presets_file,{async:false,dataType:"json"}).success(function(data){
-        presets = data;
-        Object.keys(presets).map(function(key) {
-          var preset = presets[key];
-          preset.nameCased = preset.name;
-          preset.name = preset.name.toLowerCase();
-          preset.terms = !preset.terms ? [] : preset.terms.map(function(term) {return term.toLowerCase();});
-        });
-      }).error(function(){
-        throw new Error();
-      });
-    } catch(e) {
-      console.log("failed to load presets file", presets_file, e);
-    }
-  })();
-  // load preset translations
-  (function loadPresetTranslations() {
-    if (typeof $ === "undefined" || typeof i18n === "undefined") return;
-    var language = i18n.getLanguage();
-    if (language == "en") return;
-    var translation_file = "data/iD_presets_"+language+".json";
-    try {
-      $.ajax(translation_file,{async:false,dataType:"json"}).success(function(data){
-        // load translated names and terms into presets object
-        Object.keys(data).map(function(preset) {
-          var translation = data[preset];
-          preset = presets[preset];
-          preset.translated = true;
-          // save original preset name under alternative terms
-          var oriPresetName = preset.name;
-          // save translated preset name
-          preset.nameCased = translation.name;
-          preset.name = translation.name.toLowerCase();
-          // add new terms
-          if (translation.terms)
-            preset.terms = translation.terms.split(",")
-              .map(function(term) { return term.trim().toLowerCase(); })
-              .concat(preset.terms);
-          // add this to the front to allow exact (english) preset names to match before terms
-          preset.terms.unshift(oriPresetName);
-        });
-      }).error(function(){
-        throw new Error();
-      });
-    } catch(e) {
-      console.log("failed to load preset translations file: "+translation_file);
-    }
-  })();
-
-  freeFormQuery.get_query_clause = function(condition) {
-    // search presets for ffs term
-    var search = condition.free.toLowerCase();
-    var candidates = Object.keys(presets).map(function(key) {
-      return presets[key];
-    }).filter(function(preset) {
-      if (preset.searchable===false) return false;
-      if (preset.name === search) return true;
-      preset._termsIndex = preset.terms.indexOf(search);
-      return preset._termsIndex != -1;
-    });
-    if (candidates.length === 0)
-      return false;
-    // sort candidates
-    candidates.sort(function(a,b) {
-      // prefer exact name matches
-      if (a.name === search) return -1;
-      if (b.name === search) return  1;
-      return a._termsIndex - b._termsIndex;
-    });
-    var preset = candidates[0];
-    var types = [];
-    preset.geometry.forEach(function(g) {
-      switch (g) {
-        case "point":
-        case "vertex":
-          types.push("node");
-          break;
-        case "line":
-          types.push("way");
-          break;
-        case "area":
-          types.push("way");
-          types.push("relation"); // todo: additionally add type=multipolygon?
-          break;
-        case "relation":
-          types.push("relation");
-          break;
-        default:
-          console.log("unknown geometry type "+g+" of preset "+preset.name);
-      }
-    });
-    function onlyUnique(value, index, self) {
-      return self.indexOf(value) === index;
-    }
-    return {
-      types: types.filter(onlyUnique),
-      conditions: Object.keys(preset.tags).map(function(k) {
-        var v = preset.tags[k];
-        return {
-          query: v==="*" ? "key" : "eq",
-          key: k,
-          val: v
-        };
-      })
-    };
-  }
-
-  freeFormQuery.fuzzy_search = function(condition) {
-    // search presets for ffs term
-    var search = condition.free.toLowerCase();
-    // fuzzyness: max lev.dist allowed to still match
-    var fuzzyness = 2+Math.floor(search.length/7);
-    function fuzzyMatch(term) {
-      return levenshteinDistance(term, search) <= fuzzyness;
-    }
-    var candidates = Object.keys(presets).map(function(key) {
-      return presets[key];
-    }).filter(function(preset) {
-      if (preset.searchable===false) return false;
-      if (fuzzyMatch(preset.name)) return true;
-      return preset.terms.some(fuzzyMatch);
-    });
-    if (candidates.length === 0)
-      return false;
-    // sort candidates
-    function preset_weight(preset) {
-      return [preset.name].concat(preset.terms).map(function(term, index) {
-        return levenshteinDistance(term,search);
-      }).reduce(function min(a, b) {
-        return a <= b ? a : b;
-      });
-    };
-    candidates.sort(function(a,b) {
-      return preset_weight(a) - preset_weight(b);
-    });
-    var preset = candidates[0];
-    return preset.nameCased;
-  }
-
-
-  return freeFormQuery;
-};
-turbo.ffs.parser = (function() {
-  /*
-   * Generated by PEG.js 0.8.0.
-   *
-   * http://pegjs.majda.cz/
-   */
-
-  function peg$subclass(child, parent) {
-    function ctor() { this.constructor = child; }
-    ctor.prototype = parent.prototype;
-    child.prototype = new ctor();
-  }
-
-  function SyntaxError(message, expected, found, offset, line, column) {
-    this.message  = message;
-    this.expected = expected;
-    this.found    = found;
-    this.offset   = offset;
-    this.line     = line;
-    this.column   = column;
-
-    this.name     = "SyntaxError";
-  }
-
-  peg$subclass(SyntaxError, Error);
-
-  function parse(input) {
-    var options = arguments.length > 1 ? arguments[1] : {},
-
-        peg$FAILED = {},
-
-        peg$startRuleIndices = { start: 0 },
-        peg$startRuleIndex   = 0,
-
-        peg$consts = [
-          peg$FAILED,
-          function(x) { return x },
-          [],
-          "in bbox",
-          { type: "literal", value: "in bbox", description: "\"in bbox\"" },
-          "IN BBOX",
-          { type: "literal", value: "IN BBOX", description: "\"IN BBOX\"" },
-          function(x) { return { bounds:"bbox", query:x } },
-          "in",
-          { type: "literal", value: "in", description: "\"in\"" },
-          "IN",
-          { type: "literal", value: "IN", description: "\"IN\"" },
-          function(x, y) { return { bounds:"area", query:x, area:y } },
-          "around",
-          { type: "literal", value: "around", description: "\"around\"" },
-          "AROUND",
-          { type: "literal", value: "AROUND", description: "\"AROUND\"" },
-          function(x, y) { return { bounds:"around", query:x, area:y } },
-          "global",
-          { type: "literal", value: "global", description: "\"global\"" },
-          "GLOBAL",
-          { type: "literal", value: "GLOBAL", description: "\"GLOBAL\"" },
-          function(x) { return { bounds:"global", query:x } },
-          "or",
-          { type: "literal", value: "or", description: "\"or\"" },
-          "OR",
-          { type: "literal", value: "OR", description: "\"OR\"" },
-          "||",
-          { type: "literal", value: "||", description: "\"||\"" },
-          "|",
-          { type: "literal", value: "|", description: "\"|\"" },
-          function(x, y) { return { logical:"or", queries:[x,y] } },
-          "and",
-          { type: "literal", value: "and", description: "\"and\"" },
-          "AND",
-          { type: "literal", value: "AND", description: "\"AND\"" },
-          "&&",
-          { type: "literal", value: "&&", description: "\"&&\"" },
-          "&",
-          { type: "literal", value: "&", description: "\"&\"" },
-          function(x, y) { return { logical:"and", queries:[x,y] } },
-          "(",
-          { type: "literal", value: "(", description: "\"(\"" },
-          ")",
-          { type: "literal", value: ")", description: "\")\"" },
-          function(x) { return x; },
-          "=",
-          { type: "literal", value: "=", description: "\"=\"" },
-          "==",
-          { type: "literal", value: "==", description: "\"==\"" },
-          function(x, y) { return { query:"eq", key:x, val:y } },
-          "!=",
-          { type: "literal", value: "!=", description: "\"!=\"" },
-          "<>",
-          { type: "literal", value: "<>", description: "\"<>\"" },
-          function(x, y) { return { query:"neq", key:x, val:y } },
-          "*",
-          { type: "literal", value: "*", description: "\"*\"" },
-          function(x) { return { query:"key", key:x } },
-          "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(x) { return { query:"nokey", key:x } },
-          "~=",
-          { type: "literal", value: "~=", description: "\"~=\"" },
-          "~",
-          { type: "literal", value: "~", description: "\"~\"" },
-          "=~",
-          { type: "literal", value: "=~", description: "\"=~\"" },
-          function(x, y) { return { query:"like", key:x, val:y.regex?y:{regex:y} } },
-          "like",
-          { type: "literal", value: "like", description: "\"like\"" },
-          "LIKE",
-          { type: "literal", value: "LIKE", description: "\"LIKE\"" },
-          function(x, y) { return { query:"likelike", key:x, val:y.regex?y:{regex:y} } },
-          "!~",
-          { type: "literal", value: "!~", description: "\"!~\"" },
-          function(x, y) { return { query:"notlike", key:x, val:y.regex?y:{regex:y} } },
-          ":",
-          { type: "literal", value: ":", description: "\":\"" },
-          function(x, y) { return { query:"substr", key:x, val:y } },
-          "type",
-          { type: "literal", value: "type", description: "\"type\"" },
-          function(x) { return { query:"type", type:x } },
-          "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(x, y) { return { query:"meta", meta:x, val:y } },
-          function(x) { return { query:"free form", free:x } },
-          { type: "other", description: "Key" },
-          /^[a-zA-Z0-9_:\-]/,
-          { type: "class", value: "[a-zA-Z0-9_:\\-]", description: "[a-zA-Z0-9_:\\-]" },
-          function(s) { return s.join(''); },
-          "\"",
-          { type: "literal", value: "\"", description: "\"\\\"\"" },
-          "'",
-          { type: "literal", value: "'", description: "\"'\"" },
-          function(parts) {
-                return parts[1];
-              },
-          { type: "other", description: "string" },
-          /^[^'" ()~=!*\/:<>&|[\]{}#+@$%?\^.,]/,
-          { type: "class", value: "[^'\" ()~=!*\\/:<>&|[\\]{}#+@$%?\\^.,]", description: "[^'\" ()~=!*\\/:<>&|[\\]{}#+@$%?\\^.,]" },
-          function(chars) { return chars.join(""); },
-          void 0,
-          "\\",
-          { type: "literal", value: "\\", description: "\"\\\\\"" },
-          { type: "any", description: "any character" },
-          function(char_) { return char_;     },
-          function(sequence) { return sequence;  },
-          /^['"\\bfnrtv]/,
-          { type: "class", value: "['\"\\\\bfnrtv]", description: "['\"\\\\bfnrtv]" },
-          function(char_) {
-                return char_
-                  .replace("b", "\b")
-                  .replace("f", "\f")
-                  .replace("n", "\n")
-                  .replace("r", "\r")
-                  .replace("t", "\t")
-                  .replace("v", "\x0B") // IE does not recognize "\v".
-              },
-          "/",
-          { type: "literal", value: "/", description: "\"/\"" },
-          null,
-          "i",
-          { type: "literal", value: "i", description: "\"i\"" },
-          "",
-          function(parts) {
-                return { regex: parts[1], modifier: parts[3] };
-              },
-          "\\/",
-          { type: "literal", value: "\\/", description: "\"\\\\/\"" },
-          function() { return "/";  },
-          { type: "other", description: "whitespace" },
-          /^[ \t\n\r]/,
-          { type: "class", value: "[ \\t\\n\\r]", description: "[ \\t\\n\\r]" }
-        ],
-
-        peg$bytecode = [
-          peg$decode("!7;+<$7!+2%7;+(%4#6!#!!%$##  $\"#  \"#  "),
-          peg$decode("!7\"+]$ \"7<+&$,#&7<\"\"\"  +D%.#\"\"2#3$*) \".%\"\"2%3&+(%4#6'#!\"%$##  $\"#  \"#  *\u0158 \"!7\"+\x81$ \"7<+&$,#&7<\"\"\"  +h%.(\"\"2(3)*) \".*\"\"2*3++L% \"7<+&$,#&7<\"\"\"  +3%72+)%4%6,%\"$ %$%#  $$#  $##  $\"#  \"#  *\xE9 \"!7\"+\x81$ \"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'!! %"),
-          peg$decode("!7#+\x99$ \"7<+&$,#&7<\"\"\"  +\x80%.7\"\"2738*A \".9\"\"293:*5 \".;\"\"2;3<*) \".=\"\"2=3>+L% \"7<+&$,#&7<\"\"\"  +3%7\"+)%4%6?%\"$ %$%#  $$#  $##  $\"#  \"#  *# \"7#"),
-          peg$decode("!7$+\x99$ \"7<+&$,#&7<\"\"\"  +\x80%.@\"\"2 at 3A*A \".B\"\"2B3C*5 \".D\"\"2D3E*) \".F\"\"2F3G+L% \"7<+&$,#&7<\"\"\"  +3%7#+)%4%6H%\"$ %$%#  $$#  $##  $\"#  \"#  *# \"7$"),
-          peg$decode("7%*g \"!.I\"\"2I3J+V$7;+L%7\"+B%7;+8%.K\"\"2K3L+(%4%6M%!\"%$%#  $$#  $##  $\"#  \"#  "),
-          peg$decode("7.*Y \"7/*S \"7&*M \"7'*G \"7(*A \"7)*; \"7**5 \"7+*/ \"7,*) \"7-*# \"70"),
-          peg$decode("!71+c$7;+Y%.N\"\"2N3O*) \".P\"\"2P3Q+=%7;+3%72+)%4%6R%\"$ %$%#  $$#  $##  $\"#  \"#  "),
-          peg$decode("!71+c$7;+Y%.S\"\"2S3T*) \".U\"\"2U3V+=%7;+3%72+)%4%6W%\"$ %$%#  $$#  $##  $\"#  \"#  "),
-          peg$decode("!71+h$7;+^%.N\"\"2N3O*) \".P\"\"2P3Q+B%7;+8%.X\"\"2X3Y+(%4%6Z%!$%$%#  $$#  $##  $\"#  \"#  *\u0122 \"!72+\u0117$ \"7<+&$,#&7<\"\"\"  +\xFE%!.[\"\"2[3\\+u$ \"7<+&$,#&7<\"\"\"  +\\%.]\"\"2]3^+L% \"7<+&$,#&7<\"\"\"  +3%._\"\"2_3`+#%'%%$%#  $$#  $##  $\"#  \"#  *\x86 \"!.a\"\"2a3b+u$ \"7<+&$,#&7<\"\"\"  +\\%.c\"\"2c3d+L% \"7<+&$,#&7<\"\"\"  +3%.e\"\"2e3f+#%'%%$%#  $$#  $##  $\"#  \"#  +(%4#6Z#!\"%$##  $\"#  \"#  "),
-          peg$decode("!71+h$7;+^%.S\"\"2S3T*) \".U\"\"2U3V+B%7;+8%.X\"\"2X3Y+(%4%6g%!$%$%#  $$#  $##  $\"#  \"#  *\xD0 \"!72+\xC5$ \"7<+&$,#&7<\"\"\"  +\xAC%!.[\"\"2[3\\+L$ \"7<+&$,#&7<\"\"\"  +3%._\"\"2_3`+#%'#%$##  $\"#  \"#  *] \"!.a\"\"2a3b+L$ \"7<+&$,#&7<\"\"\"  +3%.e\"\"2e3f+#%'#%$##  $\"#  \"#  +(%4#6g#!\"%$##  $\"#  \"#  "),
-          peg$decode("!71+u$7;+k%.h\"\"2h3i*5 \".j\"\"2j3k*) \".l\"\"2l3m+C%7;+9%72*# \"78+)%4%6n%\"$ %$%#  $$#  $##  $\"#  \"#  *\x92 \"!72+\x87$ \"7<+&$,#&7<\"\"\"  +n%.o\"\"2o3p*) \".q\"\"2q3r+R% \"7<+&$,#&7<\"\"\"  +9%72*# \"78+)%4%6n%\"$ %$%#  $$#  $##  $\"#  \"#  "),
-          peg$decode("!.j\"\"2j3k+\x89$7;+%72+u%7;+k%.h\"\"2h3i*5 \".j\"\"2j3k*) \".l\"\"2l3m+C%7;+9%72*# \"78+)%4'6s'\"$ %$'#  $&#  $%#  $$#  $##  $\"#  \"#  "),
-          peg$decode("!71+]$7;+S%.t\"\"2t3u+C%7;+9%72*# \"78+)%4%6v%\"$ %$%#  $$#  $##  $\"#  \"#  *\xFA \"!72+\xEF$ \"7<+&$,#&7<\"\"\"  +\xD6%!.]\"\"2]3^+L$ \"7<+&$,#&7<\"\"\"  +3%.o\"\"2o3p+#%'#%$##  $\"#  \"#  *] \"!.c\"\"2c3d+L$ \"7<+&$,#&7<\"\"\"  +3%.q\"\"2q3r+#%'#%$##  $\"#  \"#  +R% \"7<+&$,#&7<\"\"\"  +9%72*# \"78+)%4%6v%\"$ %$%#  $$#  $##  $\"#  \"#  "),
-          peg$decode("!72+W$7;+M%.w\"\"2w3x+=%7;+3%72+)%4%6y%\"$ %$%#  $$#  $##  $\"#  \"#  "),
-          peg$decode("!.z\"\"2z3{+V$7;+L%.w\"\"2w3x+<%7;+2%72+(%4%6|%! %$%#  $$#  $##  $\"#  \"#  "),
-          peg$decode("!.}\"\"2}3~*A \".\"\"23\x80*5 \".\x81\"\"2\x813\x82*) \".\x83\"\"2\x833\x84+W$7;+M%.w\"\"2w3x+=%7;+3%72+)%4%6\x85%\"$ %$%#  $$#  $##  $\"#  \"#  "),
-          peg$decode("!72+' 4!6\x86!! %"),
-          peg$decode("8! \"0\x88\"\"1!3\x89+,$,)&0\x88\"\"1!3\x89\"\"\"  +' 4!6\x8A!! %*\x8B \"!!.\x8B\"\"2\x8B3\x8C+=$73+3%.\x8B\"\"2\x8B3\x8C+#%'#%$##  $\"#  \"#  *N \"!.\x8D\"\"2\x8D3\x8E+=$74+3%.\x8D\"\"2\x8D3\x8E+#%'#%$##  $\"#  \"#  +' 4!6\x8F!! %9*\" 3\x87"),
-          peg$decode("8! \"0\x91\"\"1!3\x92+,$,)&0\x91\"\"1!3\x92\"\"\"  +' 4!6\x8A!! %*\x8B \"!!.\x8B\"\"2\x8B3\x8C+=$73+3%.\x8B\"\"2\x8B3\x8C+#%'#%$##  $\"#  \"#  *N \"!.\x8D\"\"2\x8D3\x8E+=$74+3%.\x8D\"\"2\x8D3\x8E+#%'#%$##  $\"#  \"#  +' 4!6\x8F!! %9*\" 3\x90"),
-          peg$decode("! \"75,#&75\"+' 4!6\x93!! %"),
-          peg$decode("! \"76,#&76\"+' 4!6\x93!! %"),
-          peg$decode("!!8.\x8B\"\"2\x8B3\x8C*) \".\x95\"\"2\x953\x969*$$\"\" \x94\"#  +7$-\"\"1!3\x97+(%4\"6\x98\"! %$\"#  \"#  *C \"!.\x95\"\"2\x953\x96+2$77+(%4\"6\x99\"! %$\"#  \"#  "),
-          peg$decode("!!8.\x8D\"\"2\x8D3\x8E*) \".\x95\"\"2\x953\x969*$$\"\" \x94\"#  +7$-\"\"1!3\x97+(%4\"6\x98\"! %$\"#  \"#  *C \"!.\x95\"\"2\x953\x96+2$77+(%4\"6\x99\"! %$\"#  \"#  "),
-          peg$decode("!0\x9A\"\"1!3\x9B+' 4!6\x9C!! %"),
-          peg$decode("8!!.\x9D\"\"2\x9D3\x9E+Y$79+O%.\x9D\"\"2\x9D3\x9E+?%.\xA0\"\"2\xA03\xA1*# \" \xA2*# \" \x9F+#%'$%$$#  $##  $\"#  \"#  +' 4!6\xA3!! %9*\" 3\x90"),
-          peg$decode("! \"7:+&$,#&7:\"\"\"  +' 4!6\x93!! %"),
-          peg$decode("!!8.\x9D\"\"2\x9D3\x9E*) \".\xA4\"\"2\xA43\xA59*$$\"\" \x94\"#  +7$-\"\"1!3\x97+(%4\"6\x98\"! %$\"#  \"#  *4 \"!.\xA4\"\"2\xA43\xA5+& 4!6\xA6! %"),
-          peg$decode("8 \"7<,#&7<\"9*\" 3\xA7"),
-          peg$decode("80\xA8\"\"1!3\xA99*\" 3\xA7")
-        ],
-
-        peg$currPos          = 0,
-        peg$reportedPos      = 0,
-        peg$cachedPos        = 0,
-        peg$cachedPosDetails = { line: 1, column: 1, seenCR: false },
-        peg$maxFailPos       = 0,
-        peg$maxFailExpected  = [],
-        peg$silentFails      = 0,
-
-        peg$result;
-
-    if ("startRule" in options) {
-      if (!(options.startRule in peg$startRuleIndices)) {
-        throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
-      }
-
-      peg$startRuleIndex = peg$startRuleIndices[options.startRule];
-    }
-
-    function text() {
-      return input.substring(peg$reportedPos, peg$currPos);
-    }
-
-    function offset() {
-      return peg$reportedPos;
-    }
-
-    function line() {
-      return peg$computePosDetails(peg$reportedPos).line;
-    }
-
-    function column() {
-      return peg$computePosDetails(peg$reportedPos).column;
-    }
-
-    function expected(description) {
-      throw peg$buildException(
-        null,
-        [{ type: "other", description: description }],
-        peg$reportedPos
-      );
-    }
-
-    function error(message) {
-      throw peg$buildException(message, null, peg$reportedPos);
-    }
-
-    function peg$computePosDetails(pos) {
-      function advance(details, startPos, endPos) {
-        var p, ch;
-
-        for (p = startPos; p < endPos; p++) {
-          ch = input.charAt(p);
-          if (ch === "\n") {
-            if (!details.seenCR) { details.line++; }
-            details.column = 1;
-            details.seenCR = false;
-          } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") {
-            details.line++;
-            details.column = 1;
-            details.seenCR = true;
-          } else {
-            details.column++;
-            details.seenCR = false;
-          }
-        }
-      }
-
-      if (peg$cachedPos !== pos) {
-        if (peg$cachedPos > pos) {
-          peg$cachedPos = 0;
-          peg$cachedPosDetails = { line: 1, column: 1, seenCR: false };
-        }
-        advance(peg$cachedPosDetails, peg$cachedPos, pos);
-        peg$cachedPos = pos;
-      }
-
-      return peg$cachedPosDetails;
-    }
-
-    function peg$fail(expected) {
-      if (peg$currPos < peg$maxFailPos) { return; }
-
-      if (peg$currPos > peg$maxFailPos) {
-        peg$maxFailPos = peg$currPos;
-        peg$maxFailExpected = [];
-      }
-
-      peg$maxFailExpected.push(expected);
-    }
-
-    function peg$buildException(message, expected, pos) {
-      function cleanupExpected(expected) {
-        var i = 1;
-
-        expected.sort(function(a, b) {
-          if (a.description < b.description) {
-            return -1;
-          } else if (a.description > b.description) {
-            return 1;
-          } else {
-            return 0;
-          }
-        });
-
-        while (i < expected.length) {
-          if (expected[i - 1] === expected[i]) {
-            expected.splice(i, 1);
-          } else {
-            i++;
-          }
-        }
-      }
-
-      function buildMessage(expected, found) {
-        function stringEscape(s) {
-          function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
-
-          return s
-            .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(ch) { return '\\x0' + hex(ch); })
-            .replace(/[\x10-\x1F\x80-\xFF]/g,    function(ch) { return '\\x'  + hex(ch); })
-            .replace(/[\u0180-\u0FFF]/g,         function(ch) { return '\\u0' + hex(ch); })
-            .replace(/[\u1080-\uFFFF]/g,         function(ch) { return '\\u'  + hex(ch); });
-        }
-
-        var expectedDescs = new Array(expected.length),
-            expectedDesc, foundDesc, i;
-
-        for (i = 0; i < expected.length; i++) {
-          expectedDescs[i] = expected[i].description;
-        }
-
-        expectedDesc = expected.length > 1
-          ? expectedDescs.slice(0, -1).join(", ")
-              + " or "
-              + expectedDescs[expected.length - 1]
-          : expectedDescs[0];
-
-        foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input";
-
-        return "Expected " + expectedDesc + " but " + foundDesc + " found.";
-      }
-
-      var posDetails = peg$computePosDetails(pos),
-          found      = pos < input.length ? input.charAt(pos) : null;
-
-      if (expected !== null) {
-        cleanupExpected(expected);
-      }
-
-      return new SyntaxError(
-        message !== null ? message : buildMessage(expected, found),
-        expected,
-        found,
-        pos,
-        posDetails.line,
-        posDetails.column
-      );
-    }
-
-    function peg$decode(s) {
-      var bc = new Array(s.length), i;
-
-      for (i = 0; i < s.length; i++) {
-        bc[i] = s.charCodeAt(i) - 32;
-      }
-
-      return bc;
-    }
-
-    function peg$parseRule(index) {
-      var bc    = peg$bytecode[index],
-          ip    = 0,
-          ips   = [],
-          end   = bc.length,
-          ends  = [],
-          stack = [],
-          params, i;
-
-      function protect(object) {
-        return Object.prototype.toString.apply(object) === "[object Array]" ? [] : object;
-      }
-
-      while (true) {
-        while (ip < end) {
-          switch (bc[ip]) {
-            case 0:
-              stack.push(protect(peg$consts[bc[ip + 1]]));
-              ip += 2;
-              break;
-
-            case 1:
-              stack.push(peg$currPos);
-              ip++;
-              break;
-
-            case 2:
-              stack.pop();
-              ip++;
-              break;
-
-            case 3:
-              peg$currPos = stack.pop();
-              ip++;
-              break;
-
-            case 4:
-              stack.length -= bc[ip + 1];
-              ip += 2;
-              break;
-
-            case 5:
-              stack.splice(-2, 1);
-              ip++;
-              break;
-
-            case 6:
-              stack[stack.length - 2].push(stack.pop());
-              ip++;
-              break;
-
-            case 7:
-              stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1]));
-              ip += 2;
-              break;
-
-            case 8:
-              stack.pop();
-              stack.push(input.substring(stack[stack.length - 1], peg$currPos));
-              ip++;
-              break;
-
-            case 9:
-              ends.push(end);
-              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
-
-              if (stack[stack.length - 1]) {
-                end = ip + 3 + bc[ip + 1];
-                ip += 3;
-              } else {
-                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
-                ip += 3 + bc[ip + 1];
-              }
-
-              break;
-
-            case 10:
-              ends.push(end);
-              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
-
-              if (stack[stack.length - 1] === peg$FAILED) {
-                end = ip + 3 + bc[ip + 1];
-                ip += 3;
-              } else {
-                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
-                ip += 3 + bc[ip + 1];
-              }
-
-              break;
-
-            case 11:
-              ends.push(end);
-              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
-
-              if (stack[stack.length - 1] !== peg$FAILED) {
-                end = ip + 3 + bc[ip + 1];
-                ip += 3;
-              } else {
-                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
-                ip += 3 + bc[ip + 1];
-              }
-
-              break;
-
-            case 12:
-              if (stack[stack.length - 1] !== peg$FAILED) {
-                ends.push(end);
-                ips.push(ip);
-
-                end = ip + 2 + bc[ip + 1];
-                ip += 2;
-              } else {
-                ip += 2 + bc[ip + 1];
-              }
-
-              break;
-
-            case 13:
-              ends.push(end);
-              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
-
-              if (input.length > peg$currPos) {
-                end = ip + 3 + bc[ip + 1];
-                ip += 3;
-              } else {
-                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
-                ip += 3 + bc[ip + 1];
-              }
-
-              break;
-
-            case 14:
-              ends.push(end);
-              ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
-
-              if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]) {
-                end = ip + 4 + bc[ip + 2];
-                ip += 4;
-              } else {
-                end = ip + 4 + bc[ip + 2] + bc[ip + 3];
-                ip += 4 + bc[ip + 2];
-              }
-
-              break;
-
-            case 15:
-              ends.push(end);
-              ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
-
-              if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]) {
-                end = ip + 4 + bc[ip + 2];
-                ip += 4;
-              } else {
-                end = ip + 4 + bc[ip + 2] + bc[ip + 3];
-                ip += 4 + bc[ip + 2];
-              }
-
-              break;
-
-            case 16:
-              ends.push(end);
-              ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
-
-              if (peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))) {
-                end = ip + 4 + bc[ip + 2];
-                ip += 4;
-              } else {
-                end = ip + 4 + bc[ip + 2] + bc[ip + 3];
-                ip += 4 + bc[ip + 2];
-              }
-
-              break;
-
-            case 17:
-              stack.push(input.substr(peg$currPos, bc[ip + 1]));
-              peg$currPos += bc[ip + 1];
-              ip += 2;
-              break;
-
-            case 18:
-              stack.push(peg$consts[bc[ip + 1]]);
-              peg$currPos += peg$consts[bc[ip + 1]].length;
-              ip += 2;
-              break;
-
-            case 19:
-              stack.push(peg$FAILED);
-              if (peg$silentFails === 0) {
-                peg$fail(peg$consts[bc[ip + 1]]);
-              }
-              ip += 2;
-              break;
-
-            case 20:
-              peg$reportedPos = stack[stack.length - 1 - bc[ip + 1]];
-              ip += 2;
-              break;
-
-            case 21:
-              peg$reportedPos = peg$currPos;
-              ip++;
-              break;
-
-            case 22:
-              params = bc.slice(ip + 4, ip + 4 + bc[ip + 3]);
-              for (i = 0; i < bc[ip + 3]; i++) {
-                params[i] = stack[stack.length - 1 - params[i]];
-              }
-
-              stack.splice(
-                stack.length - bc[ip + 2],
-                bc[ip + 2],
-                peg$consts[bc[ip + 1]].apply(null, params)
-              );
-
-              ip += 4 + bc[ip + 3];
-              break;
-
-            case 23:
-              stack.push(peg$parseRule(bc[ip + 1]));
-              ip += 2;
-              break;
-
-            case 24:
-              peg$silentFails++;
-              ip++;
-              break;
-
-            case 25:
-              peg$silentFails--;
-              ip++;
-              break;
-
-            default:
-              throw new Error("Invalid opcode: " + bc[ip] + ".");
-          }
-        }
-
-        if (ends.length > 0) {
-          end = ends.pop();
-          ip = ips.pop();
-        } else {
-          break;
-        }
-      }
-
-      return stack[0];
-    }
-
-    peg$result = peg$parseRule(peg$startRuleIndex);
-
-    if (peg$result !== peg$FAILED && peg$currPos === input.length) {
-      return peg$result;
-    } else {
-      if (peg$result !== peg$FAILED && peg$currPos < input.length) {
-        peg$fail({ type: "end", description: "end of input" });
-      }
-
-      throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos);
-    }
-  }
-
-  return {
-    SyntaxError: SyntaxError,
-    parse:       parse
-  };
-})();
diff --git a/debian/missing-sources/overpass-wizard.js b/debian/missing-sources/overpass-wizard.js
new file mode 100644
index 0000000..5ef1717
--- /dev/null
+++ b/debian/missing-sources/overpass-wizard.js
@@ -0,0 +1,1358 @@
+var parser = require("./parser.js");
+var freeFormQuery; // todo: refactor this!
+
+// todo: normalization -> node module?
+
+/* this converts a random boolean expression into a normalized form:
+ * A∧B∧… ∨ C∧D∧… ∨ …
+ * for example: A∧(B∨C) ⇔ (A∧B)∨(A∧C)
+ */
+function normalize(query) {
+  var normalized_query = {
+    logical:"or",
+    queries:[]
+  };
+  function normalize_recursive(rem_query) {
+    if (!rem_query.logical) {
+      return [{
+        logical: "and",
+        queries: [rem_query]
+      }];
+    } else if (rem_query.logical === "and") {
+      var c1 = normalize_recursive( rem_query.queries[0] );
+      var c2 = normalize_recursive( rem_query.queries[1] );
+      // return cross product of c1 and c2
+      var c = [];
+      for (var i=0; i<c1.length; i++)
+        for (var j=0; j<c2.length; j++) {
+          c.push({
+            logical: "and",
+            queries: c1[i].queries.concat(c2[j].queries)
+          });
+        }
+      return c;
+    } else if (rem_query.logical === "or") {
+      var c1 = normalize_recursive( rem_query.queries[0] );
+      var c2 = normalize_recursive( rem_query.queries[1] );
+      return [].concat(c1,c2);
+
+    } else {
+      console.error("unsupported boolean operator: "+rem_query.logical);
+    }
+  }
+  normalized_query.queries = normalize_recursive(query);
+  return normalized_query;
+}
+
+module.exports = function wizard(search, options) {
+  var defaults = {
+    comment: true,
+    outputMode: "geom", // "recursive", "recursive_meta", out <*> ("geom", "ids", …)
+    globalBbox: true,
+    //todo: more fine grained controll, e.g. to deactivate "in X"
+    timeout: 25,
+    maxsize: undefined,
+    outputFormat: "json", // "json", "xml"
+    aroundRadius: 1000,
+    freeFormPresets: undefined
+  };
+
+  for (var k in options) {
+    defaults[k] = options[k];
+  }
+  options = defaults;
+
+  // quote strings that are safe to be used within c-style comments
+  // replace any comment-ending sequences in these strings that would break the resulting query
+  function quote_comment_str(s) {
+    return s.replace(/\*\//g,'[…]').replace(/\n/g,'\\n');
+  }
+
+  var parsedQuery;
+
+  try {
+    parsedQuery = parser.parse(search);
+  } catch(e) {
+    console.error("couldn't parse wizard input");
+    return false;
+  }
+
+  var query_parts = [];
+  var bounds_part;
+
+  if (options.comment === true) {
+    query_parts.push('/*');
+    query_parts.push('This has been generated by the overpass-turbo wizard.');
+    query_parts.push('The original search was:');
+    query_parts.push('“'+quote_comment_str(search)+'”');
+    query_parts.push('*/');
+  } else if (typeof options.comment === "string") {
+    query_parts.push('/*');
+    query_parts.push(options.comment);
+    query_parts.push('*/');
+    comment = true;
+  }
+  query_parts.push(
+    '[out:'+options.outputFormat+']'+
+    '[timeout:'+options.timeout+']'+
+    (options.maxsize !== undefined ? '[maxsize:'+options.maxsize+']' : '')+
+    (options.globalBbox && parsedQuery.bounds === "bbox" ? '[bbox:{{bbox}}]' : '')+
+  ';');
+
+  switch(parsedQuery.bounds) {
+    case "area":
+      if (options.comment)
+        query_parts.push('// fetch area “'+parsedQuery.area+'” to search in');
+      query_parts.push('{{geocodeArea:'+parsedQuery.area+'}}->.searchArea;');
+      bounds_part = '(area.searchArea)';
+    break;
+    case "around":
+      if (options.comment)
+        query_parts.push('// adjust the search radius (in meters) here');
+      query_parts.push('{{radius='+options.aroundRadius+'}}');
+      bounds_part = '(around:{{radius}},{{geocodeCoords:'+parsedQuery.area+'}})';
+    break;
+    case "bbox":
+      bounds_part = options.globalBbox ? '' : '({{bbox}})';
+    break;
+    case "global":
+      bounds_part = undefined;
+    break;
+    default:
+      console.error("unknown bounds condition: "+parsedQuery.bounds);
+      return false;
+    break;
+  }
+
+  function get_query_clause(condition) {
+    function escRegexp(str) {
+      return str.replace(/([()[{*+.$^\\|?])/g, '\\$1');
+    }
+    function esc(str) {
+      if (typeof str !== "string") return;
+      // see http://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#Escaping
+      return str.replace(/\\/g,"\\\\").replace(/"/g,"\\\"") // need to escape those
+                .replace(/\t/g,"\\t").replace(/\n/g,"\\n"); // also escape newlines an tabs for better readability of the query
+    }
+    var key = esc(condition.key);
+    var val = esc(condition.val);
+    // convert substring searches into matching regexp ones
+    if (condition.query === "substr") {
+      condition.query = "like";
+      condition.val={regex:escRegexp(condition.val)};
+    }
+    // special case for empty values
+    // see https://github.com/drolbr/Overpass-API/issues/53
+    if (val === '') {
+      if (condition.query === "eq") {
+        condition.query = "like";
+        condition.val={regex:'^$'};
+      } else if (condition.query === "neq") {
+        condition.query = "notlike";
+        condition.val={regex:'^$'};
+      }
+    }
+    // special case for empty keys
+    // see https://github.com/drolbr/Overpass-API/issues/53#issuecomment-26325122
+    if (key === '') {
+      if (condition.query === "key") {
+        condition.query = "likelike";
+        key='^$';
+        condition.val={regex: '.*'};
+      } else if (condition.query === "eq") {
+        condition.query = "likelike";
+        key='^$';
+        condition.val={regex: '^'+escRegexp(condition.val)+'$'};
+      } else if (condition.query === "like") {
+        condition.query = "likelike";
+        key='^$';
+      }
+    }
+    // construct the query clause
+    switch(condition.query) {
+      case "key":
+        return '["'+key+'"]';
+      case "nokey":
+        return '["'+key+'"!~".*"]';
+      case "eq":
+        return '["'+key+'"="'+val+'"]';
+      case "neq":
+        return '["'+key+'"!="'+val+'"]';
+      case "like":
+        return '["'+key+'"~"'+esc(condition.val.regex)+'"'
+               +(condition.val.modifier==="i"?',i':'')
+               +']';
+      case "likelike":
+        return '[~"'+key+'"~"'+esc(condition.val.regex)+'"'
+               +(condition.val.modifier==="i"?',i':'')
+               +']';
+      case "notlike":
+        return '["'+key+'"!~"'+esc(condition.val.regex)+'"'
+               +(condition.val.modifier==="i"?',i':'')
+               +']';
+      case "meta":
+        switch(condition.meta) {
+          case "id":
+            return '('+val+')';
+          case "newer":
+            if (condition.val.match(/^-?\d+ ?(seconds?|minutes?|hours?|days?|weeks?|months?|years?)?$/))
+              return '(newer:"{{date:'+val+'}}")';
+            return '(newer:"'+val+'")';
+          case "user":
+            return '(user:"'+val+'")';
+          case "uid":
+            return '(uid:'+val+')';
+          default:
+            console.error("unknown query type: meta/"+condition.meta);
+            return false;
+        }
+      case "free form":
+        // own module, special cased below
+      default:
+        console.error("unknown query type: "+condition.query);
+        return false;
+    }
+  }
+  function get_query_clause_str(condition) {
+    function quotes(s) {
+      if (s.match(/^[a-zA-Z0-9_]+$/) === null)
+        return '"'+s.replace(/"/g,'\\"')+'"';
+      return s;
+    }
+    function quoteRegex(s) {
+      if (s.regex.match(/^[a-zA-Z0-9_]+$/) === null || s.modifier)
+        return '/'+s.regex.replace(/\//g,'\\/')+'/'+(s.modifier||'');
+      return s.regex;
+    }
+    switch(condition.query) {
+      case "key":
+        return quote_comment_str(quotes(condition.key)+'=*');
+      case "nokey":
+        return quote_comment_str(quotes(condition.key)+'!=*');
+      case "eq":
+        return quote_comment_str(quotes(condition.key)+'='+quotes(condition.val));
+      case "neq":
+        return quote_comment_str(quotes(condition.key)+'!='+quotes(condition.val));
+      case "like":
+        return quote_comment_str(quotes(condition.key)+'~'+quoteRegex(condition.val));
+      case "likelike":
+        return quote_comment_str('~'+quotes(condition.key)+'~'+quoteRegex(condition.val));
+      case "notlike":
+        return quote_comment_str(quotes(condition.key)+'!~'+quoteRegex(condition.val));
+      case "substr":
+        return quote_comment_str(quotes(condition.key)+':'+quotes(condition.val));
+      case "meta":
+        switch(condition.meta) {
+          case "id":
+            return quote_comment_str('id:'+quotes(condition.val));
+          case "newer":
+            return quote_comment_str('newer:'+quotes(condition.val));
+          case "user":
+            return quote_comment_str('user:'+quotes(condition.val));
+          case "uid":
+            return quote_comment_str('uid:'+quotes(condition.val));
+          default:
+            return '';
+        }
+      case "free form":
+        return quote_comment_str(quotes(condition.free));
+      default:
+        return '';
+    }
+  }
+
+  parsedQuery.query = normalize(parsedQuery.query);
+
+  if (options.comment)
+    query_parts.push('// gather results');
+  query_parts.push('(');
+  for (var i=0; i<parsedQuery.query.queries.length; i++) {
+    var and_query = parsedQuery.query.queries[i];
+
+    var types = ['node','way','relation'];
+    var clauses = [];
+    var clauses_str = [];
+    for (var j=0; j<and_query.queries.length; j++) {
+      var cond_query = and_query.queries[j];
+      // todo: looks like some code duplication here could be reduced by refactoring
+      if (cond_query.query === "free form") {
+        // eventually load free form query module
+        if (!freeFormQuery) freeFormQuery = require('./free')(options.freeFormPresets);
+        var ffs_clause = freeFormQuery.get_query_clause(cond_query);
+        if (ffs_clause === false) {
+          console.error("Couldn't find preset for free form input: "+cond_query.free)
+          return false;
+        }
+        // restrict possible data types
+        types = types.filter(function(t) {
+          return ffs_clause.types.indexOf(t) != -1;
+        });
+        // add clauses
+        if (options.comment)
+          clauses_str.push(get_query_clause_str(cond_query));
+        clauses = clauses.concat(ffs_clause.conditions.map(function(condition) {
+          return get_query_clause(condition);
+        }));
+      } else if (cond_query.query === "type") {
+        // restrict possible data types
+        types = types.indexOf(cond_query.type) != -1 ? [cond_query.type] : [];
+      } else {
+        // add another query clause
+        if (options.comment)
+          clauses_str.push(get_query_clause_str(cond_query));
+        var clause = get_query_clause(cond_query);
+        if (clause === false) return false;
+        clauses.push(clause);
+      }
+    }
+    clauses_str = clauses_str.join(' and ');
+
+    // construct query
+    if (options.comment)
+      query_parts.push('  // query part for: “'+clauses_str+'”')
+    for (var t=0; t<types.length; t++) {
+      var buffer = '  '+types[t];
+      for (var c=0; c<clauses.length; c++)
+        buffer += clauses[c];
+      if (bounds_part)
+        buffer += bounds_part;
+      buffer += ';';
+      query_parts.push(buffer);
+    }
+  }
+  query_parts.push(');');
+
+  if (options.comment)
+    query_parts.push('// print results');
+  if (options.outputMode === "recursive") {
+    query_parts.push('out body;');
+    query_parts.push('>;');
+    query_parts.push('out skel qt;');
+  } else if (options.outputMode === "recursive_meta") {
+    query_parts.push('(._;>;);');
+    query_parts.push('out meta;');
+  } else {
+    query_parts.push('out ' + options.outputMode + ';');
+  }
+
+  return query_parts.join('\n');
+}
+
+module.exports = (function() {
+  "use strict";
+
+  /*
+   * Generated by PEG.js 0.9.0.
+   *
+   * http://pegjs.org/
+   */
+
+  function peg$subclass(child, parent) {
+    function ctor() { this.constructor = child; }
+    ctor.prototype = parent.prototype;
+    child.prototype = new ctor();
+  }
+
+  function peg$SyntaxError(message, expected, found, location) {
+    this.message  = message;
+    this.expected = expected;
+    this.found    = found;
+    this.location = location;
+    this.name     = "SyntaxError";
+
+    if (typeof Error.captureStackTrace === "function") {
+      Error.captureStackTrace(this, peg$SyntaxError);
+    }
+  }
+
+  peg$subclass(peg$SyntaxError, Error);
+
+  function peg$parse(input) {
+    var options = arguments.length > 1 ? arguments[1] : {},
+        parser  = this,
+
+        peg$FAILED = {},
+
+        peg$startRuleIndices = { start: 0 },
+        peg$startRuleIndex   = 0,
+
+        peg$consts = [
+          function(x) { return x },
+          "in bbox",
+          { type: "literal", value: "in bbox", description: "\"in bbox\"" },
+          "IN BBOX",
+          { type: "literal", value: "IN BBOX", description: "\"IN BBOX\"" },
+          function(x) { return { bounds:"bbox", query:x } },
+          "in",
+          { type: "literal", value: "in", description: "\"in\"" },
+          "IN",
+          { type: "literal", value: "IN", description: "\"IN\"" },
+          function(x, y) { return { bounds:"area", query:x, area:y } },
+          "around",
+          { type: "literal", value: "around", description: "\"around\"" },
+          "AROUND",
+          { type: "literal", value: "AROUND", description: "\"AROUND\"" },
+          function(x, y) { return { bounds:"around", query:x, area:y } },
+          "global",
+          { type: "literal", value: "global", description: "\"global\"" },
+          "GLOBAL",
+          { type: "literal", value: "GLOBAL", description: "\"GLOBAL\"" },
+          function(x) { return { bounds:"global", query:x } },
+          "or",
+          { type: "literal", value: "or", description: "\"or\"" },
+          "OR",
+          { type: "literal", value: "OR", description: "\"OR\"" },
+          "||",
+          { type: "literal", value: "||", description: "\"||\"" },
+          "|",
+          { type: "literal", value: "|", description: "\"|\"" },
+          function(x, y) { return { logical:"or", queries:[x,y] } },
+          "and",
+          { type: "literal", value: "and", description: "\"and\"" },
+          "AND",
+          { type: "literal", value: "AND", description: "\"AND\"" },
+          "&&",
+          { type: "literal", value: "&&", description: "\"&&\"" },
+          "&",
+          { type: "literal", value: "&", description: "\"&\"" },
+          function(x, y) { return { logical:"and", queries:[x,y] } },
+          "(",
+          { type: "literal", value: "(", description: "\"(\"" },
+          ")",
+          { type: "literal", value: ")", description: "\")\"" },
+          function(x) { return x; },
+          "=",
+          { type: "literal", value: "=", description: "\"=\"" },
+          "==",
+          { type: "literal", value: "==", description: "\"==\"" },
+          function(x, y) { return { query:"eq", key:x, val:y } },
+          "!=",
+          { type: "literal", value: "!=", description: "\"!=\"" },
+          "<>",
+          { type: "literal", value: "<>", description: "\"<>\"" },
+          function(x, y) { return { query:"neq", key:x, val:y } },
+          "*",
+          { type: "literal", value: "*", description: "\"*\"" },
+          function(x) { return { query:"key", key:x } },
+          "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(x) { return { query:"nokey", key:x } },
+          "~=",
+          { type: "literal", value: "~=", description: "\"~=\"" },
+          "~",
+          { type: "literal", value: "~", description: "\"~\"" },
+          "=~",
+          { type: "literal", value: "=~", description: "\"=~\"" },
+          function(x, y) { return { query:"like", key:x, val:y.regex?y:{regex:y} } },
+          "like",
+          { type: "literal", value: "like", description: "\"like\"" },
+          "LIKE",
+          { type: "literal", value: "LIKE", description: "\"LIKE\"" },
+          function(x, y) { return { query:"likelike", key:x, val:y.regex?y:{regex:y} } },
+          "!~",
+          { type: "literal", value: "!~", description: "\"!~\"" },
+          function(x, y) { return { query:"notlike", key:x, val:y.regex?y:{regex:y} } },
+          ":",
+          { type: "literal", value: ":", description: "\":\"" },
+          function(x, y) { return { query:"substr", key:x, val:y } },
+          "type",
+          { type: "literal", value: "type", description: "\"type\"" },
+          function(x) { return { query:"type", type:x } },
+          "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(x, y) { return { query:"meta", meta:x, val:y } },
+          function(x) { return { query:"free form", free:x } },
+          { type: "other", description: "Key" },
+          /^[a-zA-Z0-9_:\-]/,
+          { type: "class", value: "[a-zA-Z0-9_:-]", description: "[a-zA-Z0-9_:-]" },
+          function(s) { return s.join(''); },
+          "\"",
+          { type: "literal", value: "\"", description: "\"\\\"\"" },
+          "'",
+          { type: "literal", value: "'", description: "\"'\"" },
+          function(parts) {
+                return parts[1];
+              },
+          { type: "other", description: "string" },
+          /^[^'" ()~=!*\/:<>&|[\]{}#+@$%?\^.,]/,
+          { type: "class", value: "[^'\" ()~=!*/:<>&|[\\]{}#+@$%?^.,]", description: "[^'\" ()~=!*/:<>&|[\\]{}#+@$%?^.,]" },
+          function(chars) { return chars.join(""); },
+          "\\",
+          { type: "literal", value: "\\", description: "\"\\\\\"" },
+          { type: "any", description: "any character" },
+          function(char_) { return char_;     },
+          function(sequence) { return sequence;  },
+          /^['"\\bfnrtv]/,
+          { type: "class", value: "['\"\\\\bfnrtv]", description: "['\"\\\\bfnrtv]" },
+          function(char_) {
+                return char_
+                  .replace("b", "\b")
+                  .replace("f", "\f")
+                  .replace("n", "\n")
+                  .replace("r", "\r")
+                  .replace("t", "\t")
+                  .replace("v", "\x0B") // IE does not recognize "\v".
+              },
+          "/",
+          { type: "literal", value: "/", description: "\"/\"" },
+          "i",
+          { type: "literal", value: "i", description: "\"i\"" },
+          "",
+          function(parts) {
+                return { regex: parts[1], modifier: parts[3] };
+              },
+          "\\/",
+          { type: "literal", value: "\\/", description: "\"\\\\/\"" },
+          function() { return "/";  },
+          { type: "other", description: "whitespace" },
+          /^[ \t\n\r]/,
+          { type: "class", value: "[ \\t\\n\\r]", description: "[ \\t\\n\\r]" }
+        ],
+
+        peg$bytecode = [
+          peg$decode("%;;/:#;!/1$;;/($8#: #!!)(#'#(\"'#&'#"),
+          peg$decode("%;\"/Y#$;</&#0#*;<&&&#/C$2!\"\"6!7\".) &2#\"\"6#7$/($8#:%#!\")(#'#(\"'#&'#.\u0141 &%;\"/y#$;</&#0#*;<&&&#/c$2&\"\"6&7'.) &2(\"\"6(7)/H$$;</&#0#*;<&&&#/2$;2/)$8%:*%\"$ )(%'#($'#(#'#(\"'#&'#.\
+xDB &%;\"/y#$;</&#0#*;<&&&#/c$2+\"\"6+7,.) &2-\"\"6-7./H$$;</&#0#*;<&&&#/2$;2/)$8%:/%\"$ )(%'#($'#(#'#(\"'#&'#.u &%;\"/Y#$;</&#0#*;<&&&#/C$20\"\"6071.) &22\"\"6273/($8#:4#!\")(#'#(\"'#&'#./ &%;\"/' 8!:%!! )"),
+          peg$decode("%;#/\x91#$;</&#0#*;<&&&#/{$25\"\"6576.A &27\"\"6778.5 &29\"\"697:.) &2;\"\"6;7</H$$;</&#0#*;<&&&#/2$;\"/)$8%:=%\"$ )(%'#($'#(#'#(\"'#&'#.# &;#"),
+          peg$decode("%;$/\x91#$;</&#0#*;<&&&#/{$2>\"\"6>7?.A &2@\"\"6 at 7A.5 &2B\"\"6B7C.) &2D\"\"6D7E/H$$;</&#0#*;<&&&#/2$;#/)$8%:F%\"$ )(%'#($'#(#'#(\"'#&'#.# &;$"),
+          peg$decode(";%.b &%2G\"\"6G7H/R#;;/I$;\"/@$;;/7$2I\"\"6I7J/($8%:K%!\")(%'#($'#(#'#(\"'#&'#"),
+          peg$decode(";..Y &;/.S &;&.M &;'.G &;(.A &;).; &;*.5 &;+./ &;,.) &;-.# &;0"),
+          peg$decode("%;1/_#;;/V$2L\"\"6L7M.) &2N\"\"6N7O/;$;;/2$;2/)$8%:P%\"$ )(%'#($'#(#'#(\"'#&'#"),
+          peg$decode("%;1/_#;;/V$2Q\"\"6Q7R.) &2S\"\"6S7T/;$;;/2$;2/)$8%:U%\"$ )(%'#($'#(#'#(\"'#&'#"),
+          peg$decode("%;1/d#;;/[$2L\"\"6L7M.) &2N\"\"6N7O/@$;;/7$2V\"\"6V7W/($8%:X%!$)(%'#($'#(#'#(\"'#&'#.\u010B &%;2/\u0101#$;</&#0#*;<&&&#/\xEB$%2Y\"\"6Y7Z/m#$;</&#0#*;<&&&#/W$2[\"\"6[7
+\\/H$$;</&#0#*;<&&&#/2$2]\"\"6]7^/#$+%)(%'#($'#(#'#(\"'#&'#.} &%2_\"\"6_7`/m#$;</&#0#*;<&&&#/W$2a\"\"6a7b/H$$;</&#0#*;<&&&#/2$2c\"\"6c7d/#$+%)(%'#($'#(#'#(\"'#&'#/($8#:X#!\")(#'#(\"'#&'#"),
+          peg$decode("%;1/d#;;/[$2Q\"\"6Q7R.) &2S\"\"6S7T/@$;;/7$2V\"\"6V7W/($8%:e%!$)(%'#($'#(#'#(\"'#&'#.\xC1 &%;2/\xB7#$;</&#0#*;<&&&#/\xA1$%2Y
+\"\"6Y7Z/H#$;</&#0#*;<&&&#/2$2]\"\"6]7^/#$+#)(#'#(\"'#&'#.X &%2_\"\"6_7`/H#$;</&#0#*;<&&&#/2$2c\"\"6c7d/#$+#)(#'#(\"'#&'#/($8#:e#!\")(#'#(\"'#&'#"),
+          peg$decode("%;1/q#;;/h$2f\"\"6f7g.5 &2h\"\"6h7i.) &2j\"\"6j7k/A$;;/8$;2.# &;8/)$8%:l%\"$ )(%'#($'#(#'#(\"'#&'#.
+\x89 &%;2/#$;</&#0#*;<&&&#/i$2m\"\"6m7n.) &2o\"\"6o7p/N$$;</&#0#*;<&&&#/8$;2.# &;8/)$8%:l%\"$ )(%'#($'#(#'#(\"'#&'#"),
+          peg$decode("%2h\"\"6h7i/\x83#;;/z$;2/q$;;/h$2f\"\"6f7g.5 &2h\"\"6h7i.) &2j\"\"6j7k/A$;;/8$;2.# &;8/)$8':q'\"$ )(''#(&'#(%'#($'#(#'#(\"'#&'#"),
+          peg$decode("%;1/Y#;;/P$2r\"\"6r7s/A$;;/8$;2.# &;8/)$8%:t%\"$ )(%'#($'#(#'#(\"'#&'#.\xE7 &%;2/\xDD#$;</&#0#*;<&&&#/\xC7$%2[\"\"6[7\\/H#$;</&#0#*;<&&&#/
+2$2m\"\"6m7n/#$+#)(#'#(\"'#&'#.X &%2a\"\"6a7b/H#$;</&#0#*;<&&&#/2$2o\"\"6o7p/#$+#)(#'#(\"'#&'#/N$$;</&#0#*;<&&&#/8$;2.# &;8/)$8%:t%\"$ )(%'#($'#(#'#(\"'#&'#"),
+          peg$decode("%;2/S#;;/J$2u\"\"6u7v/;$;;/2$;2/)$8%:w%\"$ )(%'#($'#(#'#(\"'#&'#"),
+          peg$decode("%2x\"\"6x7y/R#;;/I$2u\"\"6u7v/:$;;/1$;2/($8%:z%! )(%'#($'#(#'#(\"'#&'#"),
+          peg$decode("%2{\"\"6{7|.A &2}\"\"6}7~.5 &2\"\"67\x80.) &2\x81\"\"6\x817\x82/S#;;/J$2u\"\"6u7v/;$;;/2$;2/)$8%:\x83%\"$ )(%'#($'#(#'#(\"'#&'#"),
+          peg$decode("%;2/' 8!:\x84!! )"),
+          peg$decode("<%$4\x86\"\"5!7\x87/,#0)*4\x86\"\"5!7\x87&&&#/' 8!:\x88!! ).\x85 &%%2\x89\"\"6\x897\x8A/;#;3/2$2\x89\"\"6
+\x897\x8A/#$+#)(#'#(\"'#&'#.K &%2\x8B\"\"6\x8B7\x8C/;#;4/2$2\x8B\"\"6\x8B7\x8C/#$+#)(#'#(\"'#&'#/' 8!:\x8D!! )=.\" 7\x85"),
+          peg$decode("<%$4\x8F\"\"5!7\x90/,#0)*4\x8F\"\"5!7\x90&&&#/' 8!:\x88!! ).\x85 &%%2\x89\"\"6\x897\x8A/;#;3/2$2\x89\"\"6
+\x897\x8A/#$+#)(#'#(\"'#&'#.K &%2\x8B\"\"6\x8B7\x8C/;#;4/2$2\x8B\"\"6\x8B7\x8C/#$+#)(#'#(\"'#&'#/' 8!:\x8D!! )=.\" 7\x8E"),
+          peg$decode("%$;50#*;5&/' 8!:\x91!! )"),
+          peg$decode("%$;60#*;6&/' 8!:\x91!! )"),
+          peg$decode("%%<2\x89\"\"6\x897\x8A.) &2\x92\"\"6\x927\x93=.##&&!&'#/6#1\"\"5!7\x94/($8\":\x95\"! )(\"'#&'#.A &%2\x92\"\"6\x927\x93/1#;7/($8\":\x96\"! )(\"'#&'#"),
+          peg$decode("%%<2\x8B\"\"6\x8B7\x8C.) &2\x92\"\"6\x927\x93=.##&&!&'#/6#1\"\"5!7\x94/($8\":\x95\"! )(\"'#&'#.A &%2\x92\"\"6\x927\x93/1#;7/($8\":\x96\"! )(\"'#&'#"),
+          peg$decode("%4\x97\"\"5!7\x98/' 8!:\x99!! )"),
+          peg$decode("<%%2\x9A\"\"6\x9A7\x9B/U#;9/L$2\x9A\"\"6\x9A7\x9B/=$2\x9C\"\"6\x9C7\x9D.# & \x9E.\" &\"/#$+$)($'#(#'#(\"'#&'#/' 8!:\x9F!! )=.\" 7\x8E"),
+          peg$decode("%$;:/&#0#*;:&&&#/' 8!:\x91!! )"),
+          peg$decode("%%<2\x9A\"\"6\x9A7\x9B.) &2\xA0\"\"6\xA07\xA1=.##&&!&'#/6#1\"\"5!7\x94/($8\":\x95\"! )(\"'#&'#.4 &%2\xA0\"\"6\xA07\xA1/& 8!:\xA2! )"),
+          peg$decode("<$;<0#*;<&=.\" 7\xA3"),
+          peg$decode("<4\xA4\"\"5!7\xA5=.\" 7\xA3")
+        ],
+
+        peg$currPos          = 0,
+        peg$savedPos         = 0,
+        peg$posDetailsCache  = [{ line: 1, column: 1, seenCR: false }],
+        peg$maxFailPos       = 0,
+        peg$maxFailExpected  = [],
+        peg$silentFails      = 0,
+
+        peg$result;
+
+    if ("startRule" in options) {
+      if (!(options.startRule in peg$startRuleIndices)) {
+        throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
+      }
+
+      peg$startRuleIndex = peg$startRuleIndices[options.startRule];
+    }
+
+    function text() {
+      return input.substring(peg$savedPos, peg$currPos);
+    }
+
+    function location() {
+      return peg$computeLocation(peg$savedPos, peg$currPos);
+    }
+
+    function expected(description) {
+      throw peg$buildException(
+        null,
+        [{ type: "other", description: description }],
+        input.substring(peg$savedPos, peg$currPos),
+        peg$computeLocation(peg$savedPos, peg$currPos)
+      );
+    }
+
+    function error(message) {
+      throw peg$buildException(
+        message,
+        null,
+        input.substring(peg$savedPos, peg$currPos),
+        peg$computeLocation(peg$savedPos, peg$currPos)
+      );
+    }
+
+    function peg$computePosDetails(pos) {
+      var details = peg$posDetailsCache[pos],
+          p, ch;
+
+      if (details) {
+        return details;
+      } else {
+        p = pos - 1;
+        while (!peg$posDetailsCache[p]) {
+          p--;
+        }
+
+        details = peg$posDetailsCache[p];
+        details = {
+          line:   details.line,
+          column: details.column,
+          seenCR: details.seenCR
+        };
+
+        while (p < pos) {
+          ch = input.charAt(p);
+          if (ch === "\n") {
+            if (!details.seenCR) { details.line++; }
+            details.column = 1;
+            details.seenCR = false;
+          } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") {
+            details.line++;
+            details.column = 1;
+            details.seenCR = true;
+          } else {
+            details.column++;
+            details.seenCR = false;
+          }
+
+          p++;
+        }
+
+        peg$posDetailsCache[pos] = details;
+        return details;
+      }
+    }
+
+    function peg$computeLocation(startPos, endPos) {
+      var startPosDetails = peg$computePosDetails(startPos),
+          endPosDetails   = peg$computePosDetails(endPos);
+
+      return {
+        start: {
+          offset: startPos,
+          line:   startPosDetails.line,
+          column: startPosDetails.column
+        },
+        end: {
+          offset: endPos,
+          line:   endPosDetails.line,
+          column: endPosDetails.column
+        }
+      };
+    }
+
+    function peg$fail(expected) {
+      if (peg$currPos < peg$maxFailPos) { return; }
+
+      if (peg$currPos > peg$maxFailPos) {
+        peg$maxFailPos = peg$currPos;
+        peg$maxFailExpected = [];
+      }
+
+      peg$maxFailExpected.push(expected);
+    }
+
+    function peg$buildException(message, expected, found, location) {
+      function cleanupExpected(expected) {
+        var i = 1;
+
+        expected.sort(function(a, b) {
+          if (a.description < b.description) {
+            return -1;
+          } else if (a.description > b.description) {
+            return 1;
+          } else {
+            return 0;
+          }
+        });
+
+        while (i < expected.length) {
+          if (expected[i - 1] === expected[i]) {
+            expected.splice(i, 1);
+          } else {
+            i++;
+          }
+        }
+      }
+
+      function buildMessage(expected, found) {
+        function stringEscape(s) {
+          function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
+
+          return s
+            .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(ch) { return '\\x0' + hex(ch); })
+            .replace(/[\x10-\x1F\x80-\xFF]/g,    function(ch) { return '\\x'  + hex(ch); })
+            .replace(/[\u0100-\u0FFF]/g,         function(ch) { return '\\u0' + hex(ch); })
+            .replace(/[\u1000-\uFFFF]/g,         function(ch) { return '\\u'  + hex(ch); });
+        }
+
+        var expectedDescs = new Array(expected.length),
+            expectedDesc, foundDesc, i;
+
+        for (i = 0; i < expected.length; i++) {
+          expectedDescs[i] = expected[i].description;
+        }
+
+        expectedDesc = expected.length > 1
+          ? expectedDescs.slice(0, -1).join(", ")
+              + " or "
+              + expectedDescs[expected.length - 1]
+          : expectedDescs[0];
+
+        foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input";
+
+        return "Expected " + expectedDesc + " but " + foundDesc + " found.";
+      }
+
+      if (expected !== null) {
+        cleanupExpected(expected);
+      }
+
+      return new peg$SyntaxError(
+        message !== null ? message : buildMessage(expected, found),
+        expected,
+        found,
+        location
+      );
+    }
+
+    function peg$decode(s) {
+      var bc = new Array(s.length), i;
+
+      for (i = 0; i < s.length; i++) {
+        bc[i] = s.charCodeAt(i) - 32;
+      }
+
+      return bc;
+    }
+
+    function peg$parseRule(index) {
+      var bc    = peg$bytecode[index],
+          ip    = 0,
+          ips   = [],
+          end   = bc.length,
+          ends  = [],
+          stack = [],
+          params, i;
+
+      while (true) {
+        while (ip < end) {
+          switch (bc[ip]) {
+            case 0:
+              stack.push(peg$consts[bc[ip + 1]]);
+              ip += 2;
+              break;
+
+            case 1:
+              stack.push(void 0);
+              ip++;
+              break;
+
+            case 2:
+              stack.push(null);
+              ip++;
+              break;
+
+            case 3:
+              stack.push(peg$FAILED);
+              ip++;
+              break;
+
+            case 4:
+              stack.push([]);
+              ip++;
+              break;
+
+            case 5:
+              stack.push(peg$currPos);
+              ip++;
+              break;
+
+            case 6:
+              stack.pop();
+              ip++;
+              break;
+
+            case 7:
+              peg$currPos = stack.pop();
+              ip++;
+              break;
+
+            case 8:
+              stack.length -= bc[ip + 1];
+              ip += 2;
+              break;
+
+            case 9:
+              stack.splice(-2, 1);
+              ip++;
+              break;
+
+            case 10:
+              stack[stack.length - 2].push(stack.pop());
+              ip++;
+              break;
+
+            case 11:
+              stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1]));
+              ip += 2;
+              break;
+
+            case 12:
+              stack.push(input.substring(stack.pop(), peg$currPos));
+              ip++;
+              break;
+
+            case 13:
+              ends.push(end);
+              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
+
+              if (stack[stack.length - 1]) {
+                end = ip + 3 + bc[ip + 1];
+                ip += 3;
+              } else {
+                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
+                ip += 3 + bc[ip + 1];
+              }
+
+              break;
+
+            case 14:
+              ends.push(end);
+              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
+
+              if (stack[stack.length - 1] === peg$FAILED) {
+                end = ip + 3 + bc[ip + 1];
+                ip += 3;
+              } else {
+                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
+                ip += 3 + bc[ip + 1];
+              }
+
+              break;
+
+            case 15:
+              ends.push(end);
+              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
+
+              if (stack[stack.length - 1] !== peg$FAILED) {
+                end = ip + 3 + bc[ip + 1];
+                ip += 3;
+              } else {
+                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
+                ip += 3 + bc[ip + 1];
+              }
+
+              break;
+
+            case 16:
+              if (stack[stack.length - 1] !== peg$FAILED) {
+                ends.push(end);
+                ips.push(ip);
+
+                end = ip + 2 + bc[ip + 1];
+                ip += 2;
+              } else {
+                ip += 2 + bc[ip + 1];
+              }
+
+              break;
+
+            case 17:
+              ends.push(end);
+              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
+
+              if (input.length > peg$currPos) {
+                end = ip + 3 + bc[ip + 1];
+                ip += 3;
+              } else {
+                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
+                ip += 3 + bc[ip + 1];
+              }
+
+              break;
+
+            case 18:
+              ends.push(end);
+              ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
+
+              if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]) {
+                end = ip + 4 + bc[ip + 2];
+                ip += 4;
+              } else {
+                end = ip + 4 + bc[ip + 2] + bc[ip + 3];
+                ip += 4 + bc[ip + 2];
+              }
+
+              break;
+
+            case 19:
+              ends.push(end);
+              ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
+
+              if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]) {
+                end = ip + 4 + bc[ip + 2];
+                ip += 4;
+              } else {
+                end = ip + 4 + bc[ip + 2] + bc[ip + 3];
+                ip += 4 + bc[ip + 2];
+              }
+
+              break;
+
+            case 20:
+              ends.push(end);
+              ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
+
+              if (peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))) {
+                end = ip + 4 + bc[ip + 2];
+                ip += 4;
+              } else {
+                end = ip + 4 + bc[ip + 2] + bc[ip + 3];
+                ip += 4 + bc[ip + 2];
+              }
+
+              break;
+
+            case 21:
+              stack.push(input.substr(peg$currPos, bc[ip + 1]));
+              peg$currPos += bc[ip + 1];
+              ip += 2;
+              break;
+
+            case 22:
+              stack.push(peg$consts[bc[ip + 1]]);
+              peg$currPos += peg$consts[bc[ip + 1]].length;
+              ip += 2;
+              break;
+
+            case 23:
+              stack.push(peg$FAILED);
+              if (peg$silentFails === 0) {
+                peg$fail(peg$consts[bc[ip + 1]]);
+              }
+              ip += 2;
+              break;
+
+            case 24:
+              peg$savedPos = stack[stack.length - 1 - bc[ip + 1]];
+              ip += 2;
+              break;
+
+            case 25:
+              peg$savedPos = peg$currPos;
+              ip++;
+              break;
+
+            case 26:
+              params = bc.slice(ip + 4, ip + 4 + bc[ip + 3]);
+              for (i = 0; i < bc[ip + 3]; i++) {
+                params[i] = stack[stack.length - 1 - params[i]];
+              }
+
+              stack.splice(
+                stack.length - bc[ip + 2],
+                bc[ip + 2],
+                peg$consts[bc[ip + 1]].apply(null, params)
+              );
+
+              ip += 4 + bc[ip + 3];
+              break;
+
+            case 27:
+              stack.push(peg$parseRule(bc[ip + 1]));
+              ip += 2;
+              break;
+
+            case 28:
+              peg$silentFails++;
+              ip++;
+              break;
+
+            case 29:
+              peg$silentFails--;
+              ip++;
+              break;
+
+            default:
+              throw new Error("Invalid opcode: " + bc[ip] + ".");
+          }
+        }
+
+        if (ends.length > 0) {
+          end = ends.pop();
+          ip = ips.pop();
+        } else {
+          break;
+        }
+      }
+
+      return stack[0];
+    }
+
+    peg$result = peg$parseRule(peg$startRuleIndex);
+
+    if (peg$result !== peg$FAILED && peg$currPos === input.length) {
+      return peg$result;
+    } else {
+      if (peg$result !== peg$FAILED && peg$currPos < input.length) {
+        peg$fail({ type: "end", description: "end of input" });
+      }
+
+      throw peg$buildException(
+        null,
+        peg$maxFailExpected,
+        peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,
+        peg$maxFailPos < input.length
+          ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)
+          : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
+      );
+    }
+  }
+
+  return {
+    SyntaxError: peg$SyntaxError,
+    parse:       peg$parse
+  };
+})();
+
+  // ffs/wizard module
+module.exports = function(presets) {
+  var freeFormQuery = {};
+  if (!presets) presets = {};
+
+  if (!presets) {
+    // browser: try to use jQuery to load presets now
+    if (typeof $ === "undefined") return false;
+    var presets_file = "data/iD_presets.json";
+    try {
+      $.ajax(presets_file,{async:false,dataType:"json"}).success(function(data){
+        presets = data;
+      }).error(function(){
+        throw new Error();
+      });
+    } catch(e) {
+      console.error("failed to load presets file", presets_file, e);
+    }
+  }
+  Object.keys(presets).map(function(key) {
+    var preset = presets[key];
+    preset.nameCased = preset.name;
+    preset.name = preset.name.toLowerCase();
+    preset.terms = !preset.terms ? [] : preset.terms.map(function(term) {return term.toLowerCase();});
+  });
+  // load preset translations
+  (function loadPresetTranslations() {
+    // node: use external preset supplier? // todo
+    // browser: use jQuery
+    if (typeof $ === "undefined" || typeof i18n === "undefined") return;
+    var language = i18n.getLanguage();
+    if (language == "en") return;
+    var translation_file = "data/iD_presets_"+language+".json";
+    try {
+      $.ajax(translation_file,{async:false,dataType:"json"}).success(function(data){
+        // load translated names and terms into presets object
+        Object.keys(data).map(function(preset) {
+          var translation = data[preset];
+          preset = presets[preset];
+          preset.translated = true;
+          // save original preset name under alternative terms
+          var oriPresetName = preset.name;
+          // save translated preset name
+          preset.nameCased = translation.name;
+          preset.name = translation.name.toLowerCase();
+          // add new terms
+          if (translation.terms)
+            preset.terms = translation.terms.split(",")
+              .map(function(term) { return term.trim().toLowerCase(); })
+              .concat(preset.terms);
+          // add this to the front to allow exact (english) preset names to match before terms
+          preset.terms.unshift(oriPresetName);
+        });
+      }).error(function(){
+        throw new Error();
+      });
+    } catch(e) {
+      console.error("failed to load preset translations file: "+translation_file);
+    }
+  })();
+
+  freeFormQuery.get_query_clause = function(condition) {
+    // search presets for ffs term
+    var search = condition.free.toLowerCase();
+    var candidates = Object.keys(presets).map(function(key) {
+      return presets[key];
+    }).filter(function(preset) {
+      if (preset.searchable===false) return false;
+      if (preset.name === search) return true;
+      preset._termsIndex = preset.terms.indexOf(search);
+      return preset._termsIndex != -1;
+    });
+    if (candidates.length === 0)
+      return false;
+    // sort candidates
+    candidates.sort(function(a,b) {
+      // prefer exact name matches
+      if (a.name === search) return -1;
+      if (b.name === search) return  1;
+      return a._termsIndex - b._termsIndex;
+    });
+    var preset = candidates[0];
+    var types = [];
+    preset.geometry.forEach(function(g) {
+      switch (g) {
+        case "point":
+        case "vertex":
+          types.push("node");
+          break;
+        case "line":
+          types.push("way");
+          break;
+        case "area":
+          types.push("way");
+          types.push("relation"); // todo: additionally add type=multipolygon?
+          break;
+        case "relation":
+          types.push("relation");
+          break;
+        default:
+          console.error("unknown geometry type "+g+" of preset "+preset.name);
+      }
+    });
+    function onlyUnique(value, index, self) {
+      return self.indexOf(value) === index;
+    }
+    return {
+      types: types.filter(onlyUnique),
+      conditions: Object.keys(preset.tags).map(function(k) {
+        var v = preset.tags[k];
+        return {
+          query: v==="*" ? "key" : "eq",
+          key: k,
+          val: v
+        };
+      })
+    };
+  }
+
+  freeFormQuery.fuzzy_search = function(condition) {
+    // search presets for ffs term
+    var search = condition.free.toLowerCase();
+    // fuzzyness: max lev.dist allowed to still match
+    var fuzzyness = 2+Math.floor(search.length/7);
+    function fuzzyMatch(term) {
+      return levenshteinDistance(term, search) <= fuzzyness;
+    }
+    var candidates = Object.keys(presets).map(function(key) {
+      return presets[key];
+    }).filter(function(preset) {
+      if (preset.searchable===false) return false;
+      if (fuzzyMatch(preset.name)) return true;
+      return preset.terms.some(fuzzyMatch);
+    });
+    if (candidates.length === 0)
+      return false;
+    // sort candidates
+    function preset_weight(preset) {
+      return [preset.name].concat(preset.terms).map(function(term, index) {
+        return levenshteinDistance(term,search);
+      }).reduce(function min(a, b) {
+        return a <= b ? a : b;
+      });
+    };
+    candidates.sort(function(a,b) {
+      return preset_weight(a) - preset_weight(b);
+    });
+    var preset = candidates[0];
+    return preset.nameCased;
+  }
+
+
+  return freeFormQuery;
+};
+
+/*
+ * partial implementation of overpass turbo extended query syntax
+ * (http://wiki.openstreetmap.org/wiki/Overpass_turbo/Extended_Overpass_Queries)
+ */
+
+var Promise = require('promise'),
+    request = require('request-promise');
+
+// converts relative time to ISO time string
+function relativeTime(instr, callback) {
+  var now = Date.now();
+  // very basic differential date
+  instr = instr.toLowerCase().match(/(-?[0-9]+) ?(seconds?|minutes?|hours?|days?|weeks?|months?|years?)?/);
+  if (instr === null) {
+    return Promise.reject(new Error('unable to expand date shortcut.'));
+  }
+  var count = parseInt(instr[1]);
+  var interval;
+  switch (instr[2]) {
+    case "second":
+    case "seconds":
+    interval=1; break;
+    case "minute":
+    case "minutes":
+    interval=60; break;
+    case "hour":
+    case "hours":
+    interval=3600; break;
+    case "day":
+    case "days":
+    default:
+    interval=86400; break;
+    case "week":
+    case "weeks":
+    interval=604800; break;
+    case "month":
+    case "months":
+    interval=2628000; break;
+    case "year":
+    case "years":
+    interval=31536000; break;
+  }
+  var date = now - count*interval*1000;
+  return Promise.resolve((new Date(date)).toISOString());
+}
+
+// Promise wrapper for browser XMLHttpRequest
+// from http://www.html5rocks.com/en/tutorials/es6/promises/
+function get(url) {
+  return new Promise(function(resolve, reject) {
+    var req = new XMLHttpRequest();
+    req.open('GET', url);
+
+    req.onload = function() {
+      if (req.status == 200) {
+        resolve(req.response);
+      }
+      else {
+        reject(Error("XMLHttpRequest Error: "+req.statusText));
+      }
+    };
+    req.onerror = function() {
+      reject(Error("Network Error"));
+    };
+    req.send();
+  });
+}
+// helper function to query nominatim for best fitting result
+function nominatimRequest(search,filter) {
+  var requestUrl = "https://nominatim.openstreetmap.org/search?format=json&q="+encodeURIComponent(search);
+  var _request;
+  if (typeof XMLHttpRequest !== "undefined") {
+    // browser
+    _request = get(requestUrl).then(JSON.parse);
+  } else {
+    // node
+    _request = request({
+      url: requestUrl,
+      method: "GET",
+      headers: {"User-Agent": "overpass-wizard"},
+      json: true
+    });
+  }
+  return _request.then(function(data) {
+    if (filter)
+      data = data.filter(filter);
+    if (data.length === 0)
+      return Promise.reject(new Error("No result found for geocoding search: "+search));
+    else
+      return data[0];
+  });
+}
+
+// geocoding shortcuts
+function geocodeArea(instr) {
+  function filter(n) {
+    return n.osm_type && n.osm_id && n.osm_type!=="node";
+  }
+  return nominatimRequest(instr,filter).then(function(res) {
+    var area_ref = 1*res.osm_id;
+    if (res.osm_type == "way")
+      area_ref += 2400000000;
+    if (res.osm_type == "relation")
+      area_ref += 3600000000;
+    res = "area("+area_ref+")";
+    return res;
+  });
+}
+function geocodeCoords(instr) {
+  return nominatimRequest(instr).then(function(res) {
+    res = res.lat+','+res.lon;
+    return res;
+  });
+}
+
+var expansions = {
+  date: relativeTime,
+  geocodeArea: geocodeArea,
+  geocodeCoords: geocodeCoords
+};
+
+module.exports = function(overpassQuery, bbox, callback) {
+  // 1. bbox
+  if (bbox) overpassQuery = overpassQuery.replace(/{{bbox}}/g, bbox);
+  // 2. constants
+  var constantRegexp = "{{([a-zA-Z0-9_]+)=(.+?)}}";
+  var constants = overpassQuery.match(new RegExp(constantRegexp, 'g')) || [];
+  constants.forEach(function(constant) {
+    var constantDefinition = constant.match(new RegExp(constantRegexp)),
+        constantShortcut = "{{"+constantDefinition[1]+"}}",
+        constantValue = constantDefinition[2];
+    while (overpassQuery.indexOf(constantShortcut) >= 0) {
+      overpassQuery = overpassQuery.replace(constantShortcut, constantValue);
+    }
+  });
+  // 3. shortcuts
+  var shortcutRegexp = "{{(date|geocodeArea|geocodeCoords):([\\s\\S]*?)}}";
+  var shortcuts = overpassQuery.match(new RegExp(shortcutRegexp, 'g')) || [];
+  return Promise.all(shortcuts.map(function(shortcut) {
+    shortcut = shortcut.match(new RegExp(shortcutRegexp));
+    return expansions[shortcut[1]](shortcut[2]);
+  })).then(function(expansions) {
+    expansions.forEach(function(expansion, index) {
+      overpassQuery = overpassQuery.replace(shortcuts[index], expansion);
+    });
+    return overpassQuery;
+  }).then(function(overpassQuery) {
+    callback(undefined, overpassQuery)
+  }).catch(function(err) {
+    callback(err);
+  });
+};

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/josm.git



More information about the Pkg-grass-devel mailing list