From 07c64bddd756f1bbaa36ee80dbf7052b4df4c278 Mon Sep 17 00:00:00 2001
From: Simon Legner <Simon.Legner@gmail.com>
Date: Thu, 4 Aug 2016 22:34:12 +0200
Subject: [PATCH 2/2] Evaluate extended Overpass querty `geocodeArea`

---
 src/org/openstreetmap/josm/io/NameFinder.java      |  9 ++++
 .../josm/io/OverpassDownloadReader.java            |  5 +--
 .../josm/tools/OverpassTurboQueryWizard.java       | 49 ++++++++++++++++++++++
 .../josm/tools/OverpassTurboQueryWizardTest.java   | 32 +++++++++++++-
 4 files changed, 90 insertions(+), 5 deletions(-)

diff --git a/src/org/openstreetmap/josm/io/NameFinder.java b/src/org/openstreetmap/josm/io/NameFinder.java
index c7f9256..6bdb981 100644
--- a/src/org/openstreetmap/josm/io/NameFinder.java
+++ b/src/org/openstreetmap/josm/io/NameFinder.java
@@ -13,6 +13,9 @@
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.PrimitiveId;
+import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
 import org.openstreetmap.josm.tools.HttpClient;
 import org.openstreetmap.josm.tools.OsmUrlToBounds;
 import org.openstreetmap.josm.tools.UncheckedParseException;
