[geocode-glib] geocode-glib: Add a backend abstraction



commit af4bac3ca2260c3fea047e54c87d82dd946e1832
Author: Philip Withnall <philip withnall collabora co uk>
Date:   Thu Oct 13 11:35:34 2016 +0100

    geocode-glib: Add a backend abstraction
    
    Add an abstraction for backends, the GeocodeBackend interface, and split
    the existing Nominatim code out into an implementation of that,
    GeocodeNominatim. Parameterise this implementation so the default
    instance uses nominatim.gnome.org, but other instances can be created
    which use other Nominatim servers.
    
    Rebase GeocodeForward and GeocodeReverse on GeocodeBackend so that they
    use the new abstraction, and can have their backends changed to use a
    custom GeocodeBackend instance. The default is the global GNOME
    GeocodeNominatim instance.
    
    The GeocodeNominatim class uses the same Nominatim code as before, moved
    from GeocodeForward and GeocodeReverse. In order to be backend-agnostic,
    the parameters accepted by GeocodeBackend methods use the XEP-0080 keys
    and GValues, rather than anything specific to Nominatim, which means that
    some of the parameter-handling code had to be reworked slightly.
    
    GeocodeNominatim delegates its network requests to virtual methods — the
    default implementations of these use libsoup as before, but a follow-up
    commit will add a derived class which tests the Nominatim implementation
    by overriding them to check the generated network requests.
    
    This adds the following new API, but does not break backwards
    compatibility:
     • GeocodeBackend
     • GeocodeNominatim
     • geocode_[forward|reverse]_set_backend()
    
    This requires the recently-bumped GIO dependency of 2.44.
    
    Largely based on a patch by Aleksander Morgado
    <aleksander morgado collabora co uk>.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=756311

 docs/geocode-glib-docs.xml        |    2 +
 geocode-glib/Makefile.am          |    4 +
 geocode-glib/geocode-backend.c    |  348 +++++++++
 geocode-glib/geocode-backend.h    |  127 ++++
 geocode-glib/geocode-forward.c    |  880 +++-------------------
 geocode-glib/geocode-forward.h    |    4 +
 geocode-glib/geocode-glib.h       |    2 +
 geocode-glib/geocode-glib.symbols |   12 +
 geocode-glib/geocode-nominatim.c  | 1458 +++++++++++++++++++++++++++++++++++++
 geocode-glib/geocode-nominatim.h  |   88 +++
 geocode-glib/geocode-reverse.c    |  464 ++----------
 geocode-glib/geocode-reverse.h    |    4 +
 12 files changed, 2243 insertions(+), 1150 deletions(-)
---
diff --git a/docs/geocode-glib-docs.xml b/docs/geocode-glib-docs.xml
index 6b3a5cb..19f25e5 100644
--- a/docs/geocode-glib-docs.xml
+++ b/docs/geocode-glib-docs.xml
@@ -17,9 +17,11 @@
 
   <chapter>
     <title>Geocode-glib</title>
+       <xi:include href="xml/geocode-backend.xml"/>
        <xi:include href="xml/geocode-error.xml"/>
        <xi:include href="xml/geocode-forward.xml"/>
        <xi:include href="xml/geocode-location.xml"/>
+       <xi:include href="xml/geocode-nominatim.xml"/>
        <xi:include href="xml/geocode-place.xml"/>
        <xi:include href="xml/geocode-reverse.xml"/>
        <xi:include href="xml/geocode-bounding-box.xml"/>
diff --git a/geocode-glib/Makefile.am b/geocode-glib/Makefile.am
index 4fef6f7..df5852b 100644
--- a/geocode-glib/Makefile.am
+++ b/geocode-glib/Makefile.am
@@ -7,6 +7,8 @@ BUILT_GIRSOURCES =
 lib_LTLIBRARIES = libgeocode-glib.la
 
 libgeocode_glib_la_PUBLICSOURCES =                     \
+       geocode-backend.c                               \
+       geocode-nominatim.c                             \
        geocode-location.c                              \
        geocode-forward.c                               \
        geocode-reverse.c                               \
@@ -30,6 +32,8 @@ libgeocode_glib_la_LDFLAGS =                          \
 
 GCGLIB_HEADER_FILES = \
        geocode-glib.h                                  \
+       geocode-backend.h                               \
+       geocode-nominatim.h                             \
        geocode-location.h                              \
        geocode-forward.h                               \
        geocode-reverse.h                               \
