[geocode-glib] location: Add parsing of geo URI
- From: Jonas Danielsson <jonasdn src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geocode-glib] location: Add parsing of geo URI
- Date: Mon, 16 Dec 2013 21:26:39 +0000 (UTC)
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]