[geocode-glib] location: Add parsing of geo URI



commit df0554941673a08faeb5ba076771c7ccb42c9971
Author: Jonas Danielsson <jonas threetimestwo org>
Date:   Thu Dec 12 21:55:08 2013 +0100

    location: Add parsing of geo URI
    
    This patch adds parsing of geo URI to geocode-location.
    
    The RFC for the geo URI scheme can be found at:
    http://www.rfc-editor.org/rfc/rfc5870.txt
    
    https://bugzilla.gnome.org/show_bug.cgi?id=706130

 geocode-glib/Makefile.am          |    3 +-
 geocode-glib/geocode-glib.symbols |    3 +
 geocode-glib/geocode-location.c   |  270 +++++++++++++++++++++++++++++++++++++
 geocode-glib/geocode-location.h   |   23 +++-
 geocode-glib/test-geouri.c        |  157 +++++++++++++++++++++
 5 files changed, 452 insertions(+), 4 deletions(-)
---
diff --git a/geocode-glib/Makefile.am b/geocode-glib/Makefile.am
index 80cda77..ef94fc3 100644
--- a/geocode-glib/Makefile.am
+++ b/geocode-glib/Makefile.am
@@ -103,9 +103,10 @@ CLEANFILES += $(gir_DATA) $(typelib_DATA)
 
 endif # HAVE_INTROSPECTION
 
-TEST_PROGS += test-gcglib
+TEST_PROGS += test-geouri test-gcglib
 noinst_PROGRAMS = $(TEST_PROGS)
 
+test_geouri_LDADD = libgeocode-glib.la $(GEOCODE_LIBS)
 test_gcglib_LDADD = libgeocode-glib.la $(GEOCODE_LIBS)
 
 MAINTAINERCLEANFILES = Makefile.in
diff --git a/geocode-glib/geocode-glib.symbols b/geocode-glib/geocode-glib.symbols
index 38fc28b..a285c32 100644
--- a/geocode-glib/geocode-glib.symbols
+++ b/geocode-glib/geocode-glib.symbols
@@ -1,7 +1,10 @@
 geocode_location_get_type
 geocode_location_crs_get_type
+geocode_location_uri_scheme_get_type
 geocode_location_new
 geocode_location_new_with_description
+geocode_location_set_from_uri
+geocode_location_to_uri
 geocode_location_get_accuracy
 geocode_location_get_description
 geocode_location_get_distance_from
diff --git a/geocode-glib/geocode-location.c b/geocode-glib/geocode-location.c
index 9fd9ad1..133c992 100644
--- a/geocode-glib/geocode-location.c
+++ b/geocode-glib/geocode-location.c
@@ -24,6 +24,7 @@
 #include <geocode-glib/geocode-error.h>
 #include <geocode-glib/geocode-enum-types.h>
 #include <math.h>
+#include <string.h>
 #include "geocode-location.h"
 
 #define EARTH_RADIUS_KM 6372.795
@@ -201,6 +202,192 @@ geocode_location_set_property(GObject      *object,
         }
 }
 