diff --git a/geocode-glib/geocode-backend.c b/geocode-glib/geocode-backend.c
new file mode 100644
index 0000000..bd26feb
--- /dev/null
+++ b/geocode-glib/geocode-backend.c
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2016 Collabora Ltd.
+ *
+ * The geocode-glib library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The geocode-glib library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301  USA.
+ *
+ * Authors:
+ *     Aleksander Morgado <aleksander morgado collabora co uk>
+ *     Philip Withnall <philip withnall collabora co uk>
+ */
+
+#include "geocode-backend.h"
+
+/**
+ * SECTION:geocode-backend
+ * @short_description: Geocode backend object
+ * @include: geocode-glib/geocode-glib.h
+ *
+ * The #GeocodeBackend interface defines the operations that a resolver
+ * service must implement.
+ *
+ * Since: UNRELEASED
+ */
+
+G_DEFINE_INTERFACE (GeocodeBackend, geocode_backend, G_TYPE_OBJECT)
+
+/**
+ * geocode_backend_forward_search_async:
+ * @backend: a #GeocodeBackend.
+ * @params: (transfer none) (element-type utf8 GValue): a #GHashTable with string keys, and #GValue values.
+ * @cancellable: optional #GCancellable, %NULL to ignore.
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: the data to pass to the @callback function
+ *
+ * Asynchronously performs a forward geocoding query using the @backend. Use
+ * geocode_backend_forward_search() to do the same thing synchronously.
+ *
+ * The @params hash table is in the format used by Telepathy, and documented
+ * in the [Telepathy 
specification](http://telepathy.freedesktop.org/spec/Connection_Interface_Location.html#Mapping:Location).
+ *
+ * See also: [XEP-0080 specification](http://xmpp.org/extensions/xep-0080.html).
+ *
+ * When the operation is finished, @callback will be called. You can then call
+ * geocode_backend_forward_search_finish() to get the result of the operation.
+ *
+ * Since: UNRELEASED
+ */
+void
+geocode_backend_forward_search_async (GeocodeBackend      *backend,
+                                      GHashTable          *params,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+       GeocodeBackendInterface *iface;
+
+       g_return_if_fail (GEOCODE_IS_BACKEND (backend));
+       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+       iface = GEOCODE_BACKEND_GET_IFACE (backend);
+
+       return iface->forward_search_async (backend, params, cancellable,
+                                           callback, user_data);
+}
+
+/**
+ * geocode_backend_forward_search_finish:
+ * @backend: a #GeocodeBackend.
+ * @result: a #GAsyncResult.
+ * @error: a #GError.
+ *
+ * Finishes a forward geocoding operation. See
+ * geocode_backend_forward_search_async().
+ *
+ * Returns: (element-type GeocodePlace) (transfer container): A list of
+ * places or %NULL in case of errors. Free the returned list with
+ * g_list_free() when done.
+ *
+ * Since: UNRELEASED
+ */
+GList *
+geocode_backend_forward_search_finish (GeocodeBackend  *backend,
+                                       GAsyncResult    *result,
+                                       GError         **error)
+{
+       GeocodeBackendInterface *iface;
+
+       g_return_val_if_fail (GEOCODE_IS_BACKEND (backend), NULL);
+       g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+       iface = GEOCODE_BACKEND_GET_IFACE (backend);
+
+       return iface->forward_search_finish (backend, result, error);
+}
+
+/**
+ * geocode_backend_forward_search:
+ * @backend: a #GeocodeBackend.
+ * @params: (transfer none) (element-type utf8 GValue): a #GHashTable with string keys, and #GValue values.
+ * @cancellable: optional #GCancellable, %NULL to ignore.
+ * @error: a #GError
+ *
+ * Gets the result of a forward geocoding query using the @backend.
+ *
+ * This is a synchronous function, which means it may block on network requests.
+ * In most situations, the asynchronous version
+ * (geocode_backend_forward_search_async()) is more appropriate. See its
+ * documentation for more information on usage.
+ *
+ * Returns: (element-type GeocodePlace) (transfer container): A list of
+ * places or %NULL in case of errors. Free the returned list with
+ * g_list_free() when done.
+ *
+ * Since: UNRELEASED
+ */
+GList *
+geocode_backend_forward_search (GeocodeBackend  *backend,
+                                GHashTable      *params,
+                                GCancellable    *cancellable,
+                                GError         **error)
+{
+       GeocodeBackendInterface *iface;
+
+       g_return_val_if_fail (GEOCODE_IS_BACKEND (backend), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+       iface = GEOCODE_BACKEND_GET_IFACE (backend);
+
+       return iface->forward_search (backend, params, cancellable, error);
+}
+
+/**
+ * geocode_backend_reverse_resolve_async:
+ * @backend: a #GeocodeBackend.
+ * @location: a #GeocodeLocation.
+ * @cancellable: optional #GCancellable object, %NULL to ignore.
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: the data to pass to callback function.
+ *
+ * Asynchronously gets the result of a reverse geocoding query using the
+ * backend. Use geocode_backend_reverse_resolve() to do the same thing
+ * synchronously.
+ *
+ * When the operation is finished, @callback will be called. You can then call
+ * geocode_backend_reverse_resolve_finish() to get the result of the operation.
+ *
+ * Since: UNRELEASED
+ */
+void
+geocode_backend_reverse_resolve_async (GeocodeBackend      *backend,
+                                       GeocodeLocation     *location,
+                                       GCancellable        *cancellable,
+                                       GAsyncReadyCallback  callback,
+                                       gpointer             user_data)
+{
+       GeocodeBackendInterface *iface;
+
+       g_return_if_fail (GEOCODE_IS_BACKEND (backend));
+       g_return_if_fail (GEOCODE_IS_LOCATION (location));
+       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+       iface = GEOCODE_BACKEND_GET_IFACE (backend);
+
+       return (* iface->reverse_resolve_async) (backend, location,
+                                                cancellable, callback, user_data);
+}
+
+/**
+ * geocode_backend_reverse_resolve_finish:
+ * @backend: a #GeocodeBackend.
+ * @result: a #GAsyncResult.
+ * @error: a #GError.
+ *
+ * Finishes a reverse geocoding operation. See geocode_backend_reverse_resolve_async().
+ *
+ * Returns: (transfer full): A #GeocodePlace instance, or %NULL in case of
+ * errors. Free the returned instance with g_object_unref() when done.
+ *
+ * Since: UNRELEASED
+ **/
+GeocodePlace *
+geocode_backend_reverse_resolve_finish (GeocodeBackend  *backend,
+                                        GAsyncResult    *result,
+                                        GError         **error)
+{
+       GeocodeBackendInterface *iface;
+
+       g_return_val_if_fail (GEOCODE_IS_BACKEND (backend), NULL);
+       g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+       iface = GEOCODE_BACKEND_GET_IFACE (backend);
+
+       return (* iface->reverse_resolve_finish) (backend, result, error);
+}
+
+/**
+ * geocode_backend_reverse_resolve:
+ * @backend: a #GeocodeBackend.
+ * @location: a #GeocodeLocation.
+ * @cancellable: optional #GCancellable object, %NULL to ignore.
+ * @error: a #GError.
+ *
+ * Gets the result of a reverse geocoding query using the @backend.
+ *
+ * Returns: (transfer full): A #GeocodePlace instance, or %NULL in case of
+ * errors. Free the returned instance with g_object_unref() when done.
+ *
+ * Since: UNRELEASED
+ */
+GeocodePlace *
+geocode_backend_reverse_resolve (GeocodeBackend   *backend,
+                                 GeocodeLocation  *location,
+                                 GCancellable     *cancellable,
+                                 GError          **error)
+{
+       GeocodeBackendInterface *iface;
+
+       g_return_val_if_fail (GEOCODE_IS_BACKEND (backend), NULL);
+       g_return_val_if_fail (GEOCODE_IS_LOCATION (location), NULL);
+       g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return NULL;
+
+       iface = GEOCODE_BACKEND_GET_IFACE (backend);
+
+       if (iface->reverse_resolve == NULL) {
+               g_set_error_literal (error, G_IO_ERROR,
+                                    G_IO_ERROR_NOT_SUPPORTED,
+                                    "Operation not supported");
+               return NULL;
+       }
+
+       return (* iface->reverse_resolve) (backend, location,
+                                          cancellable, error);
+}
+
+/* Free a GList of GeocodePlace objects. */
+static void
+places_list_free (GList *places)
+{
+       g_list_free_full (places, g_object_unref);
+}
+
+static void
+forward_search_async_thread (GTask           *task,
+                             GeocodeBackend  *backend,
+                             GHashTable      *params,
+                             GCancellable    *cancellable)
+{
+       GError *error = NULL;
+       GList *places;
+
+       places = geocode_backend_forward_search (backend, params,
+                                                cancellable, &error);
+       if (error)
+               g_task_return_error (task, error);
+       else
+               g_task_return_pointer (task, places, (GDestroyNotify) places_list_free);
+}
+
+static void
+real_forward_search_async (GeocodeBackend       *backend,
+                           GHashTable           *params,
+                           GCancellable         *cancellable,
+                           GAsyncReadyCallback   callback,
+                           gpointer              user_data)
+{
+       GTask *task;
+
+       task = g_task_new (backend, cancellable, callback, user_data);
+       g_task_set_task_data (task, g_hash_table_ref (params),
+                             (GDestroyNotify) g_hash_table_unref);
+       g_task_run_in_thread (task, (GTaskThreadFunc) forward_search_async_thread);
+       g_object_unref (task);
+}
+
+static GList *
+real_forward_search_finish (GeocodeBackend  *backend,
+                            GAsyncResult    *result,
+                            GError         **error)
+{
+       return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+reverse_resolve_async_thread (GTask           *task,
+                              GeocodeBackend  *backend,
+                              GeocodeLocation *location,
+                              GCancellable    *cancellable)
+{
+       GError *error = NULL;
+       GeocodePlace *place;
+
+       place = geocode_backend_reverse_resolve (backend, location,
+                                                cancellable, &error);
+       if (error)
+               g_task_return_error (task, error);
+       else
+               g_task_return_pointer (task, place, g_object_unref);
+}
+
+static void
+real_reverse_resolve_async (GeocodeBackend      *backend,
+                            GeocodeLocation     *location,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+       GTask *task;
+
+       task = g_task_new (backend, cancellable, callback, user_data);
+       g_task_set_task_data (task, g_object_ref (location), g_object_unref);
+       g_task_run_in_thread (task, (GTaskThreadFunc) reverse_resolve_async_thread);
+       g_object_unref (task);
+}
+
+static GeocodePlace *
+real_reverse_resolve_finish (GeocodeBackend  *backend,
+                             GAsyncResult    *result,
+                             GError         **error)
+{
+       return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+geocode_backend_default_init (GeocodeBackendInterface *iface)
+{
+       iface->forward_search_async  = real_forward_search_async;
+       iface->forward_search_finish = real_forward_search_finish;
+       iface->reverse_resolve_async  = real_reverse_resolve_async;
+       iface->reverse_resolve_finish = real_reverse_resolve_finish;
+}
diff --git a/geocode-glib/geocode-backend.h b/geocode-glib/geocode-backend.h
new file mode 100644
index 0000000..98d2443
--- /dev/null
+++ b/geocode-glib/geocode-backend.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 Collabora Ltd.
+ *
+ * The geocode-glib library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The geocode-glib library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301  USA.
+ *
+ * Authors:
+ *     Aleksander Morgado <aleksander morgado collabora co uk>
+ *     Philip Withnall <philip withnall collabora co uk>
+ */
+
+#ifndef GEOCODE_BACKEND_H
+#define GEOCODE_BACKEND_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#include "geocode-place.h"
+
+G_BEGIN_DECLS
+
+/**
+ * GeocodeBackend:
+ *
+ * All the fields in the #GeocodeBackend structure are private and should
+ * never be accessed directly.
+ *
+ * Since: UNRELEASED
+ */
+#define GEOCODE_TYPE_BACKEND (geocode_backend_get_type ())
+G_DECLARE_INTERFACE (GeocodeBackend, geocode_backend, GEOCODE, BACKEND, GObject)
+
+/**
+ * GeocodeBackendInterface:
+ * @forward_search: handles a synchronous forward geocoding request.
+ * @forward_search_async: starts an asynchronous forward geocoding request.
+ * @forward_search_finish: finishes an asynchronous forward geocoding request.
+ * @reverse_resolve: handles a synchronous reverse geocoding request.
+ * @reverse_resolve_async: starts an asynchronous reverse geocoding request.
+ * @reverse_resolve_finish: finishes an asynchronous reverse geocoding request.
+ *
+ * Interface which defines the basic operations for geocoding.
+ *
+ * Since: UNRELEASED
+ */
+struct _GeocodeBackendInterface
+{
+       /*< private >*/
+       GTypeInterface g_iface;
+
+       /*< public >*/
+
+       /* Forward */
+       GList        *(*forward_search)          (GeocodeBackend       *backend,
+                                                 GHashTable           *params,
+                                                 GCancellable         *cancellable,
+                                                 GError              **error);
+       void          (*forward_search_async)    (GeocodeBackend       *backend,
+                                                 GHashTable           *params,
+                                                 GCancellable         *cancellable,
+                                                 GAsyncReadyCallback   callback,
+                                                 gpointer              user_data);
+       GList        *(*forward_search_finish)   (GeocodeBackend       *backend,
+                                                 GAsyncResult         *result,
+                                                 GError              **error);
+
+       /* Reverse */
+       GeocodePlace *(*reverse_resolve)         (GeocodeBackend       *backend,
+                                                 GeocodeLocation      *location,
+                                                 GCancellable         *cancellable,
+                                                 GError              **error);
+       void          (*reverse_resolve_async)   (GeocodeBackend       *backend,
+                                                 GeocodeLocation      *location,
+                                                 GCancellable         *cancellable,
+                                                 GAsyncReadyCallback   callback,
+                                                 gpointer              user_data);
+       GeocodePlace *(*reverse_resolve_finish)  (GeocodeBackend       *backend,
+                                                 GAsyncResult         *result,
+                                                 GError              **error);
+
+       /*< private >*/
+       gpointer padding[4];
+};
+
+/* Forward geocoding operations */
+void          geocode_backend_forward_search_async   (GeocodeBackend      *backend,
+                                                      GHashTable          *params,
+                                                      GCancellable        *cancellable,
+                                                      GAsyncReadyCallback  callback,
+                                                      gpointer             user_data);
+GList        *geocode_backend_forward_search_finish  (GeocodeBackend      *backend,
+                                                      GAsyncResult        *result,
+                                                      GError             **error);
+GList        *geocode_backend_forward_search         (GeocodeBackend      *backend,
+                                                      GHashTable          *params,
+                                                      GCancellable        *cancellable,
+                                                      GError             **error);
+
+/* Reverse geocoding operations */
+void          geocode_backend_reverse_resolve_async  (GeocodeBackend       *backend,
+                                                      GeocodeLocation      *location,
+                                                      GCancellable         *cancellable,
+                                                      GAsyncReadyCallback   callback,
+                                                      gpointer              user_data);
+GeocodePlace *geocode_backend_reverse_resolve_finish (GeocodeBackend       *backend,
+                                                      GAsyncResult         *result,
+                                                      GError              **error);
+GeocodePlace *geocode_backend_reverse_resolve        (GeocodeBackend       *backend,
+                                                      GeocodeLocation      *location,
+                                                      GCancellable         *cancellable,
+                                                      GError              **error);
+
+G_END_DECLS
+
+#endif /* GEOCODE_BACKEND_H */
diff --git a/geocode-glib/geocode-forward.c b/geocode-glib/geocode-forward.c
index 7e00ae5..abeb3e6 100644
--- a/geocode-glib/geocode-forward.c
+++ b/geocode-glib/geocode-forward.c
@@ -1,5 +1,6 @@
 /*
    Copyright (C) 2011 Bastien Nocera
+   Copyright (C) 2016 Collabora Ltd.
 
    The Gnome Library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public License as
@@ -17,19 +18,19 @@
    Boston, MA 02110-1301  USA.
 
    Authors: Bastien Nocera <hadess hadess net>
-
+            Philip Withnall <philip withnall collabora co uk>
  */
 
 #include <string.h>
 #include <stdlib.h>
 #include <locale.h>
 #include <gio/gio.h>
-#include <json-glib/json-glib.h>
-#include <libsoup/soup.h>
+#include <geocode-glib/geocode-backend.h>
 #include <geocode-glib/geocode-forward.h>
 #include <geocode-glib/geocode-bounding-box.h>
 #include <geocode-glib/geocode-error.h>
 #include <geocode-glib/geocode-glib-private.h>
+#include <geocode-glib/geocode-nominatim.h>
 
 /**
  * SECTION:geocode-forward
@@ -42,10 +43,11 @@
 
 struct _GeocodeForwardPrivate {
        GHashTable *ht;
-        SoupSession *soup_session;
        guint       answer_count;
        GeocodeBoundingBox *search_area;
        gboolean bounded;
+
+       GeocodeBackend  *backend;
 };
 
 enum {
@@ -120,18 +122,13 @@ geocode_forward_set_property(GObject         *object,
        }
 }
 
-
-static void geocode_forward_add (GeocodeForward *forward,
-                                const char     *key,
-                                const char     *value);
-
 static void
 geocode_forward_finalize (GObject *gforward)
 {
        GeocodeForward *forward = (GeocodeForward *) gforward;
 
-       g_clear_pointer (&forward->priv->ht, g_hash_table_destroy);
-        g_clear_object (&forward->priv->soup_session);
+       g_clear_pointer (&forward->priv->ht, g_hash_table_unref);
+       g_clear_object (&forward->priv->backend);
 
        G_OBJECT_CLASS (geocode_forward_parent_class)->finalize (gforward);
 }
@@ -197,84 +194,30 @@ geocode_forward_class_init (GeocodeForwardClass *klass)
 }
 
 static void
+free_value (GValue *value)
+{
+       g_value_unset (value);
+       g_free (value);
+}
+
+static void
 geocode_forward_init (GeocodeForward *forward)
 {
        forward->priv = G_TYPE_INSTANCE_GET_PRIVATE ((forward), GEOCODE_TYPE_FORWARD, GeocodeForwardPrivate);
        forward->priv->ht = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                                  g_free, g_free);
-       forward->priv->soup_session = _geocode_glib_build_soup_session ();
+                                                  g_free,
+                                                  (GDestroyNotify) free_value);
        forward->priv->answer_count = DEFAULT_ANSWER_COUNT;
        forward->priv->search_area = NULL;
        forward->priv->bounded = FALSE;
 }
 
-static struct {
-       const char *tp_attr;
-       const char *gc_attr; /* NULL to ignore */
-} attrs_map[] = {
-       { "countrycode", NULL },
-       { "country", "country" },
-       { "region", "state" },
-       { "county", "county" },
-       { "locality", "city" },
-       { "area", NULL },
-       { "postalcode", "postalcode" },
-       { "street", "street" },
-       { "building", NULL },
-       { "floor", NULL },
-       { "room",  NULL },
-       { "text", NULL },
-       { "description", NULL },
-       { "uri", NULL },
-       { "language", "accept-language" },
-};
-
-static const char *
-tp_attr_to_gc_attr (const char *attr,
-                   gboolean   *found)
-{
-       guint i;
-
-       *found = FALSE;
-
-       for (i = 0; i < G_N_ELEMENTS (attrs_map); i++) {
-               if (g_str_equal (attr, attrs_map[i].tp_attr)){
-                       *found = TRUE;
-                       return attrs_map[i].gc_attr;
-               }
-       }
-
-       return NULL;
-}
-
 static void
-geocode_forward_fill_params (GeocodeForward *forward,
-                            GHashTable    *params)
+ensure_backend (GeocodeForward *object)
 {
-       GHashTableIter iter;
-       GValue *value;
-       const char *key;
-
-       g_hash_table_iter_init (&iter, params);
-       while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) {
-               gboolean found;
-               const char *gc_attr;
-               const char *str;
-
-               gc_attr = tp_attr_to_gc_attr (key, &found);
-               if (found == FALSE) {
-                       g_warning ("XEP attribute '%s' unhandled", key);
-                       continue;
-               }
-               if (gc_attr == NULL)
-                       continue;
-
-               str = g_value_get_string (value);
-               if (str == NULL)
-                       continue;
-
-               geocode_forward_add (forward, gc_attr, str);
-       }
+       /* If no backend is specified, default to the GNOME Nominatim backend */
+       if (object->priv->backend == NULL)
+               object->priv->backend = GEOCODE_BACKEND (geocode_nominatim_get_gnome ());
 }
 
 /**
@@ -293,6 +236,9 @@ GeocodeForward *
 geocode_forward_new_for_params (GHashTable *params)
 {
        GeocodeForward *forward;
+       GHashTableIter iter;
+       const gchar *key;
+       const GValue *value;
 
        g_return_val_if_fail (params != NULL, NULL);
 
@@ -302,7 +248,15 @@ geocode_forward_new_for_params (GHashTable *params)
        }
 
        forward = g_object_new (GEOCODE_TYPE_FORWARD, NULL);
-       geocode_forward_fill_params (forward, params);
+
+       g_hash_table_iter_init (&iter, params);
+
+       while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) {
+               GValue *value_copy = g_new0 (GValue, 1);
+               g_value_init (value_copy, G_VALUE_TYPE (value));
+               g_value_copy (value, value_copy);
+               g_hash_table_insert (forward->priv->ht, g_strdup (key), value_copy);
+       }
 
        return forward;
 }
@@ -320,193 +274,35 @@ GeocodeForward *
 geocode_forward_new_for_string (const char *location)
 {
        GeocodeForward *forward;
+       GValue *location_value;
 
        g_return_val_if_fail (location != NULL, NULL);
 
        forward = g_object_new (GEOCODE_TYPE_FORWARD, NULL);
-       geocode_forward_add (forward, "location", location);
-
-       return forward;
-}
 
-static void
-geocode_forward_add (GeocodeForward *forward,
-                    const char    *key,
-                    const char    *value)
-{
-       g_return_if_fail (GEOCODE_IS_FORWARD (forward));
-       g_return_if_fail (key != NULL);
-       g_return_if_fail (value == NULL || g_utf8_validate (value, -1, NULL));
+       location_value = g_new0 (GValue, 1);
+       g_value_init (location_value, G_TYPE_STRING);
+       g_value_set_string (location_value, location);
+       g_hash_table_insert (forward->priv->ht, g_strdup ("location"),
+                            location_value);
 
-       g_hash_table_insert (forward->priv->ht,
-                            g_strdup (key),
-                            g_strdup (value));
-}
-
-static void
-on_query_data_loaded (SoupSession *session,
-                      SoupMessage *query,
-                      gpointer     user_data)
-{
-       GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
-       GError *error = NULL;
-       char *contents;
-       gpointer ret;
-
-        if (query->status_code != SOUP_STATUS_OK) {
-               g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                                     query->reason_phrase ? query->reason_phrase : "Query failed");
-                g_simple_async_result_take_error (simple, error);
-               g_simple_async_result_complete_in_idle (simple);
-               g_object_unref (simple);
-               return;
-       }
-
-        contents = g_strndup (query->response_body->data, query->response_body->length);
-        ret = _geocode_parse_search_json (contents, &error);
-
-       if (ret == NULL) {
-               g_simple_async_result_take_error (simple, error);
-               g_simple_async_result_complete_in_idle (simple);
-               g_object_unref (simple);
-               g_free (contents);
-               return;
-       }
-
-       /* Now that we can parse the result, save it to cache */
-       _geocode_glib_cache_save (query, contents);
-       g_free (contents);
-
-       g_simple_async_result_set_op_res_gpointer (simple, ret, NULL);
-       g_simple_async_result_complete_in_idle (simple);
-       g_object_unref (simple);
+       return forward;
 }
 
 static void