@@ -65,6 +68,7 @@ private NameFinder() {
         public double lon;
         public int zoom;
         public Bounds bounds;
+        public PrimitiveId osmId;
 
         public Bounds getDownloadArea() {
             return bounds != null ? bounds : OsmUrlToBounds.positionToBounds(lat, lon, zoom);
@@ -125,6 +129,11 @@ public void startElement(String namespaceURI, String localName, String qName, At
                     currentResult.bounds = new Bounds(
                             Double.parseDouble(bbox[0]), Double.parseDouble(bbox[2]),
                             Double.parseDouble(bbox[1]), Double.parseDouble(bbox[3]));
+                    final String osmId = atts.getValue("osm_id");
+                    final String osmType = atts.getValue("osm_type");
+                    if (osmId != null && osmType != null) {
+                        currentResult.osmId = new SimplePrimitiveId(Long.parseLong(osmId), OsmPrimitiveType.from(osmType));
+                    }
                     data.add(currentResult);
                 }
             } catch (NumberFormatException x) {
diff --git a/src/org/openstreetmap/josm/io/OverpassDownloadReader.java b/src/org/openstreetmap/josm/io/OverpassDownloadReader.java
index be43043..cfbb637 100644
--- a/src/org/openstreetmap/josm/io/OverpassDownloadReader.java
+++ b/src/org/openstreetmap/josm/io/OverpassDownloadReader.java
@@ -50,9 +50,8 @@ protected String getRequestForBbox(double lon1, double lat1, double lon2, double
         if (overpassQuery.isEmpty())
             return super.getRequestForBbox(lon1, lat1, lon2, lat2);
         else {
-            String realQuery = completeOverpassQuery(overpassQuery);
-            return "interpreter?data=" + Utils.encodeUrl(realQuery)
-                    + "&bbox=" + lon1 + ',' + lat1 + ',' + lon2 + ',' + lat2;
+            return "interpreter?data=" + Utils.encodeUrl(overpassQuery)
+                    + (!overpassQuery.contains("searchArea") ? "&bbox=" + lon1 + ',' + lat1 + ',' + lon2 + ',' + lat2 : "");
         }
     }
 
diff --git a/src/org/openstreetmap/josm/tools/OverpassTurboQueryWizard.java b/src/org/openstreetmap/josm/tools/OverpassTurboQueryWizard.java
index f0020d9..555a525 100644
--- a/src/org/openstreetmap/josm/tools/OverpassTurboQueryWizard.java
+++ b/src/org/openstreetmap/josm/tools/OverpassTurboQueryWizard.java
@@ -1,8 +1,14 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.tools;
 
+import static org.openstreetmap.josm.tools.I18n.tr;
+
 import java.io.IOException;
 import java.io.Reader;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.script.Invocable;
 import javax.script.ScriptEngine;
@@ -10,7 +16,10 @@
 import javax.script.ScriptException;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.PrimitiveId;
 import org.openstreetmap.josm.io.CachedFile;
+import org.openstreetmap.josm.io.NameFinder;
 
 /**
  * Uses <a href="https://github.com/tyrasd/overpass-wizard/">Overpass Turbo query wizard</a> code (MIT Licensed)
@@ -70,6 +79,7 @@ public String constructQuery(String search) {
             String query = (String) result;
             if (query != null) {
                 query = query.replace("[bbox:{{bbox}}]", "");
+                query = expandExtendedQueries(query);
             }
             return query;
         } catch (NoSuchMethodException e) {
@@ -78,4 +88,43 @@ public String constructQuery(String search) {
             throw new UncheckedParseException("Failed to execute OverpassTurboQueryWizard", e);
         }
     }
+
+    /**
+     * Evaluates some features of overpass turbo extended query syntax.
+     * See https://wiki.openstreetmap.org/wiki/Overpass_turbo/Extended_Overpass_Turbo_Queries
+     */
+    static String expandExtendedQueries(String query) {
+        final StringBuffer sb = new StringBuffer();
+        final Matcher matcher = Pattern.compile("\\{\\{(geocodeArea):([^}]+)\\}\\}").matcher(query);
+        while (matcher.find()) {
+            try {
+                switch (matcher.group(1)) {
+                    case "geocodeArea":
+                        matcher.appendReplacement(sb, geocodeArea(matcher.group(2)));
+                }
+            } catch (Exception ex) {
+                final String msg = tr("Failed to evaluate {0}", matcher.group());
+                Main.warn(ex, msg);
+                matcher.appendReplacement(sb, "// " + msg + "\n");
+            }
+        }
+        matcher.appendTail(sb);
+        query = sb.toString();
+        return query;
+    }
+
+    private static String geocodeArea(String area) {
+        // Offsets defined in https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#By_element_id
+        final EnumMap<OsmPrimitiveType, Long> idOffset = new EnumMap<>(OsmPrimitiveType.class);
+        idOffset.put(OsmPrimitiveType.NODE, 0L);
+        idOffset.put(OsmPrimitiveType.WAY, 2400000000L);
+        idOffset.put(OsmPrimitiveType.RELATION, 3600000000L);
+        try {
+            final List<NameFinder.SearchResult> results = NameFinder.queryNominatim(area);
+            final PrimitiveId osmId = results.iterator().next().osmId;
+            return String.format("area(%d)", osmId.getUniqueId() + idOffset.get(osmId.getType()));
+        } catch (IOException ex) {
+            throw new UncheckedParseException(ex);
+        }
+    }
 }
diff --git a/test/unit/org/openstreetmap/josm/tools/OverpassTurboQueryWizardTest.java b/test/unit/org/openstreetmap/josm/tools/OverpassTurboQueryWizardTest.java
index 125042f..2a7226f 100644
--- a/test/unit/org/openstreetmap/josm/tools/OverpassTurboQueryWizardTest.java
+++ b/test/unit/org/openstreetmap/josm/tools/OverpassTurboQueryWizardTest.java
@@ -14,11 +14,11 @@
  */
 public class OverpassTurboQueryWizardTest {
     /**
-     * Base test environment is enough
+     * Some of this depends on preferences.
      */
     @Rule
     @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
-    public JOSMTestRules test = new JOSMTestRules();
+    public JOSMTestRules test = new JOSMTestRules().preferences();
 
     /**
      * Test key=value.
@@ -38,6 +38,34 @@ public void testKeyValue() {
     }
 
     /**
+     * Tests evaluating the extended query feature {@code geocodeArea}.
+     */
+    @Test
+    public void testGeocodeArea() {
+        final String query = OverpassTurboQueryWizard.getInstance().constructQuery("amenity=drinking_water in London");
+        assertEquals("" +
+                "[out:xml][timeout:25];\n" +
+                "area(3600065606)->.searchArea;\n" +
+                "(\n" +
+                "  node[\"amenity\"=\"drinking_water\"](area.searchArea);\n" +
+                "  way[\"amenity\"=\"drinking_water\"](area.searchArea);\n" +
+                "  relation[\"amenity\"=\"drinking_water\"](area.searchArea);\n" +
+                ");\n" +
+                "(._;>;);\n" +
+                "out meta;", query);
+    }
+
+    /**
+     * Tests evaluating the extended query feature {@code geocodeArea}.
+     */
+    @Test
+    public void testGeocodeUnknownArea() {
+        final String query = OverpassTurboQueryWizard.expandExtendedQueries("{{geocodeArea:foo-bar-baz-does-not-exist}}");
+        assertEquals("// Failed to evaluate {{geocodeArea:foo-bar-baz-does-not-exist}}\n", query);
+
+    }
+
+    /**
      * Test erroneous value.
      */
     @Test(expected = UncheckedParseException.class)
-- 
2.9.2

