]> err.no Git - mapper/blob - src/route.c
More map widget integration changes
[mapper] / src / route.c
1 #include <config.h>
2
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <strings.h>
7 #include <stddef.h>
8 #include <libintl.h>
9 #include <locale.h>
10 #include <math.h>
11 #include <errno.h>
12 #include <sys/wait.h>
13 #include <glib/gstdio.h>
14 #include <gtk/gtk.h>
15 #include <fcntl.h>
16 #include <libgnomevfs/gnome-vfs.h>
17 #include <curl/multi.h>
18
19 #include "utils.h"
20 #include "gps.h"
21 #include "route.h"
22 #include "ui-common.h"
23 #include "settings.h"
24 #include "mapper-types.h"
25 #include "map.h"
26 #include "file.h"
27 #include "latlon.h"
28 #include "map.h"
29 #include "map-download.h"
30 #include "iap.h"
31 #include "gpx.h"
32 #include "help.h"
33
34 #define DISTANCE_SQUARED(a, b) \
35         ((guint64)((((gint64)(b).unitx)-(a).unitx)*(((gint64)(b).unitx)-(a).unitx))\
36         + (guint64)((((gint64)(b).unity)-(a).unity)*(((gint64)(b).unity)-(a).unity)))
37
38 static gboolean show_directions=TRUE;
39
40 static WayPoint *announced_waypoint=NULL;
41
42 typedef struct _OriginToggleInfo OriginToggleInfo;
43 struct _OriginToggleInfo {
44         GtkWidget *rad_use_gps;
45         GtkWidget *rad_use_route;
46         GtkWidget *rad_use_text;
47         GtkWidget *chk_auto;
48         GtkWidget *txt_from;
49         GtkWidget *txt_to;
50         Path *path;
51 };
52
53 gboolean
54 route_clear(Path *route)
55 {
56 GtkWidget *confirm;
57 gboolean r=FALSE;
58
59 g_return_val_if_fail(route, FALSE);
60
61 confirm=hildon_note_new_confirmation(GTK_WINDOW(_window), _("Really clear the route?"));
62
63 if (GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(confirm))) {
64         route_cancel_autoroute(route, FALSE);
65         announced_waypoint=NULL;
66         path_clear(route);
67         path_find_nearest_point(route);
68         r=TRUE;
69 }
70
71 gtk_widget_destroy(confirm);
72 return r;
73 }
74
75 /**
76  * Check if we should announce upcoming waypoints. 
77  */
78 void
79 route_check_waypoint_announce(Path *route, GpsData *gps)
80 {
81 guint announce_thres_unsquared;
82
83 g_return_if_fail(route);
84 g_return_if_fail(gps);
85
86 if (!_announce_waypoints)
87         return;
88
89 if (!route->next_way)
90         return;
91
92 announce_thres_unsquared=(20+(guint)gps->speed)*_announce_notice_ratio*3;
93
94 if (route->next_way_dist_squared<(announce_thres_unsquared * announce_thres_unsquared) && route->next_way!=announced_waypoint) {
95         announce_waypoint(route->next_way->desc);
96         announced_waypoint=route->next_way;
97 }
98
99 }
100
101 /**
102  * Check if we should re-calculate route
103  */
104 void
105 route_autoroute_check(Path *route)
106 {
107 g_return_if_fail(route);
108
109 if (_autoroute_data.enabled && !_autoroute_data.in_progress && route->near_point_dist_squared > 400) {
110         MACRO_BANNER_SHOW_INFO(_window, _("Recalculating directions..."));
111         _autoroute_data.in_progress = TRUE;
112         show_directions = FALSE;
113         g_idle_add((GSourceFunc)route_auto_route_dl_idle_cb, NULL);
114 }
115 }
116
117 gboolean 
118 route_open_file(Path *route)
119 {
120 gchar *buffer;
121 gint size;
122
123 g_return_val_if_fail(route, FALSE);
124
125 if (file_open_get_contents(&_route_dir_uri, &buffer, &size)) {
126         /* If auto is enabled, append the route, otherwise replace it. */
127         if (gpx_parse(route, buffer, size, _autoroute_data.enabled ? GPX_PATH_APPEND : GPX_PATH_NEW)) {
128                 route_cancel_autoroute(route, FALSE);
129
130                 MACRO_BANNER_SHOW_INFO(_window, _("Route Opened"));
131                 /* Find the nearest route point, if we're connected. */
132                 path_find_nearest_point(route);
133                 route_set_destination_from_last(route, &_dest);
134                 return TRUE;
135         } else {
136                 popup_error(_window, _("Error parsing GPX file."));
137                 g_free(buffer);
138                 return FALSE;
139         }
140 }
141 return FALSE;
142 }
143
144 /**
145  * Ask user to save route
146  */
147 gboolean
148 route_save(Path *route)
149 {
150 GnomeVFSHandle *handle;
151
152 g_return_val_if_fail(route, FALSE);
153
154 if (route->head==route->tail) {
155         MACRO_BANNER_SHOW_INFO(_window, _("No route exist."));
156         return FALSE;
157 }
158
159 if (file_save(&_route_dir_uri, &_route_dir_uri, &handle)) {
160         if (gpx_write(route, handle)) {
161                 MACRO_BANNER_SHOW_INFO(_window, _("Route Saved"));
162         } else {
163                 popup_error(_window, _("Error writing GPX file."));
164         }
165         gnome_vfs_close(handle);
166         return TRUE;
167 }
168 return FALSE;
169 }
170
171 /**
172  *
173  */
174 static gboolean 
175 route_origin_type_selected_cb(GtkWidget *toggle, OriginToggleInfo *oti)
176 {
177 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle))) {
178         if (toggle == oti->rad_use_gps) {
179                 gchar buffer[80];
180                 gchar strlat[32];
181                 gchar strlon[32];
182                 g_ascii_formatd(strlat, 32, "%.06f", _gps->data.lat);
183                 g_ascii_formatd(strlon, 32, "%.06f", _gps->data.lon);
184                 g_snprintf(buffer, sizeof(buffer), "%s, %s", strlat, strlon);
185                 gtk_entry_set_text(GTK_ENTRY(oti->txt_from), buffer);
186         } else if (toggle == oti->rad_use_route) {
187                 gchar buffer[80];
188                 gchar strlat[32];
189                 gchar strlon[32];
190                 gdouble lat, lon;
191                 Point *p;
192
193                 p=path_find_last_point(oti->path);
194                 if (p) {
195                         unit2latlon(p->unitx, p->unity, lat, lon);
196                         g_ascii_formatd(strlat, 32, "%.06f", lat);
197                         g_ascii_formatd(strlon, 32, "%.06f", lon);
198                         g_snprintf(buffer, sizeof(buffer), "%s, %s", strlat, strlon);
199                         gtk_entry_set_text(GTK_ENTRY(oti->txt_from), buffer);
200                 }
201         }
202         gtk_widget_set_sensitive(oti->txt_from, toggle == oti->rad_use_text);
203         gtk_widget_set_sensitive(oti->chk_auto, toggle == oti->rad_use_gps);
204 }
205 return TRUE;
206 }
207
208 /**
209  * Cancel the current auto-route.
210  */
211 void 
212 route_cancel_autoroute(Path *route, gboolean temporary)
213 {
214 if (_autoroute_data.enabled) {
215         if (!temporary) {
216                 _autoroute_data.enabled = FALSE;
217
218                 g_free(_autoroute_data.dest);
219                 _autoroute_data.dest = NULL;
220
221                 g_free(_autoroute_data.src_str);
222                 _autoroute_data.src_str = NULL;
223         }
224
225         if (_autoroute_data.curl_easy) {
226                 if (_curl_multi)
227                         curl_multi_remove_handle(_curl_multi, _autoroute_data.curl_easy);
228                 curl_easy_cleanup(_autoroute_data.curl_easy);
229                 _autoroute_data.curl_easy = NULL;
230         }
231
232         g_free(_autoroute_data.rdl_data.bytes);
233         _autoroute_data.rdl_data.bytes = NULL;
234         _autoroute_data.rdl_data.bytes_read = 0;
235
236         _autoroute_data.in_progress = FALSE;
237 }
238 }
239
240 /**
241  * Read the data provided by the given handle as GPX data, updating the
242  * auto-route with that data.
243  */
244 size_t
245 route_dl_cb_read(void *ptr, size_t size, size_t nmemb, RouteDownloadData * rdl_data)
246 {
247 size_t old_size = rdl_data->bytes_read;
248
249 rdl_data->bytes_read += size * nmemb;
250 rdl_data->bytes = g_renew(gchar, rdl_data->bytes, rdl_data->bytes_read);
251 g_memmove(rdl_data->bytes + old_size, ptr, size * nmemb);
252
253 return (size * nmemb);
254 }
255
256 gboolean
257 route_auto_route_dl_idle_cb()
258 {
259 gchar latstr[32], lonstr[32], *latlonstr;
260
261 g_ascii_dtostr(latstr, 32, _gps->data.lat);
262 g_ascii_dtostr(lonstr, 32, _gps->data.lon);
263 latlonstr = g_strdup_printf("%s,%s", latstr, lonstr);
264 _autoroute_data.src_str = g_strdup_printf(_route_dl_url, latlonstr, _autoroute_data.dest);
265 g_free(latlonstr);
266
267 MACRO_CURL_EASY_INIT(_autoroute_data.curl_easy);
268 curl_easy_setopt(_autoroute_data.curl_easy, CURLOPT_URL, _autoroute_data.src_str);
269 curl_easy_setopt(_autoroute_data.curl_easy, CURLOPT_WRITEFUNCTION, route_dl_cb_read);
270 curl_easy_setopt(_autoroute_data.curl_easy, CURLOPT_WRITEDATA, &_autoroute_data.rdl_data);
271 curl_multi_add_handle(_curl_multi, _autoroute_data.curl_easy);
272
273 if (iap_is_connected() && !_curl_sid)
274         _curl_sid = g_timeout_add(100, (GSourceFunc)map_download_timeout, NULL);
275
276 _autoroute_data.in_progress = TRUE;
277
278 return FALSE;
279 }
280
281 /**
282  * Display a dialog box to the user asking them to download a route.  The
283  * "From" and "To" textfields may be initialized using the first two
284  * parameters.  The third parameter, if set to TRUE, will cause the "Use GPS
285  * Location" checkbox to be enabled, which automatically sets the "From" to the
286  * current GPS position (this overrides any value that may have been passed as
287  * the "To" initializer).
288  * None of the passed strings are freed - that is left to the caller, and it is
289  * safe to free either string as soon as this function returns.
290  */
291 gboolean 
292 route_download(Path *route, gchar *to)
293 {
294 GtkWidget *dialog;
295 GtkWidget *table;
296 GtkWidget *label;
297 GtkWidget *txt_source_url;
298 GtkWidget *hbox;
299 OriginToggleInfo oti;
300 GtkEntryCompletion *from_comp;
301 GtkEntryCompletion *to_comp;
302 gboolean r=FALSE;
303
304 oti.path=route;
305
306 dialog = gtk_dialog_new_with_buttons(_("Download Route"),
307                              GTK_WINDOW(_window),
308                              GTK_DIALOG_MODAL, GTK_STOCK_OK,
309                              GTK_RESPONSE_ACCEPT,
310                              GTK_STOCK_CANCEL,
311                              GTK_RESPONSE_REJECT, NULL);
312
313 help_dialog_help_enable(GTK_DIALOG(dialog), HELP_ID_DOWNROUTE);
314 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), table = gtk_table_new(2, 5, FALSE), TRUE, TRUE, 0);
315
316 from_comp = gtk_entry_completion_new();
317 gtk_entry_completion_set_model(from_comp, GTK_TREE_MODEL(_loc_model));
318 gtk_entry_completion_set_text_column(from_comp, 0);
319 to_comp = gtk_entry_completion_new();
320 gtk_entry_completion_set_model(to_comp, GTK_TREE_MODEL(_loc_model));
321 gtk_entry_completion_set_text_column(to_comp, 0);
322
323 /* Source URL. */
324 gtk_table_attach(GTK_TABLE(table), label = gtk_label_new(_("Source URL")), 0, 1, 0, 1, GTK_FILL, 0, 2, 4);
325 gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
326 gtk_table_attach(GTK_TABLE(table), txt_source_url = gtk_entry_new(), 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 4);
327 gtk_entry_set_width_chars(GTK_ENTRY(txt_source_url), 25);
328
329 /* Auto. */
330 gtk_table_attach(GTK_TABLE(table), hbox = gtk_hbox_new(FALSE, 6), 0, 2, 1, 2, GTK_FILL, 0, 2, 4);
331 gtk_box_pack_start(GTK_BOX(hbox), oti.rad_use_gps = gtk_radio_button_new_with_label(NULL, _("Use GPS Location")), TRUE, TRUE, 0);
332 gtk_box_pack_start(GTK_BOX(hbox), oti.chk_auto = gtk_check_button_new_with_label(_("Auto-Update")), TRUE, TRUE, 0);
333 gtk_widget_set_sensitive(oti.chk_auto, FALSE);
334
335 /* Use End of Route. */
336 gtk_table_attach(GTK_TABLE(table), hbox = gtk_hbox_new(FALSE, 6), 0, 2, 2, 3, GTK_FILL, 0, 2, 4);
337 gtk_box_pack_start(GTK_BOX(hbox), oti.rad_use_route = gtk_radio_button_new_with_label_from_widget
338                    (GTK_RADIO_BUTTON(oti.rad_use_gps), _("Use End of Route")), TRUE, TRUE, 0);
339 gtk_widget_set_sensitive(oti.rad_use_route, route->head != route->tail);
340
341 /* Origin. */
342 gtk_table_attach(GTK_TABLE(table), oti.rad_use_text = gtk_radio_button_new_with_label_from_widget
343                  (GTK_RADIO_BUTTON(oti.rad_use_gps), _("Origin")), 0, 1, 3, 4, GTK_FILL, 0, 2, 4);
344 gtk_table_attach(GTK_TABLE(table), oti.txt_from = gtk_entry_new(), 1, 2, 3, 4, GTK_EXPAND | GTK_FILL, 0, 2, 4);
345 gtk_entry_set_completion(GTK_ENTRY(oti.txt_from), from_comp);
346 gtk_entry_set_width_chars(GTK_ENTRY(oti.txt_from), 25);
347
348 /* Destination. */
349 gtk_table_attach(GTK_TABLE(table), label = gtk_label_new(_("Destination")), 0, 1, 4, 5, GTK_FILL, 0, 2, 4);
350 gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
351 gtk_table_attach(GTK_TABLE(table), oti.txt_to = gtk_entry_new(), 1, 2, 4, 5, GTK_EXPAND | GTK_FILL, 0, 2, 4);
352 gtk_entry_set_completion(GTK_ENTRY(oti.txt_to), to_comp);
353 gtk_entry_set_width_chars(GTK_ENTRY(oti.txt_to), 25);
354
355 g_signal_connect(G_OBJECT(oti.rad_use_gps), "toggled", G_CALLBACK(route_origin_type_selected_cb), &oti);
356 g_signal_connect(G_OBJECT(oti.rad_use_route), "toggled", G_CALLBACK(route_origin_type_selected_cb), &oti);
357 g_signal_connect(G_OBJECT(oti.rad_use_text), "toggled", G_CALLBACK(route_origin_type_selected_cb), &oti);
358
359 #if defined (WITH_HILDON) && defined (HILDON_AUTOCAP)
360 g_object_set(G_OBJECT(oti.txt_from), HILDON_AUTOCAP, FALSE, NULL);
361 g_object_set(G_OBJECT(oti.txt_to), HILDON_AUTOCAP, FALSE, NULL);
362 #endif
363
364 /* Initialize fields. */
365 gtk_entry_set_text(GTK_ENTRY(txt_source_url), _route_dl_url);
366 gtk_entry_set_text(GTK_ENTRY(oti.txt_to), (to ? to : ""));
367
368 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oti.rad_use_text), TRUE);
369
370 if (route->head != route->tail) {
371         /* Use "End of Route" by default if they have a route. */
372         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oti.rad_use_route), TRUE);
373         gtk_widget_grab_focus(oti.rad_use_route);
374 } else if (_enable_gps) {
375         /* Else use "GPS Location" if they have GPS enabled. */
376         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oti.rad_use_gps), TRUE);
377         gtk_widget_grab_focus(oti.rad_use_gps);
378 } else {
379         /* Else use text. */
380         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oti.rad_use_text), TRUE);
381         gtk_widget_grab_focus(oti.txt_from);
382 }
383
384 gtk_widget_show_all(dialog);
385
386 while (GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog))) {
387         CURL *curl_easy;
388         RouteDownloadData rdl_data = { 0, 0 };
389         gchar buffer[BUFFER_SIZE];
390         const gchar *source_url, *from, *to;
391         gchar *from_escaped, *to_escaped;
392
393         source_url = gtk_entry_get_text(GTK_ENTRY(txt_source_url));
394         if (!strlen(source_url)) {
395                 popup_error(dialog, _("Please specify a source URL."));
396                 continue;
397         } else {
398                 g_free(_route_dl_url);
399                 _route_dl_url = g_strdup(source_url);
400         }
401
402         from = gtk_entry_get_text(GTK_ENTRY(oti.txt_from));
403         if (!strlen(from)) {
404                 popup_error(dialog, _("Please specify a start location."));
405                 continue;
406         }
407
408         to = gtk_entry_get_text(GTK_ENTRY(oti.txt_to));
409         if (!strlen(to)) {
410                 popup_error(dialog, _("Please specify an end location."));
411                 continue;
412         }
413
414         from_escaped = gnome_vfs_escape_string(from);
415         to_escaped = gnome_vfs_escape_string(to);
416         g_snprintf(buffer, sizeof(buffer), source_url, from_escaped, to_escaped);
417         g_free(from_escaped);
418         g_free(to_escaped);
419
420         iap_connect();
421
422         /* Attempt to download the route from the server. */
423         MACRO_CURL_EASY_INIT(curl_easy);
424         curl_easy_setopt(curl_easy, CURLOPT_URL, buffer);
425         curl_easy_setopt(curl_easy, CURLOPT_WRITEFUNCTION, route_dl_cb_read);
426         curl_easy_setopt(curl_easy, CURLOPT_WRITEDATA, &rdl_data);
427         if (CURLE_OK != curl_easy_perform(curl_easy)) {
428                 popup_error(dialog, _("Failed to connect to GPX Directions server"));
429                 curl_easy_cleanup(curl_easy);
430                 g_free(rdl_data.bytes);
431                 /* Let them try again */
432                 continue;
433         }
434         curl_easy_cleanup(curl_easy);
435
436         if (strncmp(rdl_data.bytes, "<?xml", strlen("<?xml"))) {
437                 /* Not an XML document - must be bad locations. */
438                 popup_error(dialog, _("Could not generate directions. Make sure your source and destination are valid."));
439                 g_free(rdl_data.bytes);
440                 /* Let them try again. */
441         }
442         /* Else, if GPS is enabled, replace the route, otherwise append it. */
443         else if (gpx_parse(route, rdl_data.bytes, rdl_data.bytes_read,
444                         (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(oti.rad_use_gps)) ? GPX_PATH_NEW : GPX_PATH_APPEND))) {
445                 GtkTreeIter iter;
446
447                 /* Find the nearest route point, if we're connected. */
448                 path_find_nearest_point(route);
449
450                 /* Cancel any autoroute that might be occurring. */
451                 route_cancel_autoroute(route, FALSE);
452
453                 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(oti.chk_auto))) {
454                         /* Kick off a timeout to start the first update. */
455                         _autoroute_data.dest = gnome_vfs_escape_string(to);
456                         _autoroute_data.enabled = TRUE;
457                 }
458
459                 /* Save Origin in Route Locations list if not from GPS. */
460                 if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(oti.rad_use_gps)) && !g_slist_find_custom(_loc_list, from, (GCompareFunc) strcmp)) {
461                         _loc_list = g_slist_prepend(_loc_list, g_strdup(from));
462                         gtk_list_store_insert_with_values(_loc_model, &iter, INT_MAX, 0, from, -1);
463                 }
464
465                 /* Save Destination in Route Locations list. */
466                 if (!g_slist_find_custom(_loc_list, to, (GCompareFunc) strcmp)) {
467                         _loc_list = g_slist_prepend(_loc_list, g_strdup(to));
468                         gtk_list_store_insert_with_values(_loc_model, &iter, INT_MAX, 0, to, -1);
469                 }
470
471                 MACRO_BANNER_SHOW_INFO(_window, _("Route Downloaded"));
472                 g_free(rdl_data.bytes);
473                 route_set_destination_from_last(route, &_dest);
474
475                 /* Success! Get out of the while loop. */
476                 r=TRUE;
477                 break;
478         } else {
479                 popup_error(dialog, _("Error parsing route GPX data."));
480                 g_free(rdl_data.bytes);
481                 /* Let them try again. */
482         }
483 }
484
485 gtk_widget_hide(dialog);        /* Destroying causes a crash (!?!?!??!) */
486
487 return r;
488 }
489
490 /**
491  * Show the distance from the current GPS location to the given point,
492  * following the route. If point is NULL, then the distance is shown to the
493  * next waypoint.
494  */
495 gboolean 
496 route_show_distance_to(Path *route, Point *point)
497 {
498 gchar buffer[80];
499 gdouble lat, lon;
500 gdouble sum;
501
502 g_return_val_if_fail(route, FALSE);
503 g_return_val_if_fail(point, FALSE);
504
505 unit2latlon(point->unitx, point->unity, &lat, &lon);
506 sum=path_get_distance_to(route, point, lat, lon);
507 g_snprintf(buffer, sizeof(buffer), "%s: %.02f %s", _("Distance"), sum * UNITS_CONVERT[_units], UNITS_TEXT[_units]);
508 MACRO_BANNER_SHOW_INFO(_window, buffer);
509
510 return TRUE;
511 }
512
513 void 
514 route_show_distance_to_next_waypoint(Path *route)
515 {
516 g_return_if_fail(route);
517
518 if (!route_show_distance_to(route, NULL))
519         MACRO_BANNER_SHOW_INFO(_window, _("There is no next waypoint."));
520 }
521
522 void 
523 route_show_distance_to_last(Path *route)
524 {
525 g_return_if_fail(route);
526
527 if (route->head != route->tail) {
528         route_show_distance_to(route, path_find_last_point(route));
529 } else {
530         MACRO_BANNER_SHOW_INFO(_window, _("The current route is empty."));
531 }
532 }