-on_cache_data_loaded (GObject      *source_forward,
-                     GAsyncResult *res,
-                     gpointer      user_data)
+backend_forward_search_ready (GeocodeBackend *backend,
+                              GAsyncResult   *res,
+                              GTask          *task)
 {
-       GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
-       GFile *cache;
+       GList *places;  /* (element-type GeocodePlace) */
        GError *error = NULL;
-       char *contents;
-       gpointer ret;
-
-       cache = G_FILE (source_forward);
-       if (g_file_load_contents_finish (cache,
-                                        res,
-                                        &contents,
-                                        NULL,
-                                        NULL,
-                                        NULL) == FALSE) {
-                GObject *object;
-                SoupMessage *query;
-
-                object = g_async_result_get_source_object (G_ASYNC_RESULT (simple));
-               query = g_object_get_data (G_OBJECT (cache), "query");
-                g_object_ref (query); /* soup_session_queue_message steals ref */
-               soup_session_queue_message (GEOCODE_FORWARD (object)->priv->soup_session,
-                                            query,
-                                           on_query_data_loaded,
-                                           simple);
-               return;
-       }
-
-        ret = _geocode_parse_search_json (contents, &error);
-       g_free (contents);
 
-       if (ret == NULL)
-               g_simple_async_result_take_error (simple, error);
+       places = geocode_backend_forward_search_finish (backend, res, &error);
+       if (places != NULL)
+               g_task_return_pointer (task, places, (GDestroyNotify) g_list_free);
        else
-               g_simple_async_result_set_op_res_gpointer (simple, ret, NULL);
-
-       g_simple_async_result_complete_in_idle (simple);
-       g_object_unref (simple);
-}
-
-static SoupMessage *
-get_search_query_for_params (GeocodeForward *forward,
-                            GError        **error)
-{
-       SoupMessage *ret;
-       GHashTable *ht;
-       char *lang;
-       char *params;
-       char *search_term;
-       char *uri;
-        guint8 i;
-        gboolean query_possible = FALSE;
-        char *location;
-        const char *allowed_attributes[] = { "country",
-                                             "region",
-                                             "county",
-                                             "locality",
-                                             "postalcode",
-                                             "street",
-                                             "location",
-                                             NULL };
-
-        /* Make sure we have at least one parameter that Nominatim allows querying for */
-       for (i = 0; allowed_attributes[i] != NULL; i++) {
-               if (g_hash_table_lookup (forward->priv->ht,
-                                         allowed_attributes[i]) != NULL) {
-                       query_possible = TRUE;
-                       break;
-               }
-       }
-
-        if (!query_possible) {
-                char *str;
-
-                str = g_strjoinv (", ", (char **) allowed_attributes);
-                g_set_error (error, GEOCODE_ERROR, GEOCODE_ERROR_INVALID_ARGUMENTS,
-                             "Only following parameters supported: %s", str);
-                g_free (str);
-
-               return NULL;
-       }
-
-       /* Prepare the query parameters */
-       ht = _geocode_glib_dup_hash_table (forward->priv->ht);
-       g_hash_table_insert (ht, (gpointer) "format", (gpointer) "jsonv2");
-       g_hash_table_insert (ht, (gpointer) "email", (gpointer) "zeeshanak gnome org");
-       g_hash_table_insert (ht, (gpointer) "addressdetails", (gpointer) "1");
-
-       lang = NULL;
-       if (g_hash_table_lookup (ht, "accept-language") == NULL) {
-               lang = _geocode_object_get_lang ();
-               if (lang)
-                       g_hash_table_insert (ht, (gpointer) "accept-language", lang);
-       }
-
-        location = g_strdup (g_hash_table_lookup (ht, "location"));
-        g_hash_table_remove (ht, "location");
-       params = soup_form_encode_hash (ht);
-       g_hash_table_destroy (ht);
-        if (lang)
-                g_free (lang);
-
-        if (location != NULL) {
-               /* Prepare the search term */
-                search_term = soup_uri_encode (location, NULL);
-                uri = g_strdup_printf ("https://nominatim.gnome.org/search?q=%s&limit=%u&bounded=%d&%s";,
-                                       search_term,
-                                       forward->priv->answer_count,
-                                       !!forward->priv->bounded,
-                                       params);
-                g_free (search_term);
-                g_free (location);
-        } else {
-                uri = g_strdup_printf ("https://nominatim.gnome.org/search?limit=1&%s";,
-                                       params);
-        }
-       g_free (params);
-
-       ret = soup_message_new ("GET", uri);
-       g_free (uri);
-
-       return ret;
+               g_task_return_error (task, error);
+       g_object_unref (task);
 }
 
 /**
@@ -529,45 +325,21 @@ geocode_forward_search_async (GeocodeForward      *forward,
                              GAsyncReadyCallback  callback,
                              gpointer             user_data)
 {
-       GSimpleAsyncResult *simple;
-       SoupMessage *query;
-       char *cache_path;
-       GError *error = NULL;
+       GTask *task;
 
        g_return_if_fail (GEOCODE_IS_FORWARD (forward));
-
-       simple = g_simple_async_result_new (G_OBJECT (forward),
-                                           callback,
-                                           user_data,
-                                           geocode_forward_search_async);
-       g_simple_async_result_set_check_cancellable (simple, cancellable);
-
-        query = get_search_query_for_params (forward, &error);
-       if (!query) {
-               g_simple_async_result_take_error (simple, error);
-               g_simple_async_result_complete_in_idle (simple);
-               g_object_unref (simple);
-               return;
-       }
-
-       cache_path = _geocode_glib_cache_path_for_query (query);
-       if (cache_path == NULL) {
-               soup_session_queue_message (forward->priv->soup_session,
-                                            query,
-                                           on_query_data_loaded,
-                                           simple);
-       } else {
-               GFile *cache;
-
-               cache = g_file_new_for_path (cache_path);
-               g_object_set_data_full (G_OBJECT (cache), "query", query, (GDestroyNotify) g_object_unref);
-               g_file_load_contents_async (cache,
-                                           cancellable,
-                                           on_cache_data_loaded,
-                                           simple);
-               g_object_unref (cache);
-               g_free (cache_path);
-       }
+       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+       ensure_backend (forward);
+       g_assert (forward->priv->backend != NULL);
+
+       task = g_task_new (forward, cancellable, callback, user_data);
+       geocode_backend_forward_search_async (forward->priv->backend,
+                                             forward->priv->ht,
+                                             cancellable,
+                                             (GAsyncReadyCallback) backend_forward_search_ready,
+                                             g_object_ref (task));
+       g_object_unref (task);
 }
 
 /**
@@ -587,445 +359,11 @@ geocode_forward_search_finish (GeocodeForward       *forward,
                               GAsyncResult        *res,
                               GError             **error)
 {
-       GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
-
        g_return_val_if_fail (GEOCODE_IS_FORWARD (forward), NULL);
+       g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-       g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == geocode_forward_search_async);
-
-       if (g_simple_async_result_propagate_error (simple, error))
-               return NULL;
-
-       return g_simple_async_result_get_op_res_gpointer (simple);
-}
-
-static struct {
-       const char *nominatim_attr;
-        const char *place_prop; /* NULL to ignore */
-} nominatim_to_place_map[] = {
-        { "license", NULL },
-        { "osm_id", "osm-id" },
-        { "lat", NULL },
-        { "lon", NULL },
-        { "display_name", NULL },
-        { "house_number", "building" },
-        { "road", "street" },
-        { "suburb", "area" },
-        { "city",  "town" },
-        { "village",  "town" },
-        { "county", "county" },
-        { "state_district", "administrative-area" },
-        { "state", "state" },
-        { "postcode", "postal-code" },
-        { "country", "country" },
-        { "country_code", "country-code" },
-        { "continent", "continent" },
-        { "address", NULL },
-};
-
-static void
-fill_place_from_entry (const char   *key,
-                       const char   *value,
-                       GeocodePlace *place)
-{
-        guint i;
-
-        for (i = 0; i < G_N_ELEMENTS (nominatim_to_place_map); i++) {
-                if (g_str_equal (key, nominatim_to_place_map[i].nominatim_attr)){
-                        g_object_set (G_OBJECT (place),
-                                      nominatim_to_place_map[i].place_prop,
-                                      value,
-                                      NULL);
-                        break;
-                }
-        }
-
-        if (g_str_equal (key, "osm_type")) {
-                gpointer ref = g_type_class_ref (geocode_place_osm_type_get_type ());
-                GEnumClass *class = G_ENUM_CLASS (ref);
-                GEnumValue *evalue = g_enum_get_value_by_nick (class, value);
-
-                if (evalue)
-                        g_object_set (G_OBJECT (place), "osm-type", evalue->value, NULL);
-                else
-                        g_warning ("Unsupported osm-type %s", value);
-
-                g_type_class_unref (ref);
-        }
-}
-
-static gboolean
-node_free_func (GNode    *node,
-               gpointer  user_data)
-{
-       /* Leaf nodes are GeocodeLocation objects
-        * which we reuse for the results */
-       if (G_NODE_IS_LEAF (node) == FALSE)
-               g_free (node->data);
-
-       return FALSE;
-}
-
-static const char const *attributes[] = {
-       "country",
-       "state",
-       "county",
-       "state_district",
-       "postcode",
-       "city",
-       "suburb",
-       "village",
-};
-
-static GeocodePlaceType
-get_place_type_from_attributes (GHashTable *ht)
-{
-        char *category, *type;
-        GeocodePlaceType place_type = GEOCODE_PLACE_TYPE_UNKNOWN;
-
-        category = g_hash_table_lookup (ht, "category");
-        type = g_hash_table_lookup (ht, "type");
-
-        if (g_strcmp0 (category, "place") == 0) {
-                if (g_strcmp0 (type, "house") == 0 ||
-                    g_strcmp0 (type, "building") == 0 ||
-                    g_strcmp0 (type, "residential") == 0 ||
-                    g_strcmp0 (type, "plaza") == 0 ||
-                    g_strcmp0 (type, "office") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_BUILDING;
-                else if (g_strcmp0 (type, "estate") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_ESTATE;
-                else if (g_strcmp0 (type, "town") == 0 ||
-                         g_strcmp0 (type, "city") == 0 ||
-                         g_strcmp0 (type, "hamlet") == 0 ||
-                         g_strcmp0 (type, "isolated_dwelling") == 0 ||
-                         g_strcmp0 (type, "village") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_TOWN;
-                else if (g_strcmp0 (type, "suburb") == 0 ||
-                         g_strcmp0 (type, "neighbourhood") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_SUBURB;
-                else if (g_strcmp0 (type, "state") == 0 ||
-                         g_strcmp0 (type, "region") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_STATE;
-                else if (g_strcmp0 (type, "farm") == 0 ||
-                         g_strcmp0 (type, "forest") == 0 ||
-                         g_strcmp0 (type, "valey") == 0 ||
-                         g_strcmp0 (type, "park") == 0 ||
-                         g_strcmp0 (type, "hill") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_LAND_FEATURE;
-                else if (g_strcmp0 (type, "island") == 0 ||
-                         g_strcmp0 (type, "islet") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_ISLAND;
-                else if (g_strcmp0 (type, "country") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_COUNTRY;
-                else if (g_strcmp0 (type, "continent") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_CONTINENT;
-                else if (g_strcmp0 (type, "lake") == 0 ||
-                         g_strcmp0 (type, "bay") == 0 ||
-                         g_strcmp0 (type, "river") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_DRAINAGE;
-                else if (g_strcmp0 (type, "sea") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_SEA;
-                else if (g_strcmp0 (type, "ocean") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_OCEAN;
-        } else if (g_strcmp0 (category, "highway") == 0) {
-                if (g_strcmp0 (type, "motorway") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_MOTORWAY;
-                else if (g_strcmp0 (type, "bus_stop") == 0)
-                        place_type =  GEOCODE_PLACE_TYPE_BUS_STOP;
-                else
-                        place_type =  GEOCODE_PLACE_TYPE_STREET;
-        } else if (g_strcmp0 (category, "railway") == 0) {
-                if (g_strcmp0 (type, "station") == 0 ||
-                    g_strcmp0 (type, "halt") == 0)
-                        place_type = GEOCODE_PLACE_TYPE_RAILWAY_STATION;
-                else if (g_strcmp0 (type, "tram_stop") == 0)
-                        place_type = GEOCODE_PLACE_TYPE_LIGHT_RAIL_STATION;
-        } else if (g_strcmp0 (category, "waterway") == 0) {
-                place_type =  GEOCODE_PLACE_TYPE_DRAINAGE;
-        } else if (g_strcmp0 (category, "boundary") == 0) {
-                if (g_strcmp0 (type, "administrative") == 0) {
-                        int rank;
-
-                        rank = atoi (g_hash_table_lookup (ht, "place_rank"));
-                        if (rank < 2)
-                                place_type =  GEOCODE_PLACE_TYPE_UNKNOWN;
-
-                        if (rank == 28)
-                                place_type =  GEOCODE_PLACE_TYPE_BUILDING;
-                        else if (rank == 16)
-                                place_type =  GEOCODE_PLACE_TYPE_TOWN;
-                        else if (rank == 12)
-                                place_type =  GEOCODE_PLACE_TYPE_COUNTY;
-                        else if (rank == 10 || rank == 8)
-                                place_type =  GEOCODE_PLACE_TYPE_STATE;
-                        else if (rank == 4)
-                                place_type =  GEOCODE_PLACE_TYPE_COUNTRY;
-                }
-        } else if (g_strcmp0 (category, "amenity") == 0) {
-                if (g_strcmp0 (type, "school") == 0)
-                        place_type = GEOCODE_PLACE_TYPE_SCHOOL;
-                else if (g_strcmp0 (type, "place_of_worship") == 0)
-                        place_type = GEOCODE_PLACE_TYPE_PLACE_OF_WORSHIP;
-                else if (g_strcmp0 (type, "restaurant") == 0)
-                        place_type = GEOCODE_PLACE_TYPE_RESTAURANT;
-                else if (g_strcmp0 (type, "bar") == 0 ||
-                         g_strcmp0 (type, "pub") == 0)
-                        place_type = GEOCODE_PLACE_TYPE_BAR;
-        } else if (g_strcmp0 (category, "aeroway") == 0) {
-                if (g_strcmp0 (type, "aerodrome") == 0)
-                        place_type = GEOCODE_PLACE_TYPE_AIRPORT;
-        }
-
-        return place_type;
-}
-
-GeocodePlace *
-_geocode_create_place_from_attributes (GHashTable *ht)
-{
-        GeocodePlace *place;
-        GeocodeLocation *loc = NULL;
-        const char *name, *street, *building, *bbox_corner;
-        GeocodePlaceType place_type;
-        gdouble longitude, latitude;
-
-        place_type = get_place_type_from_attributes (ht);
-
-        name = g_hash_table_lookup (ht, "name");
-        if (name == NULL)
-                name = g_hash_table_lookup (ht, "display_name");
-
-        place = geocode_place_new (name, place_type);
-
-        /* If one corner exists, then all exists */
-        bbox_corner = g_hash_table_lookup (ht, "boundingbox-top");
-        if (bbox_corner != NULL) {
-            GeocodeBoundingBox *bbox;
-            gdouble top, bottom, left, right;
-
-            top = g_ascii_strtod (bbox_corner, NULL);
-
-            bbox_corner = g_hash_table_lookup (ht, "boundingbox-bottom");
-            bottom = g_ascii_strtod (bbox_corner, NULL);
-
-            bbox_corner = g_hash_table_lookup (ht, "boundingbox-left");
-            left = g_ascii_strtod (bbox_corner, NULL);
-
-            bbox_corner = g_hash_table_lookup (ht, "boundingbox-right");
-            right = g_ascii_strtod (bbox_corner, NULL);
-
-            bbox = geocode_bounding_box_new (top, bottom, left, right);
-            geocode_place_set_bounding_box (place, bbox);
-            g_object_unref (bbox);
-        }
-
-        /* Nominatim doesn't give us street addresses as such */
-        street = g_hash_table_lookup (ht, "road");
-        building = g_hash_table_lookup (ht, "house_number");
-        if (street != NULL && building != NULL) {
-            char *address;
-            gboolean number_after;
-
-            number_after = _geocode_object_is_number_after_street ();
-            address = g_strdup_printf ("%s %s",
-                                       number_after ? street : building,
-                                       number_after ? building : street);
-            geocode_place_set_street_address (place, address);
-            g_free (address);
-        }
-
-        g_hash_table_foreach (ht, (GHFunc) fill_place_from_entry, place);
-
-        /* Get latitude and longitude and create GeocodeLocation object. */
-        longitude = g_ascii_strtod (g_hash_table_lookup (ht, "lon"), NULL);
-        latitude = g_ascii_strtod (g_hash_table_lookup (ht, "lat"), NULL);
-        name = geocode_place_get_name (place);
-
-        loc = geocode_location_new_with_description (latitude,
-                                                     longitude,
-                                                     GEOCODE_LOCATION_ACCURACY_UNKNOWN,
-                                                     name);
-        geocode_place_set_location (place, loc);
-        g_object_unref (loc);
-
-        return place;
-}
-
-static void
-insert_place_into_tree (GNode *place_tree, GHashTable *ht)
-{
-       GNode *start = place_tree;
-        GeocodePlace *place = NULL;
-       char *attr_val = NULL;
-       guint i;
-
-       for (i = 0; i < G_N_ELEMENTS(attributes); i++) {
-               GNode *child = NULL;
-
-               attr_val = g_hash_table_lookup (ht, attributes[i]);
-               if (!attr_val) {
-                       /* Add a dummy node if the attribute value is not
-                        * available for the place */
-                       child = g_node_insert_data (start, -1, NULL);
-               } else {
-                       /* If the attr value (eg for country United States)
-                        * already exists, then keep on adding other attributes under that node. */
-                       child = g_node_first_child (start);
-                       while (child &&
-                              child->data &&
-                              g_ascii_strcasecmp (child->data, attr_val) != 0) {
-                               child = g_node_next_sibling (child);
-                       }
-                       if (!child) {
-                               /* create a new node */
-                               child = g_node_insert_data (start, -1, g_strdup (attr_val));
-                       }
-               }
-               start = child;
-       }
-
-        place = _geocode_create_place_from_attributes (ht);
-
-        /* The leaf node of the tree is the GeocodePlace object, containing
-         * associated GeocodePlace object */
-       g_node_insert_data (start, -1, place);
-}
-
-static void
-make_place_list_from_tree (GNode  *node,
-                           char  **s_array,
-                           GList **place_list,
-                           int     i)
-{
-       GNode *child;
-
-       if (node == NULL)
-               return;
-
-       if (G_NODE_IS_LEAF (node)) {
-               GPtrArray *rev_s_array;
-               GeocodePlace *place;
-               GeocodeLocation *loc;
-               char *name;
-               int counter = 0;
-
-               rev_s_array = g_ptr_array_new ();
-
-               /* If leaf node, then add all the attributes in the s_array
-                * and set it to the description of the loc object */
-               place = (GeocodePlace *) node->data;
-               name = (char *) geocode_place_get_name (place);
-               loc = geocode_place_get_location (place);
-
-               /* To print the attributes in a meaningful manner
-                * reverse the s_array */
-               g_ptr_array_add (rev_s_array, (gpointer) name);
-               for (counter = 1; counter <= i; counter++)
-                       g_ptr_array_add (rev_s_array, s_array[i - counter]);
-               g_ptr_array_add (rev_s_array, NULL);
-               name = g_strjoinv (", ", (char **) rev_s_array->pdata);
-               g_ptr_array_unref (rev_s_array);
-
-               geocode_place_set_name (place, name);
-               geocode_location_set_description (loc, name);
-               g_free (name);
-
-               *place_list = g_list_prepend (*place_list, place);
-       } else {
-                GNode *prev, *next;
-
-                prev = g_node_prev_sibling (node);
-                next = g_node_next_sibling (node);
-
-               /* If there are other attributes with a different value,
-                * add those attributes to the string to differentiate them */
-               if (node->data && ((prev && prev->data) || (next && next->data))) {
-                        s_array[i] = node->data;
-                        i++;
-               }
-       }
-
-       for (child = node->children; child != NULL; child = child->next)
-               make_place_list_from_tree (child, s_array, place_list, i);
-}
-
-GList *
-_geocode_parse_search_json (const char *contents,
-                            GError    **error)
-{
-       GList *ret;
-       JsonParser *parser;
-       JsonNode *root;
-       JsonReader *reader;
-       const GError *err = NULL;
-       int num_places, i;
-       GNode *place_tree;
-       char *s_array[G_N_ELEMENTS (attributes)];
-
-       ret = NULL;
-
-       parser = json_parser_new ();
-       if (json_parser_load_from_data (parser, contents, -1, error) == FALSE) {
-               g_object_unref (parser);
-               return ret;
-       }
-
-       root = json_parser_get_root (parser);
-       reader = json_reader_new (root);
-
-       num_places = json_reader_count_elements (reader);
-       if (num_places < 0)
-               goto parse;
-        if (num_places == 0) {
-               g_set_error_literal (error,
-                                     GEOCODE_ERROR,
-                                     GEOCODE_ERROR_NO_MATCHES,
-                                     "No matches found for request");
-               goto no_results;
-        }
-
-       place_tree = g_node_new (NULL);
-
-       for (i = 0; i < num_places; i++) {
-               GHashTable *ht;
-
-               json_reader_read_element (reader, i);
-
-                ht = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                           g_free, g_free);
-                _geocode_read_nominatim_attributes (reader, ht);
-
-               /* Populate the tree with place details */
-               insert_place_into_tree (place_tree, ht);
-
-               g_hash_table_destroy (ht);
-
-               json_reader_end_element (reader);
-       }
-
-       make_place_list_from_tree (place_tree, s_array, &ret, 0);
-
-       g_node_traverse (place_tree,
-                        G_IN_ORDER,
-                        G_TRAVERSE_ALL,
-                        -1,
-                        (GNodeTraverseFunc) node_free_func,
-                        NULL);
-
-       g_node_destroy (place_tree);
-
-       g_object_unref (parser);
-       g_object_unref (reader);
-       ret = g_list_reverse (ret);
-
-       return ret;
-parse:
-       err = json_reader_get_error (reader);
-       g_set_error_literal (error, GEOCODE_ERROR, GEOCODE_ERROR_PARSE, err->message);
-no_results:
-       g_object_unref (parser);
-       g_object_unref (reader);
-       return NULL;
+       return g_task_propagate_pointer (G_TASK (res), error);
 }
 
 /**
@@ -1044,38 +382,16 @@ GList *
 geocode_forward_search (GeocodeForward      *forward,
                        GError             **error)
 {
-       SoupMessage *query;
-       char *contents;
-       GList *ret;
-       gboolean to_cache = FALSE;
-
        g_return_val_if_fail (GEOCODE_IS_FORWARD (forward), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-        query = get_search_query_for_params (forward, error);
-       if (!query)
-               return NULL;
-
-       if (_geocode_glib_cache_load (query, &contents) == FALSE) {
-                if (soup_session_send_message (forward->priv->soup_session,
-                                               query) != SOUP_STATUS_OK) {
-                        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                                             query->reason_phrase ? query->reason_phrase : "Query failed");
-                        g_object_unref (query);
-                        return NULL;
-                }
-                contents = g_strndup (query->response_body->data, query->response_body->length);
-
-               to_cache = TRUE;
-       }
-
-        ret = _geocode_parse_search_json (contents, error);
-       if (to_cache && ret != NULL)
-               _geocode_glib_cache_save (query, contents);
+       ensure_backend (forward);
+       g_assert (forward->priv->backend != NULL);
 
-       g_free (contents);
-       g_object_unref (query);
-
-       return ret;
+       return geocode_backend_forward_search (forward->priv->backend,
+                                              forward->priv->ht,
+                                              NULL,
+                                              error);
 }
 
 /**
@@ -1089,9 +405,19 @@ void
 geocode_forward_set_answer_count (GeocodeForward *forward,
                                  guint           count)
 {
+       GValue *count_value;
+
        g_return_if_fail (GEOCODE_IS_FORWARD (forward));
 
        forward->priv->answer_count = count;
+
+       /* Note: This key name is not defined in the Telepathy specification or
+        * in XEP-0080; it is custom, but standard within Geocode. */
+       count_value = g_new0 (GValue, 1);
+       g_value_init (count_value, G_TYPE_UINT);
+       g_value_set_uint (count_value, count);
+       g_hash_table_insert (forward->priv->ht, g_strdup ("limit"),
+                            count_value);
 }
 
 /**
@@ -1105,6 +431,7 @@ void
 geocode_forward_set_search_area (GeocodeForward     *forward,
                                 GeocodeBoundingBox *bbox)
 {
+       GValue *area_value;
        char *area;
        char top[G_ASCII_DTOSTR_BUF_SIZE];
        char left[G_ASCII_DTOSTR_BUF_SIZE];
@@ -1128,9 +455,14 @@ geocode_forward_set_search_area (GeocodeForward     *forward,
        g_ascii_dtostr (right, G_ASCII_DTOSTR_BUF_SIZE,
                        geocode_bounding_box_get_right (bbox));
 
+       /* Note: This key name is not defined in the Telepathy specification or
+        * in XEP-0080; it is custom, but standard within Geocode. */
        area = g_strdup_printf ("%s,%s,%s,%s", left, top, right, bottom);
-       geocode_forward_add (forward, "viewbox", area);
-       g_free (area);
+       area_value = g_new0 (GValue, 1);
+       g_value_init (area_value, G_TYPE_STRING);
+       g_value_take_string (area_value, area);
+       g_hash_table_insert (forward->priv->ht, g_strdup ("viewbox"),
+                            area_value);
 }
 
 /**
@@ -1146,9 +478,19 @@ void
 geocode_forward_set_bounded (GeocodeForward *forward,
                             gboolean        bounded)
 {
+       GValue *bounded_value;
+
        g_return_if_fail (GEOCODE_IS_FORWARD (forward));
 
        forward->priv->bounded = bounded;
+
+       /* Note: This key name is not defined in the Telepathy specification or
+        * in XEP-0080; it is custom, but standard within Geocode. */
+       bounded_value = g_new0 (GValue, 1);
+       g_value_init (bounded_value, G_TYPE_STRING);
+       g_value_set_boolean (bounded_value, bounded);
+       g_hash_table_insert (forward->priv->ht, g_strdup ("bounded"),
+                            bounded_value);
 }
 
 /**
@@ -1195,3 +537,25 @@ geocode_forward_get_bounded (GeocodeForward *forward)
 
        return forward->priv->bounded;
 }
+
+/**
+ * geocode_forward_set_backend:
+ * @forward: a #GeocodeForward representing a query
+ * @backend: (nullable) (transfer none): a #GeocodeBackend, or %NULL to use the
+ *    default one.
+ *
+ * Specifies the backend to use in the forward geocoding operation.
+ *
+ * If none is given, the default GNOME Nominatim server is used.
+ *
+ * Since: UNRELEASED
+ */
+void
+geocode_forward_set_backend (GeocodeForward *forward,
+                             GeocodeBackend *backend)
+{
+       g_return_if_fail (GEOCODE_IS_FORWARD (forward));
+       g_return_if_fail (backend == NULL || GEOCODE_IS_BACKEND (backend));
+
+       g_set_object (&forward->priv->backend, backend);
+}
diff --git a/geocode-glib/geocode-forward.h b/geocode-glib/geocode-forward.h
index 59924be..50267f2 100644
--- a/geocode-glib/geocode-forward.h
+++ b/geocode-glib/geocode-forward.h
@@ -26,6 +26,7 @@
 #include <glib.h>
 #include <gio/gio.h>
 #include <geocode-glib/geocode-glib.h>
+#include <geocode-glib/geocode-backend.h>
 #include <geocode-glib/geocode-bounding-box.h>
 
 G_BEGIN_DECLS
@@ -90,6 +91,9 @@ GList *geocode_forward_search_finish (GeocodeForward  *forward,
 GList *geocode_forward_search (GeocodeForward  *forward,
                               GError         **error);
 
+void geocode_forward_set_backend (GeocodeForward *forward,
+                                  GeocodeBackend *backend);
+
 G_END_DECLS
 
 #endif /* GEOCODE_FORWARD_H */
diff --git a/geocode-glib/geocode-glib.h b/geocode-glib/geocode-glib.h
index a72c045..d60d2a3 100644
--- a/geocode-glib/geocode-glib.h
+++ b/geocode-glib/geocode-glib.h
@@ -32,5 +32,7 @@
 #include <geocode-glib/geocode-bounding-box.h>
 #include <geocode-glib/geocode-error.h>
 #include <geocode-glib/geocode-enum-types.h>
+#include <geocode-glib/geocode-backend.h>
+#include <geocode-glib/geocode-nominatim.h>
 
 #endif /* GEOCODE_GLIB_H */
diff --git a/geocode-glib/geocode-glib.symbols b/geocode-glib/geocode-glib.symbols
index 826c9ef..64e86c1 100644
--- a/geocode-glib/geocode-glib.symbols
+++ b/geocode-glib/geocode-glib.symbols
@@ -26,11 +26,13 @@ geocode_forward_set_bounded
 geocode_forward_search_async
 geocode_forward_search_finish
 geocode_forward_search
+geocode_forward_set_backend
 geocode_reverse_get_type
 geocode_reverse_new_for_location
 geocode_reverse_resolve_async
 geocode_reverse_resolve_finish
 geocode_reverse_resolve
+geocode_reverse_set_backend
 geocode_error_quark
 geocode_error_get_type
 _geocode_parse_search_json
@@ -81,3 +83,13 @@ geocode_bounding_box_get_top
 geocode_bounding_box_get_bottom
 geocode_bounding_box_get_left
 geocode_bounding_box_get_right
+geocode_backend_get_type
+geocode_backend_forward_search_async
+geocode_backend_forward_search_finish
+geocode_backend_forward_search
+geocode_backend_reverse_resolve_async
+geocode_backend_reverse_resolve_finish
+geocode_backend_reverse_resolve
+geocode_nominatim_get_gnome
+geocode_nominatim_get_type
+geocode_nominatim_new
diff --git a/geocode-glib/geocode-nominatim.c b/geocode-glib/geocode-nominatim.c
new file mode 100644
index 0000000..78c064a
--- /dev/null
+++ b/geocode-glib/geocode-nominatim.c
@@ -0,0 +1,1458 @@
+/*
+ *  Copyright (C) 2011-2016 Bastien Nocera
+ *  Copyright (C) 2016 Collabora Ltd.
+ *
+ * The geocode-glib library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The geocode-glib library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301  USA.
+ *
+ * Authors: Bastien Nocera <hadess hadess net>
+ *          Aleksander Morgado <aleksander morgado collabora co uk>
+ *          Philip Withnall <philip withnall collabora co uk>
+ */
+
+#include <gio/gio.h>
+#include <json-glib/json-glib.h>
+#include <libsoup/soup.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "geocode-glib-private.h"
+#include "geocode-glib.h"
+#include "geocode-nominatim.h"
+
+typedef enum {
+       PROP_BASE_URL = 1,
+       PROP_MAINTAINER_EMAIL_ADDRESS,
+} GeocodeNominatimProperty;
+
+static GParamSpec *properties[PROP_MAINTAINER_EMAIL_ADDRESS + 1];
+
+typedef struct {
+       char *base_url;
+       char *maintainer_email_address;
+} GeocodeNominatimPrivate;
+
+static void geocode_backend_iface_init (GeocodeBackendInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GeocodeNominatim, geocode_nominatim, G_TYPE_OBJECT,
+                         G_ADD_PRIVATE (GeocodeNominatim)
+                         G_IMPLEMENT_INTERFACE (GEOCODE_TYPE_BACKEND,
+                                                geocode_backend_iface_init))
+
+/******************************************************************************/
+
+static struct {
+       const char *tp_attr;
+       const char *gc_attr; /* NULL to ignore */
+} attrs_map[] = {
+       /* See http://xmpp.org/extensions/xep-0080.html: */
+       { "countrycode", NULL },
+       { "country", "country" },
+       { "region", "state" },
+       { "county", "county" },
+       { "locality", "city" },
+       { "area", NULL },
+       { "postalcode", "postalcode" },
+       { "street", "street" },
+       { "building", NULL },
+       { "floor", NULL },
+       { "room",  NULL },
+       { "text", NULL },
+       { "description", NULL },
+       { "uri", NULL },
+       { "language", "accept-language" },
+
+       /* Custom keys which are passed through: */
+       { "location", "location" },
+       { "limit", "limit" },
+};
+
+static const char *
+tp_attr_to_gc_attr (const char *attr,
+                   gboolean   *found)
+{
+       guint i;
+
+       *found = FALSE;
+
+       for (i = 0; i < G_N_ELEMENTS (attrs_map); i++) {
+               if (g_str_equal (attr, attrs_map[i].tp_attr)){
+                       *found = TRUE;
+                       return attrs_map[i].gc_attr;
+               }
+       }
+
+       return NULL;
+}
+
+static GHashTable *
+geocode_forward_fill_params (GHashTable *params)
+{
+       GHashTable *params_out = NULL;
+       GHashTableIter iter;
+       GValue *value;
+       const char *key;
+
+       params_out = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                           g_free, g_free);
+
+       g_hash_table_iter_init (&iter, params);
+       while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) {
+               gboolean found;
+               const char *gc_attr;
+               char *str = NULL;
+               GValue string_value = G_VALUE_INIT;
+
+               gc_attr = tp_attr_to_gc_attr (key, &found);
+               if (found == FALSE) {
+                       g_warning ("XEP attribute '%s' unhandled", key);
+                       continue;
+               }
+               if (gc_attr == NULL)
+                       continue;
+
+               g_value_init (&string_value, G_TYPE_STRING);
+               g_assert (g_value_transform (value, &string_value));
+               str = g_value_dup_string (&string_value);
+               g_value_unset (&string_value);
+
+               if (str == NULL)
+                       continue;
+
+               g_return_val_if_fail (g_utf8_validate (str, -1, NULL), NULL);
+
+               g_hash_table_insert (params_out,
+                                    g_strdup (gc_attr),
+                                    str);
+       }
+
+       return params_out;
+}
+
+static gchar *
+get_search_uri_for_params (GeocodeNominatim  *self,
+                           GHashTable        *params,
+                           GError           **error)
+{
+       GeocodeNominatimPrivate *priv;
+       GHashTable *ht;
+       char *lang;
+       char *encoded_params;
+       char *uri;
+        guint8 i;
+        gboolean query_possible = FALSE;
+        char *location;
+        const char *allowed_attributes[] = { "country",
+                                             "region",
+                                             "county",
+                                             "locality",
+                                             "postalcode",
+                                             "street",
+                                             "location",
+                                             NULL };
+
+       priv = geocode_nominatim_get_instance_private (self);
+
+        /* Make sure we have at least one parameter that Nominatim allows querying for */
+       for (i = 0; allowed_attributes[i] != NULL; i++) {
+               if (g_hash_table_lookup (params, allowed_attributes[i]) != NULL) {
+                       query_possible = TRUE;
+                       break;
+               }
+       }
+
+        if (!query_possible) {
+                char *str;
+
+                str = g_strjoinv (", ", (char **) allowed_attributes);
+                g_set_error (error, GEOCODE_ERROR, GEOCODE_ERROR_INVALID_ARGUMENTS,
+                             "Only following parameters supported: %s", str);
+                g_free (str);
+
+               return NULL;
+       }
+
+       /* Prepare the query parameters */
+       ht = _geocode_glib_dup_hash_table (params);
+       g_hash_table_insert (ht, (gpointer) "format", (gpointer) "jsonv2");
+       g_hash_table_insert (ht, (gpointer) "email", (gpointer) priv->maintainer_email_address);
+       g_hash_table_insert (ht, (gpointer) "addressdetails", (gpointer) "1");
+
+       lang = NULL;
+       if (g_hash_table_lookup (ht, "accept-language") == NULL) {
+               lang = _geocode_object_get_lang ();
+               if (lang)
+                       g_hash_table_insert (ht, (gpointer) "accept-language", lang);
+       }
+
+        location = g_strdup (g_hash_table_lookup (ht, "location"));
+        g_hash_table_remove (ht, "location");
+
+       if (location == NULL)
+               g_hash_table_insert (ht, (gpointer) "limit", (gpointer) "1");
+       else if (!g_hash_table_contains (ht, "limit"))
+               g_hash_table_insert (ht, (gpointer) "limit",
+                                    (gpointer) G_STRINGIFY (DEFAULT_ANSWER_COUNT));
+
+       if (location == NULL)
+               g_hash_table_remove (ht, "bounded");
+       else if (!g_hash_table_contains (ht, "bounded"))
+               g_hash_table_insert (ht, (gpointer) "bounded", (gpointer) "0");
+
+       if (location != NULL)
+               g_hash_table_insert (ht, (gpointer) "q", location);
+
+       encoded_params = soup_form_encode_hash (ht);
+       g_hash_table_unref (ht);
+       g_free (lang);
+       g_free (location);
+
+       uri = g_strdup_printf ("%s/search?%s", priv->base_url, encoded_params);
+       g_free (encoded_params);
+
+       return uri;
+}
+
+static struct {
+       const char *nominatim_attr;
+        const char *place_prop; /* NULL to ignore */
+} nominatim_to_place_map[] = {
+        { "license", NULL },
+        { "osm_id", "osm-id" },
+        { "lat", NULL },
+        { "lon", NULL },
+        { "display_name", NULL },
+        { "house_number", "building" },
+        { "road", "street" },
+        { "suburb", "area" },
+        { "city",  "town" },
+        { "village",  "town" },
+        { "county", "county" },
+        { "state_district", "administrative-area" },
+        { "state", "state" },
+        { "postcode", "postal-code" },
+        { "country", "country" },
+        { "country_code", "country-code" },
+        { "continent", "continent" },
+        { "address", NULL },
+};
+
+static void
+fill_place_from_entry (const char   *key,
+                       const char   *value,
+                       GeocodePlace *place)
+{
+        guint i;
+
+        for (i = 0; i < G_N_ELEMENTS (nominatim_to_place_map); i++) {
+                if (g_str_equal (key, nominatim_to_place_map[i].nominatim_attr)){
+                        g_object_set (G_OBJECT (place),
+                                      nominatim_to_place_map[i].place_prop,
+                                      value,
+                                      NULL);
+                        break;
+                }
+        }
+
+        if (g_str_equal (key, "osm_type")) {
+                gpointer ref = g_type_class_ref (geocode_place_osm_type_get_type ());
+                GEnumClass *class = G_ENUM_CLASS (ref);
+                GEnumValue *evalue = g_enum_get_value_by_nick (class, value);
+
+                if (evalue)
+                        g_object_set (G_OBJECT (place), "osm-type", evalue->value, NULL);
+                else
+                        g_warning ("Unsupported osm-type %s", value);
+
+                g_type_class_unref (ref);
+        }
+}
+
+static gboolean
+node_free_func (GNode    *node,
+               gpointer  user_data)
+{
+       /* Leaf nodes are GeocodeLocation objects
+        * which we reuse for the results */
+       if (G_NODE_IS_LEAF (node) == FALSE)
+               g_free (node->data);
+
+       return FALSE;
+}
+
+static const char const *place_attributes[] = {
+       "country",
+       "state",
+       "county",
+       "state_district",
+       "postcode",
+       "city",
+       "suburb",
+       "village",
+};
+
+static GeocodePlaceType
+get_place_type_from_attributes (GHashTable *ht)
+{
+        char *category, *type;
+        GeocodePlaceType place_type = GEOCODE_PLACE_TYPE_UNKNOWN;
+
+        category = g_hash_table_lookup (ht, "category");
+        type = g_hash_table_lookup (ht, "type");
+
+        if (g_strcmp0 (category, "place") == 0) {
+                if (g_strcmp0 (type, "house") == 0 ||
+                    g_strcmp0 (type, "building") == 0 ||
+                    g_strcmp0 (type, "residential") == 0 ||
+                    g_strcmp0 (type, "plaza") == 0 ||
+                    g_strcmp0 (type, "office") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_BUILDING;
+                else if (g_strcmp0 (type, "estate") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_ESTATE;
+                else if (g_strcmp0 (type, "town") == 0 ||
+                         g_strcmp0 (type, "city") == 0 ||
+                         g_strcmp0 (type, "hamlet") == 0 ||
+                         g_strcmp0 (type, "isolated_dwelling") == 0 ||
+                         g_strcmp0 (type, "village") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_TOWN;
+                else if (g_strcmp0 (type, "suburb") == 0 ||
+                         g_strcmp0 (type, "neighbourhood") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_SUBURB;
+                else if (g_strcmp0 (type, "state") == 0 ||
+                         g_strcmp0 (type, "region") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_STATE;
+                else if (g_strcmp0 (type, "farm") == 0 ||
+                         g_strcmp0 (type, "forest") == 0 ||
+                         g_strcmp0 (type, "valey") == 0 ||
+                         g_strcmp0 (type, "park") == 0 ||
+                         g_strcmp0 (type, "hill") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_LAND_FEATURE;
+                else if (g_strcmp0 (type, "island") == 0 ||
+                         g_strcmp0 (type, "islet") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_ISLAND;
+                else if (g_strcmp0 (type, "country") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_COUNTRY;
+                else if (g_strcmp0 (type, "continent") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_CONTINENT;
+                else if (g_strcmp0 (type, "lake") == 0 ||
+                         g_strcmp0 (type, "bay") == 0 ||
+                         g_strcmp0 (type, "river") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_DRAINAGE;
+                else if (g_strcmp0 (type, "sea") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_SEA;
+                else if (g_strcmp0 (type, "ocean") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_OCEAN;
+        } else if (g_strcmp0 (category, "highway") == 0) {
+                if (g_strcmp0 (type, "motorway") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_MOTORWAY;
+                else if (g_strcmp0 (type, "bus_stop") == 0)
+                        place_type =  GEOCODE_PLACE_TYPE_BUS_STOP;
+                else
+                        place_type =  GEOCODE_PLACE_TYPE_STREET;
+        } else if (g_strcmp0 (category, "railway") == 0) {
+                if (g_strcmp0 (type, "station") == 0 ||
+                    g_strcmp0 (type, "halt") == 0)
+                        place_type = GEOCODE_PLACE_TYPE_RAILWAY_STATION;
+                else if (g_strcmp0 (type, "tram_stop") == 0)
+                        place_type = GEOCODE_PLACE_TYPE_LIGHT_RAIL_STATION;
+        } else if (g_strcmp0 (category, "waterway") == 0) {
+                place_type =  GEOCODE_PLACE_TYPE_DRAINAGE;
+        } else if (g_strcmp0 (category, "boundary") == 0) {
+                if (g_strcmp0 (type, "administrative") == 0) {
+                        int rank;
+
+                        rank = atoi (g_hash_table_lookup (ht, "place_rank"));
+                        if (rank < 2)
+                                place_type =  GEOCODE_PLACE_TYPE_UNKNOWN;
+
+                        if (rank == 28)
+                                place_type =  GEOCODE_PLACE_TYPE_BUILDING;
+                        else if (rank == 16)
+                                place_type =  GEOCODE_PLACE_TYPE_TOWN;
+                        else if (rank == 12)
+                                place_type =  GEOCODE_PLACE_TYPE_COUNTY;
+                        else if (rank == 10 || rank == 8)
+                                place_type =  GEOCODE_PLACE_TYPE_STATE;
+                        else if (rank == 4)
+                                place_type =  GEOCODE_PLACE_TYPE_COUNTRY;
+                }
+        } else if (g_strcmp0 (category, "amenity") == 0) {
+                if (g_strcmp0 (type, "school") == 0)
+                        place_type = GEOCODE_PLACE_TYPE_SCHOOL;
+                else if (g_strcmp0 (type, "place_of_worship") == 0)
+                        place_type = GEOCODE_PLACE_TYPE_PLACE_OF_WORSHIP;
+                else if (g_strcmp0 (type, "restaurant") == 0)
+                        place_type = GEOCODE_PLACE_TYPE_RESTAURANT;
+                else if (g_strcmp0 (type, "bar") == 0 ||
+                         g_strcmp0 (type, "pub") == 0)
+                        place_type = GEOCODE_PLACE_TYPE_BAR;
+        } else if (g_strcmp0 (category, "aeroway") == 0) {
+                if (g_strcmp0 (type, "aerodrome") == 0)
+                        place_type = GEOCODE_PLACE_TYPE_AIRPORT;
+        }
+
+        return place_type;
+}
+
+GeocodePlace *
+_geocode_create_place_from_attributes (GHashTable *ht)
+{
+        GeocodePlace *place;
+        GeocodeLocation *loc = NULL;
+        const char *name, *street, *building, *bbox_corner;
+        GeocodePlaceType place_type;
+        gdouble longitude, latitude;
+
+        place_type = get_place_type_from_attributes (ht);
+
+        name = g_hash_table_lookup (ht, "name");
+        if (name == NULL)
+                name = g_hash_table_lookup (ht, "display_name");
+
+        place = geocode_place_new (name, place_type);
+
+        /* If one corner exists, then all exists */
+        bbox_corner = g_hash_table_lookup (ht, "boundingbox-top");
+        if (bbox_corner != NULL) {
+            GeocodeBoundingBox *bbox;
+            gdouble top, bottom, left, right;
+
+            top = g_ascii_strtod (bbox_corner, NULL);
+
+            bbox_corner = g_hash_table_lookup (ht, "boundingbox-bottom");
+            bottom = g_ascii_strtod (bbox_corner, NULL);
+
+            bbox_corner = g_hash_table_lookup (ht, "boundingbox-left");
+            left = g_ascii_strtod (bbox_corner, NULL);
+
+            bbox_corner = g_hash_table_lookup (ht, "boundingbox-right");
+            right = g_ascii_strtod (bbox_corner, NULL);
+
+            bbox = geocode_bounding_box_new (top, bottom, left, right);
+            geocode_place_set_bounding_box (place, bbox);
+            g_object_unref (bbox);
+        }
+
+        /* Nominatim doesn't give us street addresses as such */
+        street = g_hash_table_lookup (ht, "road");
+        building = g_hash_table_lookup (ht, "house_number");
+        if (street != NULL && building != NULL) {
+            char *address;
+            gboolean number_after;
+
+            number_after = _geocode_object_is_number_after_street ();
+            address = g_strdup_printf ("%s %s",
+                                       number_after ? street : building,
+                                       number_after ? building : street);
+            geocode_place_set_street_address (place, address);
+            g_free (address);
+        }
+
+        g_hash_table_foreach (ht, (GHFunc) fill_place_from_entry, place);
+
+        /* Get latitude and longitude and create GeocodeLocation object. */
+        longitude = g_ascii_strtod (g_hash_table_lookup (ht, "lon"), NULL);
+        latitude = g_ascii_strtod (g_hash_table_lookup (ht, "lat"), NULL);
+        name = geocode_place_get_name (place);
+
+        loc = geocode_location_new_with_description (latitude,
+                                                     longitude,
+                                                     GEOCODE_LOCATION_ACCURACY_UNKNOWN,
+                                                     name);
+        geocode_place_set_location (place, loc);
+        g_object_unref (loc);
+
+        return place;
+}
+
+static void
+insert_place_into_tree (GNode *place_tree, GHashTable *ht)
+{
+       GNode *start = place_tree;
+        GeocodePlace *place = NULL;
+       char *attr_val = NULL;
+       guint i;
+
+       for (i = 0; i < G_N_ELEMENTS (place_attributes); i++) {
+               GNode *child = NULL;
+
+               attr_val = g_hash_table_lookup (ht, place_attributes[i]);
+               if (!attr_val) {
+                       /* Add a dummy node if the attribute value is not
+                        * available for the place */
+                       child = g_node_insert_data (start, -1, NULL);
+               } else {
+                       /* If the attr value (eg for country United States)
+                        * already exists, then keep on adding other attributes under that node. */
+                       child = g_node_first_child (start);
+                       while (child &&
+                              child->data &&
+                              g_ascii_strcasecmp (child->data, attr_val) != 0) {
+                               child = g_node_next_sibling (child);
+                       }
+                       if (!child) {
+                               /* create a new node */
+                               child = g_node_insert_data (start, -1, g_strdup (attr_val));
+                       }
+               }
+               start = child;
+       }
+
+        place = _geocode_create_place_from_attributes (ht);
+
+        /* The leaf node of the tree is the GeocodePlace object, containing
+         * associated GeocodePlace object */
+       g_node_insert_data (start, -1, place);
+}
+
+static void
+make_place_list_from_tree (GNode  *node,
+                           char  **s_array,
+                           GList **place_list,
+                           int     i)
+{
+       GNode *child;
+
+       if (node == NULL)
+               return;
+
+       if (G_NODE_IS_LEAF (node)) {
+               GPtrArray *rev_s_array;
+               GeocodePlace *place;
+               GeocodeLocation *loc;
+               char *name;
+               int counter = 0;
+
+               rev_s_array = g_ptr_array_new ();
+
+               /* If leaf node, then add all the attributes in the s_array
+                * and set it to the description of the loc object */
+               place = (GeocodePlace *) node->data;
+               name = (char *) geocode_place_get_name (place);
+               loc = geocode_place_get_location (place);
+
+               /* To print the attributes in a meaningful manner
+                * reverse the s_array */
+               g_ptr_array_add (rev_s_array, (gpointer) name);
+               for (counter = 1; counter <= i; counter++)
+                       g_ptr_array_add (rev_s_array, s_array[i - counter]);
+               g_ptr_array_add (rev_s_array, NULL);
+               name = g_strjoinv (", ", (char **) rev_s_array->pdata);
+               g_ptr_array_unref (rev_s_array);
+
+               geocode_place_set_name (place, name);
+               geocode_location_set_description (loc, name);
+               g_free (name);
+
+               *place_list = g_list_prepend (*place_list, place);
+       } else {
+                GNode *prev, *next;
+
+                prev = g_node_prev_sibling (node);
+                next = g_node_next_sibling (node);
+
+               /* If there are other attributes with a different value,
+                * add those attributes to the string to differentiate them */
+               if (node->data && ((prev && prev->data) || (next && next->data))) {
+                        s_array[i] = node->data;
+                        i++;
+               }
+       }
+
+       for (child = node->children; child != NULL; child = child->next)
+               make_place_list_from_tree (child, s_array, place_list, i);
+}
+
+GList *
+_geocode_parse_search_json (const char *contents,
+                            GError    **error)
+{
+       GList *ret;
+       JsonParser *parser;
+       JsonNode *root;
+       JsonReader *reader;
+       const GError *err = NULL;
+       int num_places, i;
+       GNode *place_tree;
+       char *s_array[G_N_ELEMENTS (place_attributes)];
+
+       g_debug ("%s: contents = %s", G_STRFUNC, contents);
+
+       ret = NULL;
+
+       parser = json_parser_new ();
+       if (json_parser_load_from_data (parser, contents, -1, error) == FALSE) {
+               g_object_unref (parser);
+               return ret;
+       }
+
+       root = json_parser_get_root (parser);
+       reader = json_reader_new (root);
+
+       num_places = json_reader_count_elements (reader);
+       if (num_places < 0)
+               goto parse;
+        if (num_places == 0) {
+               g_set_error_literal (error,
+                                     GEOCODE_ERROR,
+                                     GEOCODE_ERROR_NO_MATCHES,
+                                     "No matches found for request");
+               goto no_results;
+        }
+
+       place_tree = g_node_new (NULL);
+
+       for (i = 0; i < num_places; i++) {
+               GHashTable *ht;
+
+               json_reader_read_element (reader, i);
+
+                ht = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                           g_free, g_free);
+                _geocode_read_nominatim_attributes (reader, ht);
+
+               /* Populate the tree with place details */
+               insert_place_into_tree (place_tree, ht);
+
+               g_hash_table_unref (ht);
+
+               json_reader_end_element (reader);
+       }
+
+       make_place_list_from_tree (place_tree, s_array, &ret, 0);
+
+       g_node_traverse (place_tree,
+                        G_IN_ORDER,
+                        G_TRAVERSE_ALL,
+                        -1,
+                        (GNodeTraverseFunc) node_free_func,
+                        NULL);
+
+       g_node_destroy (place_tree);
+
+       g_object_unref (parser);
+       g_object_unref (reader);
+       ret = g_list_reverse (ret);
+
+       return ret;
+parse:
+       err = json_reader_get_error (reader);
+       g_set_error_literal (error, GEOCODE_ERROR, GEOCODE_ERROR_PARSE, err->message);
+no_results:
+       g_object_unref (parser);
+       g_object_unref (reader);
+       return NULL;
+}
+
+static GList *
+geocode_nominatim_forward_search (GeocodeBackend  *backend,
+                                  GHashTable      *params,
+                                  GCancellable    *cancellable,
+                                  GError         **error)
+{
+       GeocodeNominatim *self = GEOCODE_NOMINATIM (backend);
+       char *contents;
+       GHashTable *transformed_params = NULL;  /* (utf8, utf8) */
+       GList *result = NULL;  /* (element-type GeocodePlace) */
+       gchar *uri = NULL;
+
+       transformed_params = geocode_forward_fill_params (params);
+       uri = get_search_uri_for_params (self, transformed_params, error);
+       g_hash_table_unref (transformed_params);
+
+       if (uri == NULL)
+               return NULL;
+
+       contents = GEOCODE_NOMINATIM_GET_CLASS (self)->query (self,
+                                                             uri,
+                                                             cancellable,
+                                                             error);
+       if (contents != NULL) {
+               result = _geocode_parse_search_json (contents, error);
+               g_free (contents);
+       }
+
+       g_free (uri);
+
+       return result;
+}
+
+static void
+on_forward_query_ready (GeocodeNominatim *self,
+                        GAsyncResult     *res,
+                        GTask            *task)
+{
+       GError *error = NULL;
+       char *contents;
+       GList *places;  /* (element-type GeocodePlace) */
+
+       contents = GEOCODE_NOMINATIM_GET_CLASS (self)->query_finish (GEOCODE_NOMINATIM (self), res, &error);
+       if (contents == NULL) {
+               g_task_return_error (task, error);
+               g_object_unref (task);
+               return;
+       }
+
+       places = _geocode_parse_search_json (contents, &error);
+       g_free (contents);
+
+       if (places == NULL) {
+               g_task_return_error (task, error);
+               g_object_unref (task);
+               return;
+       }
+
+       g_task_return_pointer (task, places, (GDestroyNotify) g_list_free);
+       g_object_unref (task);
+}
+
+static void
+geocode_nominatim_forward_search_async (GeocodeBackend      *backend,
+                                        GHashTable          *params,
+                                        GCancellable        *cancellable,
+                                        GAsyncReadyCallback  callback,
+                                        gpointer             user_data)
+{
+       GeocodeNominatim *self = GEOCODE_NOMINATIM (backend);
+       GTask *task;
+       GHashTable *transformed_params = NULL;  /* (utf8, utf8) */
+       gchar *uri = NULL;
+       GError *error = NULL;
+
+       transformed_params = geocode_forward_fill_params (params);
+       uri = get_search_uri_for_params (self, transformed_params, &error);
+       g_hash_table_unref (transformed_params);
+
+       if (error != NULL) {
+               g_task_report_error (self, callback, user_data, NULL, error);
+               return;
+       }
+
+       task = g_task_new (self, cancellable, callback, user_data);
+       GEOCODE_NOMINATIM_GET_CLASS (self)->query_async (self,
+                                                        uri,
+                                                        cancellable,
+                                                        (GAsyncReadyCallback) on_forward_query_ready,
+                                                        g_object_ref (task));
+       g_object_unref (task);
+       g_free (uri);
+}
+
+static GList *
+geocode_nominatim_forward_search_finish (GeocodeBackend  *backend,
+                                         GAsyncResult    *res,
+                                         GError         **error)
+{
+       return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+/******************************************************************************/
+
+static void
+copy_item (char       *key,
+           char       *value,
+           GHashTable *ret)
+{
+       g_hash_table_insert (ret, key, value);
+}
+
+GHashTable *
+_geocode_glib_dup_hash_table (GHashTable *ht)
+{
+       GHashTable *ret;
+
+       ret = g_hash_table_new (g_str_hash, g_str_equal);
+       g_hash_table_foreach (ht, (GHFunc) copy_item, ret);
+
+       return ret;
+}
+
+static gchar *
+get_resolve_uri_for_params (GeocodeNominatim *self,
+                            GHashTable       *orig_ht)
+{
+       GHashTable *ht;
+       char *locale;
+       char *params, *uri;
+       GeocodeNominatimPrivate *priv;
+
+       priv = geocode_nominatim_get_instance_private (self);
+
+       ht = _geocode_glib_dup_hash_table (orig_ht);
+
+       g_hash_table_insert (ht, (gpointer) "format", (gpointer) "json");
+       g_hash_table_insert (ht, (gpointer) "email",
+                            (gpointer) priv->maintainer_email_address);
+       g_hash_table_insert (ht, (gpointer) "addressdetails", (gpointer) "1");
+
+       locale = NULL;
+       if (g_hash_table_lookup (ht, "accept-language") == NULL) {
+               locale = _geocode_object_get_lang ();
+               if (locale)
+                       g_hash_table_insert (ht, (gpointer) "accept-language", locale);
+       }
+
+       {
+               GHashTableIter iter;
+               gpointer key, value;
+
+               g_hash_table_iter_init (&iter, ht);
+               while (g_hash_table_iter_next (&iter, &key, &value))
+                       g_debug ("%s: %s = %s", G_STRFUNC, (const gchar *) key, (const gchar *) value);
+       }
+
+       params = soup_form_encode_hash (ht);
+       g_hash_table_unref (ht);
+       g_free (locale);
+
+       uri = g_strdup_printf ("%s/reverse?%s", priv->base_url, params);
+       g_free (params);
+
+       return uri;
+}
+
+static gchar *
+geocode_nominatim_query_finish (GeocodeNominatim  *self,
+                                GAsyncResult      *res,
+                                GError           **error)
+{
+       return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+on_query_data_loaded (SoupSession *session,
+                      SoupMessage *query,
+                      GTask       *task)
+{
+       char *contents;
+
+       if (query->status_code != SOUP_STATUS_OK)
+               g_task_return_new_error (task,
+                                        G_IO_ERROR,
+                                        G_IO_ERROR_FAILED,
+                                        "%s",
+                                        query->reason_phrase ? query->reason_phrase : "Query failed");
+       else {
+               contents = g_strndup (query->response_body->data, query->response_body->length);
+               _geocode_glib_cache_save (query, contents);
+               g_task_return_pointer (task, contents, g_free);
+       }
+
+       g_object_unref (task);
+}
+
+static void
+on_cache_data_loaded (GFile        *cache,
+                      GAsyncResult *res,
+                      GTask        *task)
+{
+       char *contents;
+       SoupSession *soup_session;
+
+       if (g_file_load_contents_finish (cache,
+                                        res,
+                                        &contents,
+                                        NULL,
+                                        NULL,
+                                        NULL)) {
+               g_task_return_pointer (task, contents, g_free);
+               g_object_unref (task);
+               return;
+       }
+
+       soup_session = _geocode_glib_build_soup_session ();
+       soup_session_queue_message (soup_session,
+                                   g_object_ref (g_task_get_task_data (task)),
+                                   (SoupSessionCallback) on_query_data_loaded,
+                                   task);
+       g_object_unref (soup_session);
+}
+
+static void
+geocode_nominatim_query_async (GeocodeNominatim    *self,
+                               const gchar         *uri,
+                               GCancellable        *cancellable,
+                               GAsyncReadyCallback  callback,
+                               gpointer             user_data)
+{
+       GTask *task;
+       SoupSession *soup_session;
+       SoupMessage *soup_query;
+       char *cache_path;
+
+       g_debug ("%s: uri = %s", G_STRFUNC, uri);
+
+       task = g_task_new (self, cancellable, callback, user_data);
+
+       soup_query = soup_message_new (SOUP_METHOD_GET, uri);
+       g_task_set_task_data (task, soup_query, g_object_unref);
+
+       cache_path = _geocode_glib_cache_path_for_query (soup_query);
+       if (cache_path != NULL) {
+               GFile *cache;
+
+               cache = g_file_new_for_path (cache_path);
+               g_file_load_contents_async (cache,
+                                           cancellable,
+                                           (GAsyncReadyCallback) on_cache_data_loaded,
+                                           task);
+               g_object_unref (cache);
+               g_free (cache_path);
+               return;
+       }
+
+       soup_session = _geocode_glib_build_soup_session ();
+       soup_session_queue_message (soup_session,
+                                   g_object_ref (soup_query),
+                                   (SoupSessionCallback) on_query_data_loaded,
+                                   task);
+       g_object_unref (soup_session);
+}
+
+static gchar *
+geocode_nominatim_query (GeocodeNominatim  *self,
+                         const gchar       *uri,
+                         GCancellable      *cancellable,
+                         GError           **error)
+{
+       SoupSession *soup_session;
+       SoupMessage *soup_query;
+       char *contents;
+
+       g_debug ("%s: uri = %s", G_STRFUNC, uri);
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return NULL;
+
+       soup_session = _geocode_glib_build_soup_session ();
+       soup_query = soup_message_new (SOUP_METHOD_GET, uri);
+
+       if (_geocode_glib_cache_load (soup_query, &contents) == FALSE) {
+               if (soup_session_send_message (soup_session, soup_query) != SOUP_STATUS_OK) {
+                       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                                            soup_query->reason_phrase ? soup_query->reason_phrase : "Query 
failed");
+                       contents = NULL;
+               } else {
+                       contents = g_strndup (soup_query->response_body->data, 
soup_query->response_body->length);
+                       _geocode_glib_cache_save (soup_query, contents);
+               }
+       }
+
+       g_object_unref (soup_query);
+       g_object_unref (soup_session);
+
+       return contents;
+}
+
+/******************************************************************************/
+
+static GHashTable *
+_geocode_location_to_params (GeocodeLocation *location)
+{
+       GHashTable *ht;
+       char coord[G_ASCII_DTOSTR_BUF_SIZE];
+       char *lat;
+       char *lon;
+
+       lat = g_strdup (g_ascii_dtostr (coord,
+                                       G_ASCII_DTOSTR_BUF_SIZE,
+                                       geocode_location_get_latitude (location)));
+
+       lon = g_strdup (g_ascii_dtostr (coord,
+                                       G_ASCII_DTOSTR_BUF_SIZE,
+                                       geocode_location_get_longitude (location)));
+
+       ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+       g_hash_table_insert (ht, g_strdup ("lat"), lat);
+       g_hash_table_insert (ht, g_strdup ("lon"), lon);
+
+       return ht;
+}
+
+static GeocodePlace *
+geocode_nominatim_reverse_resolve_finish (GeocodeBackend  *backend,
+                                          GAsyncResult    *res,
+                                          GError         **error)
+{
+       return GEOCODE_PLACE (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void
+insert_bounding_box_element (GHashTable *ht,
+                             GType       value_type,
+                             const char *name,
+                             JsonReader *reader)
+{
+       if (value_type == G_TYPE_STRING) {
+               const char *bbox_val;
+
+               bbox_val = json_reader_get_string_value (reader);
+               g_hash_table_insert (ht, g_strdup (name), g_strdup (bbox_val));
+       } else if (value_type == G_TYPE_DOUBLE) {
+               gdouble bbox_val;
+
+               bbox_val = json_reader_get_double_value (reader);
+               g_hash_table_insert(ht, g_strdup (name), g_strdup_printf ("%lf", bbox_val));
+       } else if (value_type == G_TYPE_INT64) {
+               gint64 bbox_val;
+
+               bbox_val = json_reader_get_double_value (reader);
+               g_hash_table_insert(ht, g_strdup (name), g_strdup_printf ("%"G_GINT64_FORMAT, bbox_val));
+       } else {
+               g_debug ("Unhandled node type %s for %s", g_type_name (value_type), name);
+       }
+}
+
+void
+_geocode_read_nominatim_attributes (JsonReader *reader,
+                                    GHashTable *ht)
+{
+       char **members;
+       guint i;
+       gboolean is_address;
+       const char *house_number = NULL;
+
+       is_address = (g_strcmp0 (json_reader_get_member_name (reader), "address") == 0);
+
+       members = json_reader_list_members (reader);
+       if (members == NULL) {
+               json_reader_end_member (reader);
+               return;
+       }
+
+       for (i = 0; members[i] != NULL; i++) {
+               const char *value = NULL;
+
+               json_reader_read_member (reader, members[i]);
+
+               if (json_reader_is_value (reader)) {
+                       JsonNode *node = json_reader_get_value (reader);
+                       if (json_node_get_value_type (node) == G_TYPE_STRING) {
+                               value = json_node_get_string (node);
+                               if (value && *value == '\0')
+                                       value = NULL;
+                       }
+               }
+
+               if (value != NULL) {
+                       g_hash_table_insert (ht, g_strdup (members[i]), g_strdup (value));
+
+                       if (i == 0 && is_address) {
+                               if (g_strcmp0 (members[i], "house_number") != 0)
+                                       /* Since Nominatim doesn't give us a short name,
+                                        * we use the first component of address as name.
+                                        */
+                                       g_hash_table_insert (ht, g_strdup ("name"), g_strdup (value));
+                               else
+                                       house_number = value;
+                       } else if (house_number != NULL && g_strcmp0 (members[i], "road") == 0) {
+                               gboolean number_after;
+                               char *name;
+
+                               number_after = _geocode_object_is_number_after_street ();
+                               name = g_strdup_printf ("%s %s",
+                                                       number_after ? value : house_number,
+                                                       number_after ? house_number : value);
+                               g_hash_table_insert (ht, g_strdup ("name"), name);
+                       }
+               } else if (g_strcmp0 (members[i], "boundingbox") == 0) {
+                       JsonNode *node;
+                       GType value_type;
+
+                       json_reader_read_element (reader, 0);
+                       node = json_reader_get_value (reader);
+                       value_type = json_node_get_value_type (node);
+
+                       insert_bounding_box_element (ht, value_type, "boundingbox-bottom", reader);
+                       json_reader_end_element (reader);
+
+                       json_reader_read_element (reader, 1);
+                       insert_bounding_box_element (ht, value_type, "boundingbox-top", reader);
+                       json_reader_end_element (reader);
+
+                       json_reader_read_element (reader, 2);
+                       insert_bounding_box_element (ht, value_type, "boundingbox-left", reader);
+                       json_reader_end_element (reader);
+
+                       json_reader_read_element (reader, 3);
+                       insert_bounding_box_element (ht, value_type, "boundingbox-right", reader);
+                       json_reader_end_element (reader);
+               }
+               json_reader_end_member (reader);
+       }
+
+       g_strfreev (members);
+
+       if (json_reader_read_member (reader, "address"))
+               _geocode_read_nominatim_attributes (reader, ht);
+       json_reader_end_member (reader);
+}
+
+static GHashTable *
+resolve_json (const char  *contents,
+              GError     **error)
+{
+       GHashTable *ret = NULL;
+       JsonParser *parser;
+       JsonNode *root;
+       JsonReader *reader;
+
+       g_debug ("%s: contents = %s", G_STRFUNC, contents);
+
+       parser = json_parser_new ();
+       if (json_parser_load_from_data (parser, contents, -1, error) == FALSE) {
+               g_object_unref (parser);
+               return ret;
+       }
+
+       root = json_parser_get_root (parser);
+       reader = json_reader_new (root);
+
+       if (json_reader_read_member (reader, "error")) {
+               const char *msg;
+
+               msg = json_reader_get_string_value (reader);
+               json_reader_end_member (reader);
+               if (msg && *msg == '\0')
+                       msg = NULL;
+
+               g_set_error_literal (error,
+                                    GEOCODE_ERROR,
+                                    GEOCODE_ERROR_NOT_SUPPORTED,
+                                    msg ? msg : "Query not supported");
+               g_object_unref (parser);
+               g_object_unref (reader);
+               return NULL;
+       }
+
+       ret = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+       _geocode_read_nominatim_attributes (reader, ret);
+
+       g_object_unref (parser);
+       g_object_unref (reader);
+
+       return ret;
+}
+
+static void
+on_reverse_query_ready (GeocodeNominatim *self,
+                        GAsyncResult     *res,
+                        GTask            *task)
+{
+       GError *error = NULL;
+       char *contents;
+       GeocodePlace *ret;
+       GHashTable *attributes;
+
+       contents = GEOCODE_NOMINATIM_GET_CLASS (self)->query_finish (GEOCODE_NOMINATIM (self), res, &error);
+       if (contents == NULL) {
+               g_task_return_error (task, error);
+               g_object_unref (task);
+               return;
+       }
+
+       attributes = resolve_json (contents, &error);
+       g_free (contents);
+
+       if (attributes == NULL) {
+               g_task_return_error (task, error);
+               g_object_unref (task);
+               return;
+       }
+
+       ret = _geocode_create_place_from_attributes (attributes);
+       g_hash_table_unref (attributes);
+
+       g_task_return_pointer (task, ret, g_object_unref);
+       g_object_unref (task);
+}
+
+static void
+geocode_nominatim_reverse_resolve_async (GeocodeBackend      *self,
+                                         GeocodeLocation     *location,
+                                         GCancellable        *cancellable,
+                                         GAsyncReadyCallback  callback,
+                                         gpointer             user_data)
+{
+       GTask *task;
+       GHashTable *ht;
+       gchar *uri = NULL;
+
+       g_return_if_fail (GEOCODE_IS_BACKEND (self));
+       g_return_if_fail (GEOCODE_IS_LOCATION (location));
+
+       ht = _geocode_location_to_params (location);
+       uri = get_resolve_uri_for_params (GEOCODE_NOMINATIM (self), ht);
+       g_hash_table_unref (ht);
+
+       task = g_task_new (self, cancellable, callback, user_data);
+       GEOCODE_NOMINATIM_GET_CLASS (self)->query_async (GEOCODE_NOMINATIM (self),
+                                                        uri,
+                                                        cancellable,
+                                                        (GAsyncReadyCallback) on_reverse_query_ready,
+                                                        g_object_ref (task));
+       g_object_unref (task);
+       g_free (uri);
+}
+
+static GeocodePlace *
+geocode_nominatim_reverse_resolve (GeocodeBackend   *self,
+                                   GeocodeLocation  *location,
+                                   GCancellable     *cancellable,
+                                   GError          **error)
+{
+       char *contents;
+       GHashTable *ht;
+       GHashTable *result = NULL;
+       GeocodePlace *place;
+       gchar *uri = NULL;
+
+       g_return_val_if_fail (GEOCODE_IS_BACKEND (self), NULL);
+       g_return_val_if_fail (GEOCODE_IS_LOCATION (location), NULL);
+
+       ht = _geocode_location_to_params (location);
+       uri = get_resolve_uri_for_params (GEOCODE_NOMINATIM (self), ht);
+       g_hash_table_unref (ht);
+
+       contents = GEOCODE_NOMINATIM_GET_CLASS (self)->query (GEOCODE_NOMINATIM (self),
+                                                             uri,
+                                                             cancellable,
+                                                             error);
+       if (contents != NULL) {
+               result = resolve_json (contents, error);
+               g_free (contents);
+       }
+
+       g_free (uri);
+
+       if (result == NULL)
+               return NULL;
+
+       place = _geocode_create_place_from_attributes (result);
+       g_hash_table_unref (result);
+
+       return place;
+}
+
+/******************************************************************************/
+
+G_LOCK_DEFINE_STATIC (backend_nominatim_gnome_lock);
+static GWeakRef backend_nominatim_gnome;
+
+/**
+ * geocode_nominatim_get_gnome:
+ *
+ * Gets a reference to the default Nominatim server on nominatim.gnome.org.
+ *
+ * This function is thread-safe.
+ *
+ * Returns: (transfer full): a new #GeocodeNominatim. Use g_object_unref() when done.
+ *
+ * Since: UNRELEASED
+ */
+GeocodeNominatim *
+geocode_nominatim_get_gnome (void)
+{
+       GeocodeNominatim *backend;
+
+       G_LOCK (backend_nominatim_gnome_lock);
+       backend = g_weak_ref_get (&backend_nominatim_gnome);
+       if (backend == NULL) {
+               backend = geocode_nominatim_new ("https://nominatim.gnome.org";,
+                                                "zeeshanak gnome org");
+               g_weak_ref_set (&backend_nominatim_gnome, backend);
+       }
+       G_UNLOCK (backend_nominatim_gnome_lock);
+
+       return backend;
+}
+
+/******************************************************************************/
+
+/**
+ * geocode_nominatim_new:
+ * @base_url: a the base URL of the Nominatim server.
+ * @maintainer_email_address: the email address of the software maintainer.
+ *
+ * Creates a new backend implementation for an online Nominatim server. See
+ * the documentation for #GeocodeNominatim:base-url and
+ * #GeocodeNominatim:maintainer-email-address.
+ *
+ * Returns: (transfer full): a new #GeocodeNominatim. Use g_object_unref() when done.
+ *
+ * Since: UNRELEASED
+ */
+GeocodeNominatim *
+geocode_nominatim_new (const char *base_url,
+                       const char *maintainer_email_address)
+{
+       g_return_val_if_fail (base_url != NULL, NULL);
+       g_return_val_if_fail (maintainer_email_address != NULL, NULL);
+
+       return GEOCODE_NOMINATIM (g_object_new (GEOCODE_TYPE_NOMINATIM,
+                                               "base-url", base_url,
+                                               "maintainer-email-address", maintainer_email_address,
+                                               NULL));
+}
+
+static void
+geocode_nominatim_init (GeocodeNominatim *object)
+{
+}
+
+static void
+geocode_nominatim_constructed (GObject *object)
+{
+       GeocodeNominatimPrivate *priv;
+
+       /* Chain up. */
+       G_OBJECT_CLASS (geocode_nominatim_parent_class)->constructed (object);
+
+       priv = geocode_nominatim_get_instance_private (GEOCODE_NOMINATIM (object));
+
+       /* Ensure our mandatory construction properties have been passed. */
+       g_assert (priv->base_url != NULL);
+       g_assert (priv->maintainer_email_address != NULL);
+}
+
+static void
+geocode_nominatim_get_property (GObject    *object,
+                                guint       property_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+       GeocodeNominatimPrivate *priv;
+
+       priv = geocode_nominatim_get_instance_private (GEOCODE_NOMINATIM (object));
+
+       switch ((GeocodeNominatimProperty) property_id) {
+       case PROP_BASE_URL:
+               g_value_set_string (value, priv->base_url);
+               break;
+       case PROP_MAINTAINER_EMAIL_ADDRESS:
+               g_value_set_string (value, priv->maintainer_email_address);
+               break;
+       default:
+               /* We don't have any other property... */
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+static void
+geocode_nominatim_set_property (GObject      *object,
+                                guint         property_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+       GeocodeNominatimPrivate *priv;
+
+       priv = geocode_nominatim_get_instance_private (GEOCODE_NOMINATIM (object));
+
+       switch ((GeocodeNominatimProperty) property_id) {
+       case PROP_BASE_URL:
+               /* Construct only. */
+               g_assert (priv->base_url == NULL);
+               priv->base_url = g_value_dup_string (value);
+               break;
+       case PROP_MAINTAINER_EMAIL_ADDRESS:
+               /* Construct only. */
+               g_assert (priv->maintainer_email_address == NULL);
+               priv->maintainer_email_address = g_value_dup_string (value);
+               break;
+       default:
+               /* We don't have any other property... */
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+static void
+geocode_nominatim_finalize (GObject *object)
+{
+       GeocodeNominatimPrivate *priv;
+
+       priv = geocode_nominatim_get_instance_private (GEOCODE_NOMINATIM (object));
+
+       g_free (priv->base_url);
+       g_free (priv->maintainer_email_address);
+
+       G_OBJECT_CLASS (geocode_nominatim_parent_class)->finalize (object);
+}
+
+static void
+geocode_backend_iface_init (GeocodeBackendInterface *iface)
+{
+       iface->forward_search         = geocode_nominatim_forward_search;
+       iface->forward_search_async   = geocode_nominatim_forward_search_async;
+       iface->forward_search_finish  = geocode_nominatim_forward_search_finish;
+
+       iface->reverse_resolve        = geocode_nominatim_reverse_resolve;
+       iface->reverse_resolve_async  = geocode_nominatim_reverse_resolve_async;
+       iface->reverse_resolve_finish = geocode_nominatim_reverse_resolve_finish;
+}
+
+static void
+geocode_nominatim_class_init (GeocodeNominatimClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->constructed  = geocode_nominatim_constructed;
+       object_class->finalize     = geocode_nominatim_finalize;
+       object_class->get_property = geocode_nominatim_get_property;
+       object_class->set_property = geocode_nominatim_set_property;
+
+       klass->query        = geocode_nominatim_query;
+       klass->query_async  = geocode_nominatim_query_async;
+       klass->query_finish = geocode_nominatim_query_finish;
+
+       /**
+        * GeocodeNominatim:base-url:
+        *
+        * The base URL of the Nominatim service, for example
+        * `https://nominatim.example.org`.
+        *
+        * Since: UNRELEASED
+        */
+       properties[PROP_BASE_URL] = g_param_spec_string ("base-url",
+                                                        "Base URL",
+                                                        "Base URL of the Nominatim service",
+                                                        NULL,
+                                                        (G_PARAM_READWRITE |
+                                                         G_PARAM_CONSTRUCT_ONLY |
+                                                         G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GeocodeNominatim:maintainer-email-address:
+        *
+        * E-mail address of the maintainer of the software making the
+        * geocoding requests to the  Nominatim server. This is used to contact
+        * them in the event of a problem with their usage. See
+        * [the Nominatim API](http://wiki.openstreetmap.org/wiki/Nominatim).
+        *
+        * Since: UNRELEASED
+        */
+       properties[PROP_MAINTAINER_EMAIL_ADDRESS] =
+           g_param_spec_string ("maintainer-email-address",
+                                "Maintainer e-mail address",
+                                "E-mail address of the maintainer",
+                                NULL,
+                                (G_PARAM_READWRITE |
+                                 G_PARAM_CONSTRUCT_ONLY |
+                                 G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_properties (object_class,
+                                          G_N_ELEMENTS (properties), properties);
+}
diff --git a/geocode-glib/geocode-nominatim.h b/geocode-glib/geocode-nominatim.h
new file mode 100644
index 0000000..df6aa5a
--- /dev/null
+++ b/geocode-glib/geocode-nominatim.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2011-2016 Bastien Nocera
+ * Copyright (C) 2016 Collabora Ltd.
+ *
+ * The geocode-glib library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The geocode-glib library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301  USA.
+ *
+ * Authors: Bastien Nocera <hadess hadess net>
+ *          Aleksander Morgado <aleksander morgado collabora co uk>
+ *          Philip Withnall <philip withnall collabora co uk>
+ */
+
+#ifndef GEOCODE_NOMINATIM_H
+#define GEOCODE_NOMINATIM_H
+
+#include <glib.h>
+#include <gio/gio.h>
+#include "geocode-place.h"
+
+G_BEGIN_DECLS
+
+/**
+ * GeocodeNominatim:
+ *
+ * All the fields in the #GeocodeNominatim structure are private and should
+ * never be accessed directly.
+ *
+ * Since: UNRELEASED
+ */
+#define GEOCODE_TYPE_NOMINATIM (geocode_nominatim_get_type ())
+G_DECLARE_DERIVABLE_TYPE (GeocodeNominatim, geocode_nominatim, GEOCODE, NOMINATIM, GObject)
+
+/**
+ * GeocodeNominatimClass:
+ * @query: synchronous query function to override network `GET` requests.
+ * @query_async: asynchronous version of @query.
+ * @query_finish: asynchronous finish function for @query_async.
+ *
+ * #GeocodeNominatim allows derived classes to override its query functions,
+ * which are called for each network request the Nominatim client makes. All
+ * network requests are `GET`s with no request body; just a URI. The default
+ * implementation makes the requests internally, but derived classes may want
+ * to override these queries to check the URIs for testing, for example.
+ *
+ * Applications should not normally have to derive #GeocodeNominatim; these
+ * virtual methods are mainly intended for testing.
+ *
+ * Since: UNRELEASED
+ */
+struct _GeocodeNominatimClass {
+       GObjectClass parent_class;
+
+       gchar *(*query)        (GeocodeNominatim    *self,
+                               const gchar         *uri,
+                               GCancellable        *cancellable,
+                               GError             **error);
+
+       void   (*query_async)  (GeocodeNominatim    *self,
+                               const gchar         *uri,
+                               GCancellable        *cancellable,
+                               GAsyncReadyCallback  callback,
+                               gpointer             user_data);
+
+       gchar *(*query_finish) (GeocodeNominatim    *self,
+                               GAsyncResult        *res,
+                               GError             **error);
+};
+
+GeocodeNominatim *geocode_nominatim_new (const gchar *base_url,
+                                         const gchar *maintainer_email_address);
+
+GeocodeNominatim *geocode_nominatim_get_gnome (void);
+
+G_END_DECLS
+
+#endif /* GEOCODE_NOMINATIM_H */
diff --git a/geocode-glib/geocode-reverse.c b/geocode-glib/geocode-reverse.c
index 8cccdd4..a20ce16 100644
--- a/geocode-glib/geocode-reverse.c
+++ b/geocode-glib/geocode-reverse.c
@@ -20,17 +20,17 @@
 
  */
 
+#include <config.h>
+
 #include <string.h>
-#include <errno.h>
 #include <locale.h>
 #include <gio/gio.h>
-#include <json-glib/json-glib.h>
-#include <libsoup/soup.h>
-#include <config.h>
 #include <glib/gi18n-lib.h>
 #include <geocode-glib/geocode-glib.h>
 #include <geocode-glib/geocode-error.h>
 #include <geocode-glib/geocode-reverse.h>
+#include <geocode-glib/geocode-backend.h>
+#include <geocode-glib/geocode-nominatim.h>
 #include <geocode-glib/geocode-glib-private.h>
 
 /**
@@ -43,8 +43,8 @@
  **/
 
 struct _GeocodeReversePrivate {
-       GHashTable *ht;
-        SoupSession *soup_session;
+       GeocodeLocation *location;
+       GeocodeBackend  *backend;
 };
 
 G_DEFINE_TYPE (GeocodeReverse, geocode_reverse, G_TYPE_OBJECT)
@@ -54,8 +54,8 @@ geocode_reverse_finalize (GObject *gobject)
 {
        GeocodeReverse *object = (GeocodeReverse *) gobject;
 
-       g_clear_pointer (&object->priv->ht, g_hash_table_destroy);
-        g_clear_object (&object->priv->soup_session);
+       g_clear_object (&object->priv->location);
+       g_clear_object (&object->priv->backend);
 
        G_OBJECT_CLASS (geocode_reverse_parent_class)->finalize (gobject);
 }
@@ -77,9 +77,6 @@ static void
 geocode_reverse_init (GeocodeReverse *object)
 {
        object->priv = G_TYPE_INSTANCE_GET_PRIVATE ((object), GEOCODE_TYPE_REVERSE, GeocodeReversePrivate);
-       object->priv->ht = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                                 g_free, g_free);
-       object->priv->soup_session = _geocode_glib_build_soup_session ();
 }
 
 /**
@@ -95,326 +92,37 @@ GeocodeReverse *
 geocode_reverse_new_for_location (GeocodeLocation *location)
 {
        GeocodeReverse *object;
-       char coord[G_ASCII_DTOSTR_BUF_SIZE];
-       char *lat;
-       char *lon;
 
-       lat = g_strdup (g_ascii_dtostr (coord,
-                                       G_ASCII_DTOSTR_BUF_SIZE,
-                                       geocode_location_get_latitude (location)));
+       g_return_val_if_fail (GEOCODE_IS_LOCATION (location), NULL);
 
-       lon = g_strdup (g_ascii_dtostr (coord,
-                                       G_ASCII_DTOSTR_BUF_SIZE,
-                                       geocode_location_get_longitude (location)));
        object = g_object_new (GEOCODE_TYPE_REVERSE, NULL);
-
-       g_hash_table_insert (object->priv->ht, g_strdup ("lat"), lat);
-       g_hash_table_insert (object->priv->ht, g_strdup ("lon"), lon);
+       object->priv->location = g_object_ref (location);
 
        return object;
 }
 
 static void
-insert_bounding_box_element (GHashTable *ht,
-                            GType       value_type,
-                            const char *name,
-                            JsonReader *reader)
+ensure_backend (GeocodeReverse *object)
 {
-       if (value_type == G_TYPE_STRING) {
-               const char *bbox_val;
-
-               bbox_val = json_reader_get_string_value (reader);
-               g_hash_table_insert (ht, g_strdup (name), g_strdup (bbox_val));
-       } else if (value_type == G_TYPE_DOUBLE) {
-               gdouble bbox_val;
-
-               bbox_val = json_reader_get_double_value (reader);
-               g_hash_table_insert(ht, g_strdup (name), g_strdup_printf ("%lf", bbox_val));
-       } else if (value_type == G_TYPE_INT64) {
-               gint64 bbox_val;
-
-               bbox_val = json_reader_get_double_value (reader);
-               g_hash_table_insert(ht, g_strdup (name), g_strdup_printf ("%"G_GINT64_FORMAT, bbox_val));
-       } else {
-               g_debug ("Unhandled node type %s for %s", g_type_name (value_type), name);
-       }
-}
-
-void
-_geocode_read_nominatim_attributes (JsonReader *reader,
-                                    GHashTable *ht)
-{
-       char **members;
-       guint i;
-        gboolean is_address;
-        const char *house_number = NULL;
-
-       is_address = (g_strcmp0 (json_reader_get_member_name (reader), "address") == 0);
-
-       members = json_reader_list_members (reader);
-        if (members == NULL) {
-                json_reader_end_member (reader);
-                return;
-        }
-
-       for (i = 0; members[i] != NULL; i++) {
-                const char *value = NULL;
-
-                json_reader_read_member (reader, members[i]);
-
-                if (json_reader_is_value (reader)) {
-                        JsonNode *node = json_reader_get_value (reader);
-                        if (json_node_get_value_type (node) == G_TYPE_STRING) {
-                                value = json_node_get_string (node);
-                                if (value && *value == '\0')
-                                        value = NULL;
-                        }
-                }
-
-                if (value != NULL) {
-                        g_hash_table_insert (ht, g_strdup (members[i]), g_strdup (value));
-
-                        if (i == 0 && is_address) {
-                               if (g_strcmp0 (members[i], "house_number") != 0)
-                                        /* Since Nominatim doesn't give us a short name,
-                                         * we use the first component of address as name.
-                                         */
-                                        g_hash_table_insert (ht, g_strdup ("name"), g_strdup (value));
-                                else
-                                        house_number = value;
-                        } else if (house_number != NULL && g_strcmp0 (members[i], "road") == 0) {
-                                gboolean number_after;
-                                char *name;
-
-                                number_after = _geocode_object_is_number_after_street ();
-                                name = g_strdup_printf ("%s %s",
-                                                        number_after ? value : house_number,
-                                                        number_after ? house_number : value);
-                                g_hash_table_insert (ht, g_strdup ("name"), name);
-                        }
-                } else if (g_strcmp0 (members[i], "boundingbox") == 0) {
-                        JsonNode *node;
-                        GType value_type;
-
-                        json_reader_read_element (reader, 0);
-                        node = json_reader_get_value (reader);
-                        value_type = json_node_get_value_type (node);
-
-                        insert_bounding_box_element (ht, value_type, "boundingbox-bottom", reader);
-                        json_reader_end_element (reader);
-
-                        json_reader_read_element (reader, 1);
-                        insert_bounding_box_element (ht, value_type, "boundingbox-top", reader);
-                        json_reader_end_element (reader);
-
-                        json_reader_read_element (reader, 2);
-                        insert_bounding_box_element (ht, value_type, "boundingbox-left", reader);
-                        json_reader_end_element (reader);
-
-                        json_reader_read_element (reader, 3);
-                        insert_bounding_box_element (ht, value_type, "boundingbox-right", reader);
-                        json_reader_end_element (reader);
-                }
-                json_reader_end_member (reader);
-        }
-
-       g_strfreev (members);
-
-        if (json_reader_read_member (reader, "address"))
-                _geocode_read_nominatim_attributes (reader, ht);
-        json_reader_end_member (reader);
-}
-
-static GHashTable *
-resolve_json (const char *contents,
-              GError    **error)
-{
-       GHashTable *ret;
-       JsonParser *parser;
-       JsonNode *root;
-       JsonReader *reader;
-
-       ret = NULL;
-
-       parser = json_parser_new ();
-       if (json_parser_load_from_data (parser, contents, -1, error) == FALSE) {
-               g_object_unref (parser);
-               return ret;
-       }
-
-       root = json_parser_get_root (parser);
-       reader = json_reader_new (root);
-
-       if (json_reader_read_member (reader, "error")) {
-               const char *msg;
-
-                msg = json_reader_get_string_value (reader);
-                json_reader_end_member (reader);
-               if (msg && *msg == '\0')
-                       msg = NULL;
-
-               g_set_error_literal (error,
-                                     GEOCODE_ERROR,
-                                     GEOCODE_ERROR_NOT_SUPPORTED,
-                                     msg ? msg : "Query not supported");
-               g_object_unref (parser);
-               g_object_unref (reader);
-               return NULL;
-       }
-
-       ret = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                    g_free, g_free);
-
-        _geocode_read_nominatim_attributes (reader, ret);
-
-       g_object_unref (parser);
-       g_object_unref (reader);
-
-       return ret;
-}
-
-static void
-on_query_data_loaded (SoupSession *session,
-                      SoupMessage *query,
-                      gpointer     user_data)
-{
-       GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
-       GError *error = NULL;
-       char *contents;
-       GeocodePlace *ret;
-       GHashTable *attributes;
-
-        if (query->status_code != SOUP_STATUS_OK) {
-               g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                                     query->reason_phrase ? query->reason_phrase : "Query failed");
-                g_simple_async_result_take_error (simple, error);
-               g_simple_async_result_complete_in_idle (simple);
-               g_object_unref (simple);
-               return;
-       }
-
-        contents = g_strndup (query->response_body->data, query->response_body->length);
-       attributes = resolve_json (contents, &error);
-
-       if (attributes == NULL) {
-               g_simple_async_result_take_error (simple, error);
-               g_simple_async_result_complete_in_idle (simple);
-               g_object_unref (simple);
-               g_free (contents);
-               return;
-       }
-
-       /* Now that we can parse the result, save it to cache */
-       _geocode_glib_cache_save (query, contents);
-       g_free (contents);
-
-        ret = _geocode_create_place_from_attributes (attributes);
-        g_hash_table_destroy (attributes);
-       g_simple_async_result_set_op_res_gpointer (simple, ret, NULL);
-
-       g_simple_async_result_complete_in_idle (simple);
-       g_object_unref (simple);
+       /* If no backend is specified, default to the GNOME Nominatim backend */
+       if (object->priv->backend == NULL)
+               object->priv->backend = GEOCODE_BACKEND (geocode_nominatim_get_gnome ());
 }
 
 static void
-on_cache_data_loaded (GObject      *source_object,
-                     GAsyncResult *res,
-                     gpointer      user_data)
+backend_reverse_resolve_ready (GeocodeBackend *backend,
+                               GAsyncResult   *res,
+                               GTask          *task)
 {
-       GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
-       GFile *cache;
+       GeocodePlace *place;
        GError *error = NULL;
-       char *contents;
-       GHashTable *result;
-
-       cache = G_FILE (source_object);
-       if (g_file_load_contents_finish (cache,
-                                        res,
-                                        &contents,
-                                        NULL,
-                                        NULL,
-                                        NULL) == FALSE) {
-               GObject *object;
-               SoupMessage *query;
-
-                object = g_async_result_get_source_object (G_ASYNC_RESULT (simple));
-               query = g_object_get_data (G_OBJECT (cache), "query");
-                g_object_ref (query); /* soup_session_queue_message steals ref */
-               soup_session_queue_message (GEOCODE_REVERSE (object)->priv->soup_session,
-                                            query,
-                                           on_query_data_loaded,
-                                           simple);
-               return;
-       }
-
-       result = resolve_json (contents, &error);
-       g_free (contents);
-
-       if (result == NULL) {
-               g_simple_async_result_take_error (simple, error);
-        } else {
-                GeocodePlace *place;
-
-                place = _geocode_create_place_from_attributes (result);
-                g_hash_table_destroy (result);
-                g_simple_async_result_set_op_res_gpointer (simple, place, NULL);
-        }
-
-       g_simple_async_result_complete_in_idle (simple);
-       g_object_unref (simple);
-}
 
-static void
-copy_item (char       *key,
-          char       *value,
-          GHashTable *ret)
-{
-       g_hash_table_insert (ret, key, value);
-}
-
-GHashTable *
-_geocode_glib_dup_hash_table (GHashTable *ht)
-{
-       GHashTable *ret;
-
-       ret = g_hash_table_new (g_str_hash, g_str_equal);
-       g_hash_table_foreach (ht, (GHFunc) copy_item, ret);
-
-       return ret;
-}
-
-static SoupMessage *
-get_resolve_query_for_params (GHashTable  *orig_ht)
-{
-       SoupMessage *ret;
-       GHashTable *ht;
-       char *locale;
-       char *params, *uri;
-
-       ht = _geocode_glib_dup_hash_table (orig_ht);
-
-       g_hash_table_insert (ht, (gpointer) "format", (gpointer) "json");
-       g_hash_table_insert (ht, (gpointer) "email", (gpointer) "zeeshanak gnome org");
-       g_hash_table_insert (ht, (gpointer) "addressdetails", (gpointer) "1");
-
-       locale = NULL;
-       if (g_hash_table_lookup (ht, "accept-language") == NULL) {
-               locale = _geocode_object_get_lang ();
-               if (locale)
-                       g_hash_table_insert (ht, (gpointer) "accept-language", locale);
-       }
-
-       params = soup_form_encode_hash (ht);
-       g_hash_table_destroy (ht);
-       g_free (locale);
-
-       uri = g_strdup_printf ("https://nominatim.gnome.org/reverse?%s";, params);
-       g_free (params);
-
-       ret = soup_message_new ("GET", uri);
-       g_free (uri);
-
-       return ret;
+       place = geocode_backend_reverse_resolve_finish (backend, res, &error);
+       if (place)
+               g_task_return_pointer (task, place, g_object_unref);
+       else
+               g_task_return_error (task, error);
+       g_object_unref (task);
 }
 
 /**
@@ -433,42 +141,25 @@ get_resolve_query_for_params (GHashTable  *orig_ht)
  **/
 void
 geocode_reverse_resolve_async (GeocodeReverse     *object,
-                              GCancellable       *cancellable,
-                              GAsyncReadyCallback callback,
-                              gpointer            user_data)
+                               GCancellable       *cancellable,
+                               GAsyncReadyCallback callback,
+                               gpointer            user_data)
 {
-       GSimpleAsyncResult *simple;
-       SoupMessage *query;
-       char *cache_path;
+       GTask *task;
 
        g_return_if_fail (GEOCODE_IS_REVERSE (object));
-
-       simple = g_simple_async_result_new (G_OBJECT (object),
-                                           callback,
-                                           user_data,
-                                           geocode_reverse_resolve_async);
-       g_simple_async_result_set_check_cancellable (simple, cancellable);
-
-       query = get_resolve_query_for_params (object->priv->ht);
-
-       cache_path = _geocode_glib_cache_path_for_query (query);
-       if (cache_path == NULL) {
-               soup_session_queue_message (object->priv->soup_session,
-                                            query,
-                                           on_query_data_loaded,
-                                           simple);
-       } else {
-               GFile *cache;
-
-               cache = g_file_new_for_path (cache_path);
-               g_object_set_data_full (G_OBJECT (cache), "query", query, (GDestroyNotify) g_object_unref);
-               g_file_load_contents_async (cache,
-                                           cancellable,
-                                           on_cache_data_loaded,
-                                           simple);
-               g_object_unref (cache);
-               g_free (cache_path);
-       }
+       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+       ensure_backend (object);
+       g_assert (object->priv->backend != NULL);
+
+       task = g_task_new (object, cancellable, callback, user_data);
+       geocode_backend_reverse_resolve_async (object->priv->backend,
+                                              object->priv->location,
+                                              cancellable,
+                                              (GAsyncReadyCallback) backend_reverse_resolve_ready,
+                                              g_object_ref (task));
+       g_object_unref (task);
 }
 
 /**
@@ -484,19 +175,14 @@ geocode_reverse_resolve_async (GeocodeReverse     *object,
  **/
 GeocodePlace *
 geocode_reverse_resolve_finish (GeocodeReverse *object,
-                               GAsyncResult   *res,
-                               GError        **error)
+                                GAsyncResult   *res,
+                                GError        **error)
 {
-       GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
-
        g_return_val_if_fail (GEOCODE_IS_REVERSE (object), NULL);
+       g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-       g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == geocode_reverse_resolve_async);
-
-       if (g_simple_async_result_propagate_error (simple, error))
-               return NULL;
-
-       return g_simple_async_result_get_op_res_gpointer (simple);
+       return GEOCODE_PLACE (g_task_propagate_pointer (G_TASK (res), error));
 }
 
 /**
@@ -512,43 +198,37 @@ geocode_reverse_resolve_finish (GeocodeReverse *object,
  **/
 GeocodePlace *
 geocode_reverse_resolve (GeocodeReverse *object,
-                        GError        **error)
+                         GError        **error)
 {
-       SoupMessage *query;
-       char *contents;
-       GHashTable *result;
-       GeocodePlace *place;
-       gboolean to_cache = FALSE;
-
        g_return_val_if_fail (GEOCODE_IS_REVERSE (object), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-       query = get_resolve_query_for_params (object->priv->ht);
-
-       if (_geocode_glib_cache_load (query, &contents) == FALSE) {
-                if (soup_session_send_message (object->priv->soup_session,
-                                               query) != SOUP_STATUS_OK) {
-                        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                                             query->reason_phrase ? query->reason_phrase : "Query failed");
-                        g_object_unref (query);
-                        return NULL;
-                }
-                contents = g_strndup (query->response_body->data, query->response_body->length);
-
-               to_cache = TRUE;
-       }
+       ensure_backend (object);
+       g_assert (object->priv->backend != NULL);
 
-       result = resolve_json (contents, error);
-       if (to_cache && result != NULL)
-               _geocode_glib_cache_save (query, contents);
-
-       g_free (contents);
-       g_object_unref (query);
-
-       if (result == NULL)
-               return NULL;
+       return geocode_backend_reverse_resolve (object->priv->backend,
+                                               object->priv->location,
+                                               NULL,
+                                               error);
+}
 
-       place = _geocode_create_place_from_attributes (result);
-       g_hash_table_destroy (result);
+/**
+ * geocode_reverse_set_backend:
+ * @object: a #GeocodeReverse representing a query
+ * @backend: (nullable): a #GeocodeBackend, or %NULL to use the default one.
+ *
+ * Specifies the backend to use in the reverse geocoding operation.
+ *
+ * If none is given, the default GNOME Nominatim server is used.
+ *
+ * Since: UNRELEASED
+ */
+void
+geocode_reverse_set_backend (GeocodeReverse *object,
+                             GeocodeBackend *backend)
+{
+       g_return_if_fail (GEOCODE_IS_REVERSE (object));
+       g_return_if_fail (backend == NULL || GEOCODE_IS_BACKEND (backend));
 
-       return place;
+       g_set_object (&object->priv->backend, backend);
 }
diff --git a/geocode-glib/geocode-reverse.h b/geocode-glib/geocode-reverse.h
index 9aa9244..884c849 100644
--- a/geocode-glib/geocode-reverse.h
+++ b/geocode-glib/geocode-reverse.h
@@ -26,6 +26,7 @@
 #include <glib.h>
 #include <gio/gio.h>
 #include "geocode-place.h"
+#include "geocode-backend.h"
 
 G_BEGIN_DECLS
 
@@ -67,6 +68,9 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (GeocodeReverse, g_object_unref)
 
 GeocodeReverse *geocode_reverse_new_for_location (GeocodeLocation *location);
 
+void geocode_reverse_set_backend (GeocodeReverse *object,
+                                  GeocodeBackend *backend);
+
 void geocode_reverse_resolve_async (GeocodeReverse      *object,
                                    GCancellable        *cancellable,
                                    GAsyncReadyCallback  callback,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]