+/*
+  From RFC 5870:
+      Both 'crs' and 'u' parameters MUST NOT appear more than once each.
+      The 'crs' and 'u' parameters MUST be given before any other
+      parameters that may be defined in future extensions.  The 'crs'
+      parameter MUST be given first if both 'crs' and 'u' are used.
+ */
+static void
+parse_geo_uri_parameters (GeocodeLocation *loc,
+                          const char      *params,
+                          GError         **error)
+{
+        char **parameters;
+        char *endptr;
+        char *val;
+        char *u = NULL;
+        char *crs = NULL;
+
+        parameters = g_strsplit (params, ";", 2);
+        if (parameters[0] == NULL)
+                goto err;
+
+        if (g_str_has_prefix (parameters[0], "u=")) {
+                /*
+                 * if u parameter is first, then there should not be any more
+                 * parameters.
+                 */
+                if (parameters[1] != NULL)
+                        goto err;
+
+                u = parameters[0];
+        } else if (g_str_has_prefix (parameters[0], "crs=")) {
+                /*
+                 * if crs parameter is first, then the next should be the u
+                 * parameter or none.
+                 */
+                crs = parameters[0];
+                if (parameters[1] != NULL){
+
+                        if (!g_str_has_prefix (parameters[1], "u="))
+                                goto err;
+
+                        u = parameters[1];
+                }
+        } else {
+                goto err;
+        }
+
+        if (u != NULL) {
+                val = u + 2; /* len of 'u=' */
+                loc->priv->accuracy = g_ascii_strtod (val, &endptr);
+                if (*endptr != '\0')
+                        goto err;
+        }
+
+        if (crs != NULL) {
+                val = crs + 4; /* len of 'crs=' */
+                if (g_strcmp0 (val, "wgs84"))
+                        goto err;
+        }
+        return;
+
+ err:
+       g_set_error_literal (error,
+                            GEOCODE_ERROR,
+                            GEOCODE_ERROR_PARSE,
+                            "Failed to parse geo URI parameters");
+}
+
+/*
+   From RFC 5870:
+      geo-URI       = geo-scheme ":" geo-path
+      geo-scheme    = "geo"
+      geo-path      = coordinates p
+      coordinates   = coord-a "," coord-b [ "," coord-c ]
+
+      [...]
+
+      The value of "-0" for <num> is allowed and is identical to "0".
+
+      In case the URI identifies a location in the default CRS of WGS-84,
+      the <coordinates> sub-components are further restricted as follows:
+
+      coord-a        = latitude
+      coord-b        = longitude
+      coord-c        = altitude
+
+      latitude       = [ "-" ] 1*2DIGIT [ "." 1*DIGIT ]
+      longitude      = [ "-" ] 1*3DIGIT [ "." 1*DIGIT ]
+      altitude       = [ "-" ] 1*DIGIT [ "." 1*DIGIT ]
+
+       p             = [ crsp ] [ uncp ] *parameter
+       crsp          = ";crs=" crslabel
+       crslabel      = "wgs84" / labeltext
+       uncp          = ";u=" uval
+       uval          = pnum
+
+       parameter     = ";" pname [ "=" pvalue ]
+       pname         = labeltext
+       pvalue        = 1*paramchar
+       paramchar     = p-unreserved / unreserved / pct-encoded
+
+       labeltext     = 1*( alphanum / "-" )
+       pnum          = 1*DIGIT [ "." 1*DIGIT ]
+       num           = [ "-" ] pnum
+       unreserved    = alphanum / mark
+       mark          = "-" / "_" / "." / "!" / "~" / "*" /
+                        "'" / "(" / ")"
+       pct-encoded   = "%" HEXDIG HEXDIG
+*/
+static void
+parse_geo_uri (GeocodeLocation *loc,
+               const char      *uri,
+               GError         **error)
+{
+        const char *uri_part;
+        char *end_ptr;
+        char *next_token;
+        const char *s;
+
+        /* bail out if we encounter whitespace in uri */
+        s = uri;
+        while (*s) {
+                if (g_ascii_isspace (*s++))
+                        goto err;
+        }
+
+        uri_part = (const char *) uri + strlen("geo") + 1;
+
+        /* g_ascii_strtod is locale safe */
+        loc->priv->latitude = g_ascii_strtod (uri_part, &end_ptr);
+        if (*end_ptr != ',' || *end_ptr == *uri_part) {
+                goto err;
+        }
+        next_token = end_ptr + 1;
+
+        loc->priv->longitude = g_ascii_strtod (next_token, &end_ptr);
+        if (*end_ptr == *next_token) {
+                goto err;
+        }
+        if (*end_ptr == ',') {
+                next_token = end_ptr + 1;
+                loc->priv->altitude = g_ascii_strtod (next_token, &end_ptr);
+                if (*end_ptr == *next_token) {
+                        goto err;
+                }
+        }
+        if (*end_ptr == ';') {
+                next_token = end_ptr + 1;
+                parse_geo_uri_parameters (loc, next_token, error);
+                return;
+        } else if (*end_ptr == '\0') {
+                return;
+        }
+ err:
+        g_set_error_literal (error,
+                             GEOCODE_ERROR,
+                             GEOCODE_ERROR_PARSE,
+                             "Failed to parse geo URI");
+}
+
+static void
+parse_uri (GeocodeLocation *location,
+           const char      *uri,
+           GError         **error)
+{
+        char *scheme;
+
+        scheme = g_uri_parse_scheme (uri);
+        if (scheme == NULL)
+                goto err;
+
+        if (g_strcmp0 (scheme, "geo") == 0)
+                parse_geo_uri (location, uri, error);
+        else
+                goto err;
+
+        return;
+
+ err:
+        g_set_error_literal (error,
+                             GEOCODE_ERROR,
+                             GEOCODE_ERROR_NOT_SUPPORTED,
+                             "Unsupported or invalid URI scheme");
+}
+
 static void
 geocode_location_finalize (GObject *glocation)
 {
@@ -343,6 +530,7 @@ geocode_location_init (GeocodeLocation *location)
         g_get_current_time (&tv);
         location->priv->timestamp = tv.tv_sec;
         location->priv->altitude = GEOCODE_LOCATION_ALTITUDE_UNKNOWN;
+        location->priv->accuracy = GEOCODE_LOCATION_ACCURACY_UNKNOWN;
         location->priv->crs = GEOCODE_LOCATION_CRS_WGS84;
 }
 
