UTF8 aware parser for some more GPS formats

I'm sure there are better ways to do this, but this appears to grok most
rational formats I was able to find. NSEW or positive/negative numbers.
Decimal degrees (WGS84) or degrees and decimal minutes (that's what most
GPSs seem to provide). I'm sure there are still corner cases that confuse
it, but it seemed reasonably robust in testing.

I don't really love the ';' as separator but that solves the obvious
problem with locales that use a decimal comma.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Dirk Hohndel 2013-01-27 14:42:14 -08:00
parent 0a91669efe
commit 546fecd850
2 changed files with 83 additions and 8 deletions

1
dive.h
View file

@ -548,6 +548,7 @@ const char *weekday(int wday);
const char *monthname(int mon);
#define UTF8_DEGREE "\xc2\xb0"
#define UCS4_DEGREE 0xb0
#define UTF8_SUBSCRIPT_2 "\xe2\x82\x82"
#define UTF8_WHITESTAR "\xe2\x98\x86"
#define UTF8_BLACKSTAR "\xe2\x98\x85"

90
info.c
View file

@ -394,12 +394,19 @@ static int get_rating(const char *string)
return rating_val;
}
/* this has to be done with UTF8 as people might want to enter the degree symbol */
static gboolean parse_gps_text(const char *gps_text, double *latitude, double *longitude)
{
const char *text = gps_text;
char *endptr;
gboolean south = FALSE;
gboolean west = FALSE;
double parselat, parselong;
gunichar degrees = UCS4_DEGREE;
gunichar c;
while (isspace(*text))
text++;
while (g_unichar_isspace(g_utf8_get_char(text)))
text = g_utf8_next_char(text);
/* an empty string is interpreted as 0.0,0.0 and therefore "no gps location" */
if (!*text) {
@ -407,11 +414,78 @@ static gboolean parse_gps_text(const char *gps_text, double *latitude, double *l
*longitude = 0.0;
return TRUE;
}
/* WGS84 style decimal degrees */
if (sscanf(text, "%lf,%lf", latitude, longitude) == 2)
return TRUE;
/* ok, let's parse by hand - first degrees of latitude */
if (g_unichar_toupper(g_utf8_get_char(text)) == 'N')
text++;
if (g_unichar_toupper(g_utf8_get_char(text)) == 'S') {
text++;
south = TRUE;
}
parselat = strtod(text, &endptr);
if (text == endptr)
return FALSE;
text = endptr;
if (parselat < 0.0) {
south = TRUE;
parselat *= -1;
}
return FALSE;
/* next optional minutes as decimal, skipping degree symbol */
while (g_unichar_isspace(c = g_utf8_get_char(text)) || c == degrees)
text = g_utf8_next_char(text);
if (g_unichar_toupper(c) != 'E' && g_unichar_toupper(c) != 'W' && c != ';' && c != ',') {
parselat += strtod(text, &endptr) / 60.0;
if (text == endptr)
return FALSE;
text = endptr;
/* skip trailing minute symbol */
if (g_utf8_get_char(text) == '\'')
text = g_utf8_next_char(text);
}
/* skip seperator between latitude and longitude */
while (g_unichar_isspace(c = g_utf8_get_char(text)) || c == ';' || c == ',')
text = g_utf8_next_char(text);
/* next degrees of longitude */
if (g_unichar_toupper(g_utf8_get_char(text)) == 'E')
text++;
if (g_unichar_toupper(g_utf8_get_char(text)) == 'W') {
text++;
west = TRUE;
}
parselong = strtod(text, &endptr);
if (text == endptr)
return FALSE;
text = endptr;
if (parselong < 0.0) {
west = TRUE;
parselong *= -1;
}
/* next optional minutes as decimal, skipping degree symbol */
while (g_unichar_isspace(c = g_utf8_get_char(text)) || c == degrees)
text = g_utf8_next_char(text);
if (*text) {
parselong += strtod(text, &endptr) / 60.0;
if (text == endptr)
return FALSE;
text = endptr;
/* skip trailing minute symbol */
if (g_utf8_get_char(text) == '\'')
text = g_utf8_next_char(text);
/* make sure there's nothing else left on the input */
while (g_unichar_isspace(g_utf8_get_char(text)))
text = g_utf8_next_char(text);
if (*text)
return FALSE;
}
if (west && parselong > 0.0)
parselong *= -1;
if (south && parselat > 0.0)
parselat *= -1;
*latitude = parselat;
*longitude = parselong;
return TRUE;
}
static gboolean gps_changed(struct dive *dive, struct dive *master, const char *gps_text)
@ -571,9 +645,9 @@ static void dive_info_widget(GtkWidget *box, struct dive *dive, struct dive_info
info->location = text_entry(box, _("Location"), location_list, dive->location);
if (dive_has_location(dive))
snprintf(gps_text, sizeof(gps_text), "%3.5lf,%3.5lf", dive->latitude.udeg / 1000000.0,
snprintf(gps_text, sizeof(gps_text), "%3.5lf;%3.5lf", dive->latitude.udeg / 1000000.0,
dive->longitude.udeg / 1000000.0);
info->gps = single_text_entry(box, _("GPS"), gps_text);
info->gps = single_text_entry(box, _("GPS (WGS84 or GPS format - use ';' as separator)"), gps_text);
hbox = gtk_hbox_new(FALSE, 3);
gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0);