15 #include <glib/gstdio.h>
18 #include <libgnomevfs/gnome-vfs.h>
19 #include <curl/multi.h>
24 #include "ui-common.h"
26 #include "mapper-types.h"
31 #include "map-download.h"
34 void route_find_nearest_point(void);
35 void cancel_autoroute(gboolean temporary);
36 void route_show_distance_to_last(void);
37 void route_set_destination_from_last(void);
39 typedef struct _OriginToggleInfo OriginToggleInfo;
40 struct _OriginToggleInfo {
41 GtkWidget *rad_use_gps;
42 GtkWidget *rad_use_route;
43 GtkWidget *rad_use_text;
52 memset(&_route, 0, sizeof(_route));
53 MACRO_PATH_INIT(_route);
59 MACRO_PATH_FREE(_route);
67 confirm = hildon_note_new_confirmation(GTK_WINDOW(_window), _("Really clear the route?"));
69 if (GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(confirm))) {
70 cancel_autoroute(FALSE);
71 MACRO_PATH_FREE(_route);
72 MACRO_PATH_INIT(_route);
73 route_find_nearest_point();
77 gtk_widget_destroy(confirm);
86 if (open_file(&buffer, NULL, &size, &_route_dir_uri, NULL, GTK_FILE_CHOOSER_ACTION_OPEN)) {
87 /* If auto is enabled, append the route, otherwise replace it. */
88 if (parse_gpx(&_route, buffer, size, _autoroute_data.enabled ? 0 : 1)) {
89 cancel_autoroute(FALSE);
91 /* Find the nearest route point, if we're connected. */
92 route_find_nearest_point();
95 MACRO_BANNER_SHOW_INFO(_window, _("Route Opened"));
96 route_set_destination_from_last();
99 popup_error(_window, _("Error parsing GPX file."));
108 * Ask user to save route
113 GnomeVFSHandle *handle;
115 if (file_save(&_route_dir_uri, &_route_dir_uri, &handle)) {
116 if (write_gpx(&_route, handle)) {
117 MACRO_BANNER_SHOW_INFO(_window, _("Route Saved"));
119 popup_error(_window, _("Error writing GPX file."));
121 gnome_vfs_close(handle);
131 origin_type_selected_cb(GtkWidget * toggle, OriginToggleInfo * oti)
133 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle))) {
134 if (toggle == oti->rad_use_gps) {
138 g_ascii_formatd(strlat, 32, "%.06f", _gps.lat);
139 g_ascii_formatd(strlon, 32, "%.06f", _gps.lon);
140 snprintf(buffer, sizeof(buffer), "%s, %s", strlat, strlon);
141 gtk_entry_set_text(GTK_ENTRY(oti->txt_from), buffer);
142 } else if (toggle == oti->rad_use_route) {
149 /* Use last non-zero route point. */
150 for (p = _route.tail; !p->unity; p--) {
153 unit2latlon(p->unitx, p->unity, lat, lon);
154 g_ascii_formatd(strlat, 32, "%.06f", lat);
155 g_ascii_formatd(strlon, 32, "%.06f", lon);
156 snprintf(buffer, sizeof(buffer), "%s, %s", strlat, strlon);
157 gtk_entry_set_text(GTK_ENTRY(oti->txt_from), buffer);
159 gtk_widget_set_sensitive(oti->txt_from, toggle == oti->rad_use_text);
160 gtk_widget_set_sensitive(oti->chk_auto, toggle == oti->rad_use_gps);
166 * Cancel the current auto-route.
169 cancel_autoroute(gboolean temporary)
171 if (_autoroute_data.enabled) {
173 _autoroute_data.enabled = FALSE;
175 g_free(_autoroute_data.dest);
176 _autoroute_data.dest = NULL;
178 g_free(_autoroute_data.src_str);
179 _autoroute_data.src_str = NULL;
182 if (_autoroute_data.curl_easy) {
184 curl_multi_remove_handle(_curl_multi, _autoroute_data.curl_easy);
185 curl_easy_cleanup(_autoroute_data.curl_easy);
186 _autoroute_data.curl_easy = NULL;
189 g_free(_autoroute_data.rdl_data.bytes);
190 _autoroute_data.rdl_data.bytes = NULL;
191 _autoroute_data.rdl_data.bytes_read = 0;
193 _autoroute_data.in_progress = FALSE;
198 * Read the data provided by the given handle as GPX data, updating the
199 * auto-route with that data.
202 route_dl_cb_read(void *ptr, size_t size, size_t nmemb, RouteDownloadData * rdl_data)
204 size_t old_size = rdl_data->bytes_read;
206 rdl_data->bytes_read += size * nmemb;
207 rdl_data->bytes = g_renew(gchar, rdl_data->bytes, rdl_data->bytes_read);
208 g_memmove(rdl_data->bytes + old_size, ptr, size * nmemb);
210 return (size * nmemb);
216 gchar latstr[32], lonstr[32], *latlonstr;
218 g_ascii_dtostr(latstr, 32, _gps.lat);
219 g_ascii_dtostr(lonstr, 32, _gps.lon);
220 latlonstr = g_strdup_printf("%s,%s", latstr, lonstr);
221 _autoroute_data.src_str =
222 g_strdup_printf(_route_dl_url, latlonstr, _autoroute_data.dest);
225 MACRO_CURL_EASY_INIT(_autoroute_data.curl_easy);
226 curl_easy_setopt(_autoroute_data.curl_easy, CURLOPT_URL,
227 _autoroute_data.src_str);
228 curl_easy_setopt(_autoroute_data.curl_easy, CURLOPT_WRITEFUNCTION,
230 curl_easy_setopt(_autoroute_data.curl_easy, CURLOPT_WRITEDATA,
231 &_autoroute_data.rdl_data);
233 /* Initialize CURL. */
234 _curl_multi = curl_multi_init();
235 /*curl_multi_setopt(_curl_multi, CURLMOPT_PIPELINING, 1); */
237 curl_multi_add_handle(_curl_multi, _autoroute_data.curl_easy);
239 if (iap_is_connected() && !_curl_sid)
240 _curl_sid = g_timeout_add(100, (GSourceFunc) map_download_timeout, NULL);
242 _autoroute_data.in_progress = TRUE;
248 * Display a dialog box to the user asking them to download a route. The
249 * "From" and "To" textfields may be initialized using the first two
250 * parameters. The third parameter, if set to TRUE, will cause the "Use GPS
251 * Location" checkbox to be enabled, which automatically sets the "From" to the
252 * current GPS position (this overrides any value that may have been passed as
253 * the "To" initializer).
254 * None of the passed strings are freed - that is left to the caller, and it is
255 * safe to free either string as soon as this function returns.
258 route_download(gchar * to)
263 GtkWidget *txt_source_url;
265 OriginToggleInfo oti;
266 GtkEntryCompletion *from_comp;
267 GtkEntryCompletion *to_comp;
271 dialog = gtk_dialog_new_with_buttons(_("Download Route"),
273 GTK_DIALOG_MODAL, GTK_STOCK_OK,
276 GTK_RESPONSE_REJECT, NULL);
278 #ifdef WITH_OSSO_HELP
279 /* Enable the help button. */
280 ossohelp_dialog_help_enable(GTK_DIALOG(dialog), HELP_ID_DOWNROUTE, _osso);
283 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
284 table = gtk_table_new(2, 5, FALSE), TRUE, TRUE, 0);
286 from_comp = gtk_entry_completion_new();
287 gtk_entry_completion_set_model(from_comp, GTK_TREE_MODEL(_loc_model));
288 gtk_entry_completion_set_text_column(from_comp, 0);
289 to_comp = gtk_entry_completion_new();
290 gtk_entry_completion_set_model(to_comp, GTK_TREE_MODEL(_loc_model));
291 gtk_entry_completion_set_text_column(to_comp, 0);
294 gtk_table_attach(GTK_TABLE(table), label = gtk_label_new(_("Source URL")), 0, 1, 0, 1, GTK_FILL, 0, 2, 4);
295 gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
296 gtk_table_attach(GTK_TABLE(table), txt_source_url = gtk_entry_new(), 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 4);
297 gtk_entry_set_width_chars(GTK_ENTRY(txt_source_url), 25);
300 gtk_table_attach(GTK_TABLE(table),
301 hbox = gtk_hbox_new(FALSE, 6),
302 0, 2, 1, 2, GTK_FILL, 0, 2, 4);
303 gtk_box_pack_start(GTK_BOX(hbox),
304 oti.rad_use_gps = gtk_radio_button_new_with_label(NULL, _("Use GPS Location")),
306 gtk_box_pack_start(GTK_BOX(hbox), oti.chk_auto =
307 gtk_check_button_new_with_label(_("Auto-Update")),
309 gtk_widget_set_sensitive(oti.chk_auto, FALSE);
311 /* Use End of Route. */
312 gtk_table_attach(GTK_TABLE(table),
313 hbox = gtk_hbox_new(FALSE, 6),
314 0, 2, 2, 3, GTK_FILL, 0, 2, 4);
315 gtk_box_pack_start(GTK_BOX(hbox),
317 gtk_radio_button_new_with_label_from_widget
318 (GTK_RADIO_BUTTON(oti.rad_use_gps),
319 _("Use End of Route")), TRUE, TRUE, 0);
320 gtk_widget_set_sensitive(oti.rad_use_route, _route.head != _route.tail);
323 gtk_table_attach(GTK_TABLE(table),
325 gtk_radio_button_new_with_label_from_widget
326 (GTK_RADIO_BUTTON(oti.rad_use_gps), _("Origin")), 0, 1,
327 3, 4, GTK_FILL, 0, 2, 4);
328 gtk_table_attach(GTK_TABLE(table), oti.txt_from =
329 gtk_entry_new(), 1, 2, 3, 4, GTK_EXPAND | GTK_FILL, 0,
331 gtk_entry_set_completion(GTK_ENTRY(oti.txt_from), from_comp);
332 gtk_entry_set_width_chars(GTK_ENTRY(oti.txt_from), 25);
335 gtk_table_attach(GTK_TABLE(table),
336 label = gtk_label_new(_("Destination")),
337 0, 1, 4, 5, GTK_FILL, 0, 2, 4);
338 gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
339 gtk_table_attach(GTK_TABLE(table),
340 oti.txt_to = gtk_entry_new(), 1, 2, 4, 5, GTK_EXPAND | GTK_FILL, 0, 2, 4);
341 gtk_entry_set_completion(GTK_ENTRY(oti.txt_to), to_comp);
342 gtk_entry_set_width_chars(GTK_ENTRY(oti.txt_to), 25);
344 g_signal_connect(G_OBJECT(oti.rad_use_gps), "toggled", G_CALLBACK(origin_type_selected_cb), &oti);
345 g_signal_connect(G_OBJECT(oti.rad_use_route), "toggled", G_CALLBACK(origin_type_selected_cb), &oti);
346 g_signal_connect(G_OBJECT(oti.rad_use_text), "toggled", G_CALLBACK(origin_type_selected_cb), &oti);
348 #if defined (WITH_HILDON) && defined (HILDON_AUTOCAP)
349 g_object_set(G_OBJECT(oti.txt_from), HILDON_AUTOCAP, FALSE, NULL);
350 g_object_set(G_OBJECT(oti.txt_to), HILDON_AUTOCAP, FALSE, NULL);
353 /* Initialize fields. */
354 gtk_entry_set_text(GTK_ENTRY(txt_source_url), _route_dl_url);
355 gtk_entry_set_text(GTK_ENTRY(oti.txt_to), (to ? to : ""));
357 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oti.rad_use_text), TRUE);
359 /* Use "End of Route" by default if they have a route. */
360 if (_route.head != _route.tail) {
361 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oti.rad_use_route), TRUE);
362 gtk_widget_grab_focus(oti.rad_use_route);
364 /* Else use "GPS Location" if they have GPS enabled. */
365 else if (_enable_gps) {
366 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oti.rad_use_gps), TRUE);
367 gtk_widget_grab_focus(oti.rad_use_gps);
371 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(oti.rad_use_text), TRUE);
372 gtk_widget_grab_focus(oti.txt_from);
375 gtk_widget_show_all(dialog);
377 while (GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog))) {
379 RouteDownloadData rdl_data = { 0, 0 };
380 gchar buffer[BUFFER_SIZE];
381 const gchar *source_url, *from, *to;
382 gchar *from_escaped, *to_escaped;
384 source_url = gtk_entry_get_text(GTK_ENTRY(txt_source_url));
385 if (!strlen(source_url)) {
386 popup_error(dialog, _("Please specify a source URL."));
389 g_free(_route_dl_url);
390 _route_dl_url = g_strdup(source_url);
393 from = gtk_entry_get_text(GTK_ENTRY(oti.txt_from));
395 popup_error(dialog, _("Please specify a start location."));
399 to = gtk_entry_get_text(GTK_ENTRY(oti.txt_to));
401 popup_error(dialog, _("Please specify an end location."));
405 from_escaped = gnome_vfs_escape_string(from);
406 to_escaped = gnome_vfs_escape_string(to);
407 snprintf(buffer, sizeof(buffer), source_url, from_escaped, to_escaped);
408 g_free(from_escaped);
411 /* Attempt to download the route from the server. */
412 MACRO_CURL_EASY_INIT(curl_easy);
413 curl_easy_setopt(curl_easy, CURLOPT_URL, buffer);
414 curl_easy_setopt(curl_easy, CURLOPT_WRITEFUNCTION, route_dl_cb_read);
415 curl_easy_setopt(curl_easy, CURLOPT_WRITEDATA, &rdl_data);
416 if (CURLE_OK != curl_easy_perform(curl_easy)) {
417 popup_error(dialog, _("Failed to connect to GPX Directions server"));
418 curl_easy_cleanup(curl_easy);
419 g_free(rdl_data.bytes);
420 /* Let them try again */
423 curl_easy_cleanup(curl_easy);
425 if (strncmp(rdl_data.bytes, "<?xml", strlen("<?xml"))) {
426 /* Not an XML document - must be bad locations. */
428 _("Could not generate directions. Make sure your "
429 "source and destination are valid."));
430 g_free(rdl_data.bytes);
431 /* Let them try again. */
433 /* Else, if GPS is enabled, replace the route, otherwise append it. */
434 else if (parse_gpx(&_route, rdl_data.bytes, rdl_data.bytes_read,
435 (gtk_toggle_button_get_active
436 (GTK_TOGGLE_BUTTON(oti.rad_use_gps)) ? 0 : 1))) {
439 /* Find the nearest route point, if we're connected. */
440 route_find_nearest_point();
442 /* Cancel any autoroute that might be occurring. */
443 cancel_autoroute(FALSE);
447 if (gtk_toggle_button_get_active
448 (GTK_TOGGLE_BUTTON(oti.chk_auto))) {
449 /* Kick off a timeout to start the first update. */
450 _autoroute_data.dest = gnome_vfs_escape_string(to);
451 _autoroute_data.enabled = TRUE;
454 /* Save Origin in Route Locations list if not from GPS. */
455 if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(oti.rad_use_gps))
456 && !g_slist_find_custom(_loc_list, from, (GCompareFunc) strcmp)) {
457 _loc_list = g_slist_prepend(_loc_list, g_strdup(from));
458 gtk_list_store_insert_with_values(_loc_model,
459 &iter, INT_MAX, 0, from, -1);
462 /* Save Destination in Route Locations list. */
463 if (!g_slist_find_custom(_loc_list, to, (GCompareFunc) strcmp)) {
464 _loc_list = g_slist_prepend(_loc_list, g_strdup(to));
465 gtk_list_store_insert_with_values(_loc_model,
471 MACRO_BANNER_SHOW_INFO(_window, _("Route Downloaded"));
472 g_free(rdl_data.bytes);
473 route_set_destination_from_last();
475 /* Success! Get out of the while loop. */
478 popup_error(dialog, _("Error parsing GPX file."));
479 g_free(rdl_data.bytes);
480 /* Let them try again. */
484 gtk_widget_hide(dialog); /* Destroying causes a crash (!?!?!??!) */
490 find_nearest_waypoint(guint unitx, guint unity)
494 guint64 nearest_squared;
495 Point pos = { unitx, unity, 0, NAN };
497 wcurr = wnear = _route.whead;
498 if (wcurr && wcurr != _route.wtail) {
499 nearest_squared = DISTANCE_SQUARED(pos, *(wcurr->point));
501 while (wcurr++ != _route.wtail) {
502 guint64 test_squared = DISTANCE_SQUARED(pos, *(wcurr->point));
503 if (test_squared < nearest_squared) {
505 nearest_squared = test_squared;
511 /* Only use the waypoint if it is within a 6*_draw_width square drawn
512 * around the position. This is consistent with select_poi(). */
513 if (abs(unitx - wnear->point->unitx) <
514 pixel2unit(3 * _draw_width)
515 && abs(unity - wnear->point->unity) <
516 pixel2unit(3 * _draw_width))
520 MACRO_BANNER_SHOW_INFO(_window, _("There are no waypoints."));
526 * Updates _near_point, _next_way, and _next_wpt. If quick is FALSE (as
527 * it is when this function is called from route_find_nearest_point), then
528 * the entire list (starting from _near_point) is searched. Otherwise, we
529 * stop searching when we find a point that is farther away.
532 route_update_nears(gboolean quick)
534 gboolean ret = FALSE;
536 WayPoint *wcurr, *wnext;
537 guint64 near_dist_squared;
539 /* If we have waypoints (_next_way != NULL), then determine the "next
540 * waypoint", which is defined as the waypoint after the nearest point,
541 * UNLESS we've passed that waypoint, in which case the waypoint after
542 * that waypoint becomes the "next" waypoint. */
544 /* First, set near_dist_squared with the new distance from
547 near_dist_squared = DISTANCE_SQUARED(_pos, *near);
549 /* Now, search _route for a closer point. If quick is TRUE, then we'll
550 * only search forward, only as long as we keep finding closer points.
552 for (curr = _near_point; curr++ != _route.tail;) {
554 guint dist_squared = DISTANCE_SQUARED(_pos, *curr);
555 if (dist_squared <= near_dist_squared) {
557 near_dist_squared = dist_squared;
563 /* Update _near_point. */
565 _near_point_dist_squared = near_dist_squared;
567 for (wnext = wcurr = _next_way; wcurr != _route.wtail; wcurr++) {
568 if (wcurr->point < near || (wcurr->point == near && quick
569 && (_next_wpt && (DISTANCE_SQUARED(_pos, *near) > _next_way_dist_squared
570 && DISTANCE_SQUARED(_pos, *_next_wpt) < _next_wpt_dist_squared))))
571 /* Okay, this else if expression warrants explanation. If the
572 * nearest track point happens to be a waypoint, then we want to
573 * check if we have "passed" that waypoint. To check this, we
574 * test the distance from _pos to the waypoint and from _pos to
575 * _next_wpt, and if the former is increasing and the latter is
576 * decreasing, then we have passed the waypoint, and thus we
577 * should skip it. Note that if there is no _next_wpt, then
578 * there is no next waypoint, so we do not skip it in that case. */
584 if (wnext == _route.wtail && (wnext->point < near || (wnext->point == near && quick
585 && (_next_wpt && (DISTANCE_SQUARED (_pos, *near) > _next_way_dist_squared
586 && DISTANCE_SQUARED(_pos, *_next_wpt) < _next_wpt_dist_squared)))))
590 _next_way_dist_squared = -1;
591 _next_wpt_dist_squared = -1;
594 /* Only update _next_way (and consequently _next_wpt) if _next_way is
595 * different, and record that fact for return. */
597 if (!quick || _next_way != wnext) {
599 _next_wpt = wnext->point;
600 if (_next_wpt == _route.tail)
603 while (!(++_next_wpt)->unity) {
604 if (_next_wpt == _route.tail) {
612 _next_way_dist_squared = DISTANCE_SQUARED(_pos, *wnext->point);
614 _next_wpt_dist_squared = DISTANCE_SQUARED(_pos, *_next_wpt);
621 * Reset the _near_point data by searching the entire route for the nearest
622 * route point and waypoint.
625 route_find_nearest_point()
627 /* Initialize _near_point to first non-zero point. */
628 _near_point = _route.head;
629 while (!_near_point->unity && _near_point != _route.tail)
632 /* Initialize _next_way. */
633 if (_route.wtail == _route.whead - 1 || (_autoroute_data.enabled && _route.wtail == _route.whead))
636 /* We have at least one waypoint. */
637 _next_way = (_autoroute_data.enabled ? _route.whead + 1 : _route.whead);
639 _next_way_dist_squared = -1;
641 /* Initialize _next_wpt. */
643 _next_wpt_dist_squared = -1;
645 route_update_nears(FALSE);
649 * Show the distance from the current GPS location to the given point,
650 * following the route. If point is NULL, then the distance is shown to the
654 route_show_distance_to(Point *point)
657 gdouble lat1, lon1, lat2, lon2;
660 /* If point is NULL, use the next waypoint. */
661 if (point == NULL && _next_way)
662 point = _next_way->point;
664 /* If point is still NULL, return an error. */
668 unit2latlon(_pos.unitx, _pos.unity, lat1, lon1);
669 if (point > _near_point) {
671 /* Skip _near_point in case we have already passed it. */
672 for (curr = _near_point + 1; curr <= point; ++curr) {
674 unit2latlon(curr->unitx, curr->unity, lat2, lon2);
675 sum += calculate_distance(lat1, lon1, lat2, lon2);
680 } else if (point < _near_point) {
682 /* Skip _near_point in case we have already passed it. */
683 for (curr = _near_point - 1; curr >= point; --curr) {
685 unit2latlon(curr->unitx, curr->unity, lat2, lon2);
686 sum += calculate_distance(lat1, lon1, lat2, lon2);
692 /* Waypoint _is_ the nearest point. */
693 unit2latlon(_near_point->unitx, _near_point->unity, lat2, lon2);
694 sum += calculate_distance(lat1, lon1, lat2, lon2);
697 g_snprintf(buffer, sizeof(buffer), "%s: %.02f %s", _("Distance"), sum * UNITS_CONVERT[_units], UNITS_TEXT[_units]);
698 MACRO_BANNER_SHOW_INFO(_window, buffer);
704 route_show_distance_to_next()
706 if (!route_show_distance_to(NULL)) {
707 MACRO_BANNER_SHOW_INFO(_window, _("There is no next waypoint."));
712 route_set_destination_from_last(void)
716 if (_route.head == _route.tail)
719 /* Find last non-zero point. */
720 for (p = _route.tail; !p->unity; p--) {
722 unit2latlon(p->unitx, p->unity, lat, lon);
729 route_show_distance_to_last(void)
733 if (_route.head != _route.tail) {
734 /* Find last non-zero point. */
735 for (p = _route.tail; !p->unity; p--) {
737 route_show_distance_to(p);
739 MACRO_BANNER_SHOW_INFO(_window, _("The current route is empty."));
746 route_generate_store(void)
753 gdouble lat1, lon1, lat2, lon2;
756 if (_route.whead==_route.wtail)
759 wcurr = _route.whead;
764 unit2latlon(wcurr->point->unitx, wcurr->point->unity, lat1, lon1);
766 store = gtk_list_store_new(ROUTE_NUM_COLUMNS,G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
768 while (wcurr!=_route.wtail) {
772 unit2latlon(wcurr->point->unitx, wcurr->point->unity, lat2, lon2);
773 g_snprintf(buffer1, sizeof(buffer1), "%.05f,%.05f", lat2, lon2);
774 sum += calculate_distance(lat1, lon1, lat2, lon2);
775 g_snprintf(buffer2, sizeof(buffer2), "%.02f %s", sum * UNITS_CONVERT[_units], UNITS_TEXT[_units]);
780 gtk_list_store_append(store, &iter);
781 gtk_list_store_set(store, &iter,
782 ROUTE_LATLON, buffer1,
783 ROUTE_DISTANCE, buffer2,
784 ROUTE_WAYPOINT, wcurr->desc,