@@ -394,6 +582,28 @@ geocode_location_new_with_description (gdouble     latitude,
 }
 
 /**
+ * geocode_location_set_from_uri:
+ * @loc: a #GeocodeLocation
+ * @uri: a URI mapping out a location
+ * @error: #GError for error reporting, or %NULL to ignore
+ *
+ * Initialize a #GeocodeLocation object with the given @uri.
+ *
+ * Returns: %TRUE on success and %FALSE on error.
+ **/
+gboolean
+geocode_location_set_from_uri (GeocodeLocation *loc,
+                               const char      *uri,
+                               GError         **error)
+{
+        parse_uri (loc, uri, error);
+        if (*error != NULL)
+                return FALSE;
+
+        return TRUE;
+}
+
+/**
  * geocode_location_set_description:
  * @loc: a #GeocodeLocation
  * @description: a description for the location
@@ -525,6 +735,66 @@ geocode_location_get_timestamp (GeocodeLocation *loc)
         return loc->priv->timestamp;
 }
 
+static char *
+geo_uri_from_location (GeocodeLocation *loc)
+{
+        char *uri;
+        char *coords;
+        char *params;
+        char *crs = "wgs84";
+        char lat[G_ASCII_DTOSTR_BUF_SIZE];
+        char lon[G_ASCII_DTOSTR_BUF_SIZE];
+        char alt[G_ASCII_DTOSTR_BUF_SIZE];
+        char acc[G_ASCII_DTOSTR_BUF_SIZE];
+
+        g_return_val_if_fail (GEOCODE_IS_LOCATION (loc), NULL);
+
+        g_ascii_dtostr (lat, G_ASCII_DTOSTR_BUF_SIZE, loc->priv->latitude);
+        g_ascii_dtostr (lon, G_ASCII_DTOSTR_BUF_SIZE, loc->priv->longitude);
+
+        if (loc->priv->altitude != GEOCODE_LOCATION_ALTITUDE_UNKNOWN) {
+                g_ascii_dtostr (alt, G_ASCII_DTOSTR_BUF_SIZE,
+                                loc->priv->altitude);
+                coords = g_strdup_printf ("%s,%s,%s", lat, lon, alt);
+        } else {
+                coords = g_strdup_printf ("%s,%s", lat, lon);
+        }
+
+        if (loc->priv->accuracy != GEOCODE_LOCATION_ACCURACY_UNKNOWN) {
+                g_ascii_dtostr (acc, G_ASCII_DTOSTR_BUF_SIZE,
+                                loc->priv->accuracy);
+                params = g_strdup_printf (";crs=%s;u=%s", crs, acc);
+        } else {
+                params = g_strdup_printf (";crs=%s", crs);
+        }
+
+        uri = g_strconcat ("geo:", coords, params, NULL);
+        g_free (coords);
+        g_free (params);
+
+        return uri;
+}
+
+/**
+ * geocode_location_to_uri:
+ * @loc: a #GeocodeLocation
+ * @scheme: the scheme of the requested URI
+ *
+ * Creates a URI representing @loc in the scheme specified in @scheme.
+ *
+ * Returns: a URI representing the location. The returned string should be freed
+ * with g_free() when no longer needed.
+ **/
+char *
+geocode_location_to_uri (GeocodeLocation *loc,
+                         GeocodeLocationURIScheme scheme)
+{
+        g_return_val_if_fail (GEOCODE_IS_LOCATION (loc), NULL);
+        g_return_val_if_fail (scheme == GEOCODE_LOCATION_URI_SCHEME_GEO, NULL);
+
+        return geo_uri_from_location (loc);
+}
+
 /**
  * geocode_location_get_distance_from:
  * @loca: a #GeocodeLocation
diff --git a/geocode-glib/geocode-location.h b/geocode-glib/geocode-location.h
index c7ab25c..701270d 100644
--- a/geocode-glib/geocode-location.h
+++ b/geocode-glib/geocode-location.h
@@ -63,6 +63,16 @@ struct _GeocodeLocationClass {
 };
 
 /**
+ * GeocodeLocationURIScheme:
+ * @GEOCODE_LOCATION_URI_SCHEME_GEO: The 'geo' URI scheme, RFC 5870
+ *
+ * The URI scheme for this location.
+ */
+typedef enum {
+       GEOCODE_LOCATION_URI_SCHEME_GEO = 0
+} GeocodeLocationURIScheme;
+
+/**
  * GeocodeLocationCRS:
  * @GEOCODE_LOCATION_CRS_WGS84: CRS is World Geodetic System, standard for Earth.
  *
@@ -123,15 +133,22 @@ typedef enum {
 
 #define GEOCODE_TYPE_LOCATION (geocode_location_get_type ())
 
-GeocodeLocation *geocode_location_new (gdouble latitude,
-                                       gdouble longitude,
-                                       gdouble accuracy);
+GeocodeLocation *geocode_location_new                  (gdouble latitude,
+                                                        gdouble longitude,
+                                                        gdouble accuracy);
 
 GeocodeLocation *geocode_location_new_with_description (gdouble     latitude,
                                                         gdouble     longitude,
                                                         gdouble     accuracy,
                                                         const char *description);
 
+gboolean geocode_location_set_from_uri                 (GeocodeLocation *loc,
+                                                        const char      *uri,
+                                                        GError         **error);
+
+char * geocode_location_to_uri                         (GeocodeLocation *loc,
+                                                        GeocodeLocationURIScheme scheme);
+
 double geocode_location_get_distance_from              (GeocodeLocation *loca,
                                                         GeocodeLocation *locb);
 
diff --git a/geocode-glib/test-geouri.c b/geocode-glib/test-geouri.c
new file mode 100644
index 0000000..97f1b89
--- /dev/null
+++ b/geocode-glib/test-geouri.c
@@ -0,0 +1,157 @@
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <stdlib.h>
+#include <gio/gio.h>
+#include <geocode-glib/geocode-glib.h>
+#include <geocode-glib/geocode-glib-private.h>
+
+struct uri {
+    const char *uri;
+    gboolean valid;
+};
+
+static struct uri uris[] = {
+    { "geo:13.37,42.42", TRUE },
+    { "geo:13.37373737,42.42424242", TRUE },
+    { "geo:13.37,42.42,12.12", TRUE },
+    { "geo:1,2,3", TRUE },
+    { "geo:-13.37,42.42", TRUE },
+    { "geo:13.37,-42.42", TRUE },
+    { "geo:13.37,42.42;u=-45.5", TRUE },
+    { "geo:13.37,42.42;u=45.5", TRUE },
+    { "geo:13.37,42.42,12.12;u=45.5", TRUE },
+    { "geo:13.37,42.42,12.12;crs=wgs84;u=45.5", TRUE },
+    { "geo:0.0,0,0", TRUE },
+    { "geo :0.0,0,0", FALSE },
+    { "geo:0.0 ,0,0", FALSE },
+    { "geo:0.0,0 ,0", FALSE },
+    { "geo: 0.0,0,0", FALSE },
+    { "geo:13.37,42.42,12.12;crs=newcrs;u=45.5", FALSE },
+    { "geo:13.37,42.42,12.12;u=45.5;crs=hej", FALSE },
+    { "geo:13.37,42.42,12.12;u=45.5;u=22", FALSE },
+    { "geo:13.37,42.42,12.12;u=alpha", FALSE },
+    { "gel:13.37,42.42,12.12", FALSE },
+    { "geo:13.37alpha,42.42", FALSE },
+    { "geo:13.37,alpha42.42", FALSE },
+    { "geo:13.37,42.42,12.alpha", FALSE },
+    { "geo:,13.37,42.42", FALSE }
+};
+
+static void
+test_parse_uri (void)
+{
+        GeocodeLocation *loc;
+        GError *error = NULL;
+        char *uri = "geo:1.2,2.3,4.5;crs=wgs84;u=67";
+
+        loc = geocode_location_new (0, 0, 0);
+
+        g_assert (geocode_location_set_from_uri (loc, uri, &error));
+        g_assert (error == NULL);
+
+        g_assert_cmpfloat (geocode_location_get_latitude (loc),
+                           ==,
+                           1.2);
+
+        g_assert_cmpfloat (geocode_location_get_longitude (loc),
+                           ==,
+                           2.3);
+
+        g_assert_cmpfloat (geocode_location_get_altitude (loc),
+                           ==,
+                           4.5);
+
+        g_assert_cmpfloat (geocode_location_get_accuracy (loc),
+                           ==,
+                           67);
+
+        g_object_unref (loc);
+}
+
+static void
+test_valid_uri (void)
+{
+        guint i;
+
+        for (i = 0; i < G_N_ELEMENTS (uris); i++) {
+                GeocodeLocation *loc;
+                GError *error = NULL;
+                gboolean success;
+
+                loc = geocode_location_new (0, 0, 0);
+                success = geocode_location_set_from_uri (loc, uris[i].uri, &error);
+                if (uris[i].valid) {
+                        g_assert (success);
+                        g_assert (error == NULL);
+                        g_object_unref (loc);
+                } else {
+                        g_assert (!success);
+                        g_assert (error != NULL);
+                }
+        }
+}
+
+static void
+test_convert_from_to_location (void)
+{
+        GeocodeLocation *loc;
+        GError *error = NULL;
+        gdouble latitude = 48.198634;
+        gdouble longitude = 16.371648;
+        gdouble altitude = 5;
+        gdouble accuracy = 40;
+        /* Karlskirche (from RFC) */
+        char *uri = "geo:48.198634,16.371648,5;crs=wgs84;u=40";
+
+        loc = geocode_location_new (0, 0, 0);
+        g_assert (geocode_location_set_from_uri (loc, uri, &error));
+        g_assert (error == NULL);
+
+        g_assert_cmpfloat (geocode_location_get_latitude (loc),
+                           ==,
+                           latitude);
+        g_assert_cmpfloat (geocode_location_get_longitude (loc),
+                           ==,
+                           longitude);
+        g_assert_cmpfloat (geocode_location_get_altitude (loc),
+                           ==,
+                           altitude);
+        g_assert_cmpfloat (geocode_location_get_accuracy (loc),
+                           ==,
+                           accuracy);
+
+        uri = geocode_location_to_uri (loc, GEOCODE_LOCATION_URI_SCHEME_GEO);
+        g_object_unref (loc);
+        loc = geocode_location_new (0, 0, 0);
+        g_assert (geocode_location_set_from_uri (loc, uri, &error));
+        g_assert (error == NULL);
+
+        g_assert_cmpfloat (geocode_location_get_latitude (loc),
+                           ==,
+                           latitude);
+        g_assert_cmpfloat (geocode_location_get_longitude (loc),
+                           ==,
+                           longitude);
+        g_assert_cmpfloat (geocode_location_get_altitude (loc),
+                           ==,
+                           altitude);
+        g_assert_cmpfloat (geocode_location_get_accuracy (loc),
+                           ==,
+                           accuracy);
+        g_free (uri);
+        g_object_unref (loc);
+}
+
+int main (int argc, char **argv)
+{
+        g_test_init (&argc, &argv, NULL);
+        g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=";);
+
+        g_test_add_func ("/geouri/parse_uri", test_parse_uri);
+        g_test_add_func ("/geouri/valid_uri", test_valid_uri);
+        g_test_add_func ("/geouri/convert_uri", test_convert_from_to_location);
+
+        return g_test_run ();
+
+        return 0;
+}


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