14 #include <glib/gstdio.h>
17 #include <libgnomevfs/gnome-vfs.h>
18 #include <gconf/gconf-client.h>
19 #include <libxml/parser.h>
20 #include <curl/multi.h>
30 #include "mapper-types.h"
31 #include "ui-common.h"
34 Point _min_center = { -1, -1 };
35 Point _max_center = { -1, -1 };
36 Point _focus = { -1, -1 };
38 /** The "base tile" is the upper-left tile in the pixmap. */
39 guint _base_tilex = -5;
40 guint _base_tiley = -5;
42 guint _zoom = 3; /* zoom level, from 0 to MAX_ZOOM. */
43 Point _center = { -1, -1 }; /* current center location, X. */
45 static guint press[2] = { 0, 0 };
46 static guint release[2] = { 0, 0 };
47 static guint before[2] = { 0, 0 };
50 void map_render_paths();
51 void map_force_redraw();
52 gboolean curl_download_timeout();
53 gchar *map_construct_url(guint tilex, guint tiley, guint zoom);
54 gboolean map_download_idle_refresh(ProgressUpdateInfo * pui);
56 gboolean get_next_pui(gpointer key, gpointer value, ProgressUpdateInfo ** data)
62 gboolean curl_download_timeout()
64 static guint destroy_counter = 50;
65 gint num_transfers = 0, num_msgs = 0;
66 gint deletes_left = 50; /* only do 50 deletes at a time. */
68 vprintf("%s()\n", __PRETTY_FUNCTION__);
70 if (_curl_multi && CURLM_CALL_MULTI_PERFORM
71 == curl_multi_perform(_curl_multi, &num_transfers))
72 return TRUE; /* Give UI a chance first. */
75 && (msg = curl_multi_info_read(_curl_multi, &num_msgs))) {
76 if (msg->msg == CURLMSG_DONE) {
77 if (msg->easy_handle == _autoroute_data.curl_easy) {
78 /* This is the autoroute download. */
79 /* Now, parse the autoroute and update the display. */
80 if (_autoroute_data.enabled
82 _autoroute_data.rdl_data.bytes,
83 _autoroute_data.rdl_data.
85 /* Find the nearest route point, if we're connected. */
86 route_find_nearest_point();
89 cancel_autoroute(TRUE); /* We're done. Clean up. */
91 ProgressUpdateInfo *pui =
92 g_hash_table_lookup(_pui_by_easy,
94 g_queue_push_head(_curl_easy_queue,
96 g_hash_table_remove(_pui_by_easy,
99 if (msg->data.result != CURLE_OK)
100 g_unlink(pui->dest_str); /* Delete so we try again. */
101 curl_multi_remove_handle(_curl_multi,
103 g_idle_add_full(G_PRIORITY_HIGH_IDLE,
105 map_download_idle_refresh, pui,
111 /* Up to 1 transfer per tile. */
112 while (num_transfers < (BUF_WIDTH_TILES * BUF_HEIGHT_TILES)
113 && g_tree_nnodes(_pui_tree)) {
114 ProgressUpdateInfo *pui;
115 g_tree_foreach(_pui_tree, (GTraverseFunc) get_next_pui, &pui);
118 /* This is a download. */
120 g_tree_steal(_pui_tree, pui);
121 g_tree_insert(_downloading_tree, pui, pui);
124 map_construct_url(pui->tilex, pui->tiley,
127 g_strdup_printf("%s/%u/%u/%u.jpg",
128 pui->repo->cache_dir, pui->zoom,
129 pui->tilex, pui->tiley);
132 /* Failed to generate URL. */
133 g_idle_add_full(G_PRIORITY_HIGH_IDLE,
135 map_download_idle_refresh, pui,
140 /* Check to see if we need to overwrite. */
141 if (pui->retries > 0) {
142 /* We're not updating - check if file already exists. */
144 (pui->dest_str, G_FILE_TEST_EXISTS)) {
145 g_idle_add_full(G_PRIORITY_HIGH_IDLE,
147 map_download_idle_refresh,
153 /* Attempt to open the file for writing. */
154 if (!(f = g_fopen(pui->dest_str, "w"))
155 && errno == ENOENT) {
156 /* Directory doesn't exist yet - create it, then we'll retry */
157 gchar buffer[BUFFER_SIZE];
158 snprintf(buffer, sizeof(buffer), "%s/%u/%u",
159 pui->repo->cache_dir, pui->zoom,
161 g_mkdir_with_parents(buffer, 0775);
162 f = g_fopen(pui->dest_str, "w");
168 curl_easy = g_queue_pop_tail(_curl_easy_queue);
170 /* Need a new curl_easy. */
171 MACRO_CURL_EASY_INIT(curl_easy);
173 curl_easy_setopt(curl_easy, CURLOPT_URL,
175 curl_easy_setopt(curl_easy, CURLOPT_WRITEDATA,
177 g_hash_table_insert(_pui_by_easy, curl_easy,
180 /* Initialize CURL. */
181 _curl_multi = curl_multi_init();
182 /*curl_multi_setopt(_curl_multi, CURLMOPT_PIPELINING, 1); */
184 curl_multi_add_handle(_curl_multi, curl_easy);
187 /* Unable to download file. */
188 gchar buffer[BUFFER_SIZE];
189 snprintf(buffer, sizeof(buffer), "%s:\n%s",
190 _("Failed to open file for writing"),
192 MACRO_BANNER_SHOW_INFO(_window, buffer);
193 g_idle_add_full(G_PRIORITY_HIGH_IDLE,
195 map_download_idle_refresh, pui,
199 } else if (--deletes_left) {
200 /* This is a delete. */
201 gchar buffer[BUFFER_SIZE];
202 g_tree_steal(_pui_tree, pui);
203 g_tree_insert(_downloading_tree, pui, pui);
205 snprintf(buffer, sizeof(buffer), "%s/%u/%u/%u.jpg",
206 pui->repo->cache_dir, pui->zoom, pui->tilex,
209 g_idle_add_full(G_PRIORITY_HIGH_IDLE,
210 (GSourceFunc) map_download_idle_refresh,
216 if (!(num_transfers || g_tree_nnodes(_pui_tree))) {
217 /* Destroy curl after 50 counts (5 seconds). */
218 if (--destroy_counter) {
221 while ((curr = g_queue_pop_tail(_curl_easy_queue)))
222 curl_easy_cleanup(curr);
224 curl_multi_cleanup(_curl_multi);
228 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
232 destroy_counter = 50;
234 vprintf("%s(): return TRUE (%d, %d)\n", __PRETTY_FUNCTION__,
235 num_transfers, g_tree_nnodes(_pui_tree));
240 * Draw the current mark (representing the current GPS location) onto
241 * _map_widget. This method does not queue the draw area.
245 printf("%s()\n", __PRETTY_FUNCTION__);
247 gdk_draw_arc(_map_widget->window, _conn_state == RCVR_FIXED ? _gc[COLORABLE_MARK] : _gc[COLORABLE_MARK_OLD], FALSE, /* not filled. */
248 _mark_x1 - _draw_width, _mark_y1 - _draw_width,
249 2 * _draw_width, 2 * _draw_width, 0, 360 * 64);
250 gdk_draw_line(_map_widget->window,
251 _conn_state == RCVR_FIXED
253 ? _gc[COLORABLE_MARK_VELOCITY] : _gc[COLORABLE_MARK])
254 : _gc[COLORABLE_MARK_OLD],
255 _mark_x1, _mark_y1, _mark_x2, _mark_y2);
257 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
261 * "Set" the mark, which translates the current GPS position into on-screen
262 * units in preparation for drawing the mark with map_draw_mark().
266 printf("%s()\n", __PRETTY_FUNCTION__);
268 _mark_x1 = unit2x(_pos.unitx);
269 _mark_y1 = unit2y(_pos.unity);
270 _mark_x2 = _mark_x1 + (_show_velvec ? _vel_offsetx : 0);
271 _mark_y2 = _mark_y1 + (_show_velvec ? _vel_offsety : 0);
272 _mark_minx = MIN(_mark_x1, _mark_x2) - (2 * _draw_width);
273 _mark_miny = MIN(_mark_y1, _mark_y2) - (2 * _draw_width);
274 _mark_width = abs(_mark_x1 - _mark_x2) + (4 * _draw_width);
275 _mark_height = abs(_mark_y1 - _mark_y2) + (4 * _draw_width);
277 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
281 * Do an in-place scaling of a pixbuf's pixels at the given ratio from the
282 * given source location. It would have been nice if gdk_pixbuf provided
283 * this method, but I guess it's not general-purpose enough.
286 map_pixbuf_scale_inplace(GdkPixbuf * pixbuf, guint ratio_p2,
287 guint src_x, guint src_y)
289 guint dest_x = 0, dest_y = 0, dest_dim = TILE_SIZE_PIXELS;
290 guint rowstride = gdk_pixbuf_get_rowstride(pixbuf);
291 guint n_channels = gdk_pixbuf_get_n_channels(pixbuf);
292 guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);
294 vprintf("%s(%d, %d, %d)\n", __PRETTY_FUNCTION__, ratio_p2, src_x,
297 /* Sweep through the entire dest area, copying as necessary, but
298 * DO NOT OVERWRITE THE SOURCE AREA. We'll copy it afterward. */
300 guint src_dim = dest_dim >> ratio_p2;
301 guint src_endx = src_x - dest_x + src_dim;
303 for (y = dest_dim - 1; y >= 0; y--) {
304 guint src_offset_y, dest_offset_y;
305 src_offset_y = (src_y + (y >> ratio_p2)) * rowstride;
306 dest_offset_y = (dest_y + y) * rowstride;
308 if ((unsigned)(dest_y + y - src_y) < src_dim
309 && (unsigned)(dest_x + x - src_x) < src_dim)
311 for (; x >= 0; x--) {
312 guint src_offset, dest_offset, i;
313 src_offset = src_offset_y + (src_x + (x >> ratio_p2)) * n_channels;
314 dest_offset = dest_offset_y + (dest_x + x) * n_channels;
315 pixels[dest_offset] = pixels[src_offset];
316 for (i = n_channels - 1; i; i--)
317 pixels[dest_offset + i] = pixels[src_offset + i];
318 if ((unsigned)(dest_y + y - src_y) < src_dim && x == src_endx)
322 /* Reuse src_dim and src_endx to store new src_x and src_y. */
323 src_dim = src_x + ((src_x - dest_x) >> ratio_p2);
324 src_endx = src_y + ((src_y - dest_y) >> ratio_p2);
330 while ((dest_dim >>= ratio_p2) > 1);
332 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
336 * Trim pixbufs that are bigger than tiles. (Those pixbufs result, when
337 * captions should be cut off.)
339 GdkPixbuf *pixbuf_trim(GdkPixbuf * pixbuf)
341 vprintf("%s()\n", __PRETTY_FUNCTION__);
343 gdk_pixbuf_new(GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha(pixbuf),
344 8, TILE_SIZE_PIXELS, TILE_SIZE_PIXELS);
346 gdk_pixbuf_copy_area(pixbuf,
347 (gdk_pixbuf_get_width(pixbuf) -
348 TILE_SIZE_PIXELS) / 2,
349 (gdk_pixbuf_get_height(pixbuf) -
350 TILE_SIZE_PIXELS) / 2, TILE_SIZE_PIXELS,
351 TILE_SIZE_PIXELS, mpixbuf, 0, 0);
353 g_object_unref(pixbuf);
354 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
359 * Given a wms uri pattern, compute the coordinate transformation and
361 * 'proj' is used for the conversion
363 gchar *map_convert_wms_to_wms(gint tilex, gint tiley, gint zoomlevel,
367 gchar cmd[BUFFER_SIZE], srs[BUFFER_SIZE];
370 gfloat lon1, lat1, lon2, lat2;
372 gchar *widthstr = strcasestr(uri, "WIDTH=");
373 gchar *heightstr = strcasestr(uri, "HEIGHT=");
374 gchar *srsstr = strcasestr(uri, "SRS=EPSG");
375 gchar *srsstre = strchr(srsstr, '&');
376 vprintf("%s()\n", __PRETTY_FUNCTION__);
378 /* missing: test if found */
380 strncpy(srs + 4, srsstr + 8, 256);
381 /* missing: test srsstre-srsstr < 526 */
382 srs[srsstre - srsstr - 4] = 0;
383 /* convert to lower, as WMC is EPSG and cs2cs is epsg */
385 gint dwidth = widthstr ? atoi(widthstr + 6) - TILE_SIZE_PIXELS : 0;
386 gint dheight = heightstr ? atoi(heightstr + 7) - TILE_SIZE_PIXELS : 0;
388 unit2latlon(tile2zunit(tilex, zoomlevel)
389 - pixel2zunit(dwidth / 2, zoomlevel),
390 tile2zunit(tiley + 1, zoomlevel)
391 + pixel2zunit((dheight + 1) / 2, zoomlevel), lat1, lon1);
393 unit2latlon(tile2zunit(tilex + 1, zoomlevel)
394 + pixel2zunit((dwidth + 1) / 2, zoomlevel),
395 tile2zunit(tiley, zoomlevel)
396 - pixel2zunit(dheight / 2, zoomlevel), lat2, lon2);
398 setlocale(LC_NUMERIC, "C");
400 snprintf(cmd, sizeof(cmd),
401 "(echo \"%.6f %.6f\"; echo \"%.6f %.6f\") | "
402 "/usr/bin/cs2cs +proj=longlat +datum=WGS84 +to +init=%s -f %%.6f "
403 " > /tmp/tmpcs2cs ", lon1, lat1, lon2, lat2, srs);
404 vprintf("Running command: %s\n", cmd);
405 system_retcode = system(cmd);
408 g_printerr("cs2cs returned error code %d\n",
409 WEXITSTATUS(system_retcode));
410 else if (!(in = g_fopen("/tmp/tmpcs2cs", "r")))
411 g_printerr("Cannot open results of conversion\n");
413 fscanf(in, "%f %f %s %f %f", &lon1, &lat1, cmd, &lon2,
415 g_printerr("Wrong conversion\n");
419 ret = g_strdup_printf(uri, lon1, lat1, lon2, lat2);
422 setlocale(LC_NUMERIC, "");
424 vprintf("%s(): return %s\n", __PRETTY_FUNCTION__, ret);
429 * Given the xyz coordinates of our map coordinate system, write the qrst
430 * quadtree coordinates to buffer.
433 map_convert_coords_to_quadtree_string(gint x, gint y, gint zoomlevel,
434 gchar * buffer, const gchar initial,
435 const gchar * const quadrant)
439 vprintf("%s()\n", __PRETTY_FUNCTION__);
444 for (n = 16 - zoomlevel; n >= 0; n--) {
445 gint xbit = (x >> n) & 1;
446 gint ybit = (y >> n) & 1;
447 *ptr++ = quadrant[xbit + 2 * ybit];
450 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
454 * Construct the URL that we should fetch, based on the current URI format.
455 * This method works differently depending on if a "%s" string is present in
456 * the URI format, since that would indicate a quadtree-based map coordinate
459 gchar *map_construct_url(guint tilex, guint tiley, guint zoom)
461 vprintf("%s()\n", __PRETTY_FUNCTION__);
462 switch (_curr_repo->type) {
464 return g_strdup_printf(_curr_repo->url, tilex, tiley, zoom);
466 case REPOTYPE_XYZ_INV:
467 return g_strdup_printf(_curr_repo->url, 17 - zoom, tilex,
470 case REPOTYPE_QUAD_QRST:
472 gchar location[MAX_ZOOM + 2];
473 map_convert_coords_to_quadtree_string(tilex, tiley,
476 return g_strdup_printf(_curr_repo->url, location);
479 case REPOTYPE_QUAD_ZERO:
481 /* This is a zero-based quadtree URI. */
482 gchar location[MAX_ZOOM + 2];
483 map_convert_coords_to_quadtree_string(tilex, tiley,
486 return g_strdup_printf(_curr_repo->url, location);
490 return map_convert_wms_to_wms(tilex, tiley, zoom,
496 vprintf("%s(): ERROR\n", __PRETTY_FUNCTION__);
501 void con_check_connection(void)
503 if (!_iap_connected && !_iap_connecting) {
504 _iap_connecting = TRUE;
505 osso_iap_connect(OSSO_IAP_ANY, OSSO_IAP_REQUESTED_CONNECT, NULL);
509 void con_check_connection(void)
511 /* NetworkManager support ? */
516 * Initiate a download of the given xyz coordinates using the given buffer
517 * as the URL. If the map already exists on disk, or if we are already
518 * downloading the map, then this method does nothing.
520 void map_initiate_download(guint tilex, guint tiley, guint zoom, gint retries)
522 ProgressUpdateInfo *pui;
523 vprintf("%s(%u, %u, %u, %d)\n", __PRETTY_FUNCTION__, tilex, tiley, zoom,
526 con_check_connection();
528 pui = g_slice_new(ProgressUpdateInfo);
532 pui->priority = (abs((gint) tilex - unit2tile(_center.unitx))
533 + abs((gint) tiley - unit2tile(_center.unity)));
535 pui->priority = -pui->priority; /* "Negative" makes them lowest pri. */
536 pui->retries = retries;
537 pui->repo = _curr_repo;
539 if (g_tree_lookup(_pui_tree, pui)
540 || g_tree_lookup(_downloading_tree, pui)) {
541 /* Already downloading. */
542 g_slice_free(ProgressUpdateInfo, pui);
546 pui->dest_str = NULL;
549 g_tree_insert(_pui_tree, pui, pui);
551 if (_iap_connected && !_curl_sid)
553 _curl_sid = g_timeout_add(100, (GSourceFunc) curl_download_timeout, NULL);
555 if (!_num_downloads++ && !_download_banner)
556 _download_banner = hildon_banner_show_progress(_window, NULL,_("Downloading maps"));
558 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
562 map_render_tile(guint tilex, guint tiley, guint destx, guint desty,
565 GdkPixbuf *pixbuf = NULL;
567 if (tilex < _world_size_tiles && tiley < _world_size_tiles) {
568 /* The tile is possible. */
569 gchar buffer[BUFFER_SIZE];
570 GError *error = NULL;
572 vprintf("%s(%u, %u, %u, %u)\n", __PRETTY_FUNCTION__,
573 tilex, tiley, destx, desty);
575 for (zoff = (_curr_repo->double_size ? 1 : 0);
576 !pixbuf && (_zoom + zoff) <= MAX_ZOOM
577 && zoff <= TILE_SIZE_P2; zoff += 1) {
578 snprintf(buffer, sizeof(buffer), "%s/%u/%u/%u.jpg",
579 _curr_repo->cache_dir, _zoom + zoff,
580 (tilex >> zoff), (tiley >> zoff));
581 pixbuf = gdk_pixbuf_new_from_file(buffer, &error);
582 if (error || !pixbuf) {
583 g_unlink(buffer); /* Delete so we try again some other day. */
587 /* Download, if we should. */
589 && _curr_repo->type != REPOTYPE_NONE
592 (_curr_repo->double_size ? 1 : 0))
593 % _curr_repo->dl_zoom_steps)) {
595 map_initiate_download(tilex >>
601 -INITIAL_DOWNLOAD_RETRIES);
605 /* Check if we need to trim. */
606 if (gdk_pixbuf_get_width(pixbuf) !=
608 || gdk_pixbuf_get_height(pixbuf) !=
610 pixbuf = pixbuf_trim(pixbuf);
611 /* Check if we need to blit. */
613 map_pixbuf_scale_inplace(pixbuf, zoff,
630 gdk_draw_pixbuf(_map_pixmap,
635 TILE_SIZE_PIXELS, TILE_SIZE_PIXELS,
636 GDK_RGB_DITHER_NONE, 0, 0);
637 g_object_unref(pixbuf);
639 gdk_draw_rectangle(_map_pixmap, _map_widget->style->black_gc,
640 TRUE, destx, desty, TILE_SIZE_PIXELS,
644 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
647 gboolean map_download_idle_refresh(ProgressUpdateInfo * pui)
649 vprintf("%s(%p, %s)\n", __PRETTY_FUNCTION__, pui, pui->src_str);
651 /* Test if download succeeded (only if retries != 0). */
652 if (!pui->retries || g_file_test(pui->dest_str, G_FILE_TEST_EXISTS)) {
653 gint zoom_diff = pui->zoom - _zoom;
654 /* Only refresh at same or "lower" (more detailed) zoom level. */
655 if (zoom_diff >= 0) {
656 /* If zoom has changed since we first put in the request for
657 * this tile, then we may have to update more than one tile. */
658 guint tilex, tiley, tilex_end, tiley_end;
659 for (tilex = pui->tilex << zoom_diff,
660 tilex_end = tilex + (1 << zoom_diff);
661 tilex < tilex_end; tilex++) {
662 for (tiley = pui->tiley << zoom_diff,
663 tiley_end = tiley + (1 << zoom_diff);
664 tiley < tiley_end; tiley++) {
665 if ((tilex - _base_tilex) < 4
666 && (tiley - _base_tiley) < 3) {
667 map_render_tile(tilex, tiley,
677 MACRO_MAP_RENDER_DATA();
678 gtk_widget_queue_draw_area
682 TILE_SIZE_P2) - _offsetx,
685 TILE_SIZE_P2) - _offsety,
693 /* Else the download failed. Update retries and maybe try again. */
695 if (pui->retries > 0)
697 else if (pui->retries < 0)
700 /* removal automatically calls progress_update_info_free(). */
701 g_tree_steal(_downloading_tree, pui);
702 g_tree_insert(_pui_tree, pui, pui);
704 if (_iap_connected && !_curl_sid)
706 _curl_sid = g_timeout_add(100,
708 curl_download_timeout,
710 /* Don't do anything else. */
713 /* No more retries left - something must be wrong. */
714 MACRO_BANNER_SHOW_INFO(_window,
716 ("Error in download. Check internet connection"
717 " and/or Map Repository URL Format."));
721 /* removal automatically calls progress_update_info_free(). */
722 g_tree_remove(_downloading_tree, pui);
724 if (++_curr_download == _num_downloads) {
725 gtk_widget_destroy(_download_banner);
726 _download_banner = NULL;
727 _num_downloads = _curr_download = 0;
729 hildon_banner_set_fraction(HILDON_BANNER(_download_banner),
731 (double)_num_downloads);
733 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
738 * Force a redraw of the entire _map_pixmap, including fetching the
739 * background maps from disk and redrawing the tracks on top of them.
741 void map_force_redraw()
744 printf("%s()\n", __PRETTY_FUNCTION__);
746 for (new_y = 0; new_y < BUF_HEIGHT_TILES; ++new_y)
747 for (new_x = 0; new_x < BUF_WIDTH_TILES; ++new_x) {
748 map_render_tile(_base_tilex + new_x,
750 new_x * TILE_SIZE_PIXELS,
751 new_y * TILE_SIZE_PIXELS, FALSE);
753 MACRO_MAP_RENDER_DATA();
754 MACRO_QUEUE_DRAW_AREA();
756 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
760 * Set the current zoom level. If the given zoom level is the same as the
761 * current zoom level, or if the new zoom is invalid
762 * (not MIN_ZOOM <= new_zoom < MAX_ZOOM), then this method does nothing.
764 void map_set_zoom(guint new_zoom)
766 printf("%s(%d)\n", __PRETTY_FUNCTION__, _zoom);
768 /* Note that, since new_zoom is a guint and MIN_ZOOM is 0, this if
769 * condition also checks for new_zoom >= MIN_ZOOM. */
770 if (new_zoom > (MAX_ZOOM - 1))
772 _zoom = new_zoom / _curr_repo->view_zoom_steps
773 * _curr_repo->view_zoom_steps;
774 _world_size_tiles = unit2tile(WORLD_SIZE_UNITS);
776 /* If we're leading, update the center to reflect new zoom level. */
777 MACRO_RECALC_CENTER(_center.unitx, _center.unity);
779 /* Update center bounds to reflect new zoom level. */
780 _min_center.unitx = pixel2unit(grid2pixel(_screen_grids_halfwidth));
781 _min_center.unity = pixel2unit(grid2pixel(_screen_grids_halfheight));
783 WORLD_SIZE_UNITS - grid2unit(_screen_grids_halfwidth) - 1;
785 WORLD_SIZE_UNITS - grid2unit(_screen_grids_halfheight) - 1;
787 BOUND(_center.unitx, _min_center.unitx, _max_center.unitx);
788 BOUND(_center.unity, _min_center.unity, _max_center.unity);
790 _base_tilex = grid2tile((gint) pixel2grid((gint)
791 unit2pixel((gint) _center.
793 - (gint) _screen_grids_halfwidth);
794 _base_tiley = grid2tile(pixel2grid(unit2pixel(_center.unity))
795 - _screen_grids_halfheight);
797 /* New zoom level, so we can't reuse the old buffer's pixels. */
799 /* Update state variables. */
800 MACRO_RECALC_OFFSET();
801 MACRO_RECALC_FOCUS_BASE();
802 MACRO_RECALC_FOCUS_SIZE();
807 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
811 * Center the view on the given unitx/unity.
813 void map_center_unit(guint new_center_unitx, guint new_center_unity)
815 gint new_base_tilex, new_base_tiley;
817 guint j, k, base_new_x, base_old_x, old_x, old_y, iox, ioy;
818 printf("%s(%d, %d)\n", __PRETTY_FUNCTION__,
819 new_center_unitx, new_center_unity);
821 /* Assure that _center.unitx/y are bounded. */
822 BOUND(new_center_unitx, _min_center.unitx, _max_center.unitx);
823 BOUND(new_center_unity, _min_center.unity, _max_center.unity);
825 _center.unitx = new_center_unitx;
826 _center.unity = new_center_unity;
828 new_base_tilex = grid2tile((gint) pixel2grid((gint)
829 unit2pixel((gint) _center.
831 - (gint) _screen_grids_halfwidth);
832 new_base_tiley = grid2tile(pixel2grid(unit2pixel(_center.unity))
833 - _screen_grids_halfheight);
835 /* Same zoom level, so it's likely that we can reuse some of the old
836 * buffer's pixels. */
838 if (new_base_tilex != _base_tilex || new_base_tiley != _base_tiley) {
839 /* If copying from old parts to new parts, we need to make sure we
840 * don't overwrite the old parts when copying, so set up new_x,
841 * new_y, old_x, old_y, iox, and ioy with that in mind. */
842 if (new_base_tiley < _base_tiley) {
843 /* New is lower than old - start at bottom and go up. */
844 new_y = BUF_HEIGHT_TILES - 1;
847 /* New is higher than old - start at top and go down. */
851 if (new_base_tilex < _base_tilex) {
852 /* New is righter than old - start at right and go left. */
853 base_new_x = BUF_WIDTH_TILES - 1;
856 /* New is lefter than old - start at left and go right. */
861 /* Iterate over the y tile values. */
862 old_y = new_y + new_base_tiley - _base_tiley;
863 base_old_x = base_new_x + new_base_tilex - _base_tilex;
864 _base_tilex = new_base_tilex;
865 _base_tiley = new_base_tiley;
866 for (j = 0; j < BUF_HEIGHT_TILES;
867 ++j, new_y += ioy, old_y += ioy) {
870 /* Iterate over the x tile values. */
871 for (k = 0; k < BUF_WIDTH_TILES;
872 ++k, new_x += iox, old_x += iox) {
873 /* Can we get this grid block from the old buffer?. */
874 if (old_x >= 0 && old_x < BUF_WIDTH_TILES
875 && old_y >= 0 && old_y < BUF_HEIGHT_TILES) {
876 /* Copy from old buffer to new buffer. */
877 gdk_draw_drawable(_map_pixmap,
891 map_render_tile(new_base_tilex + new_x,
892 new_base_tiley + new_y,
901 MACRO_MAP_RENDER_DATA();
904 MACRO_RECALC_OFFSET();
905 MACRO_RECALC_FOCUS_BASE();
908 MACRO_QUEUE_DRAW_AREA();
910 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
914 * Pan the view by the given number of units in the X and Y directions.
916 void map_pan(gint delta_unitx, gint delta_unity)
918 printf("%s(%d, %d)\n", __PRETTY_FUNCTION__, delta_unitx, delta_unity);
920 if (_center_mode > 0)
921 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM
922 (_menu_ac_none_item), TRUE);
923 map_center_unit(_center.unitx + delta_unitx,
924 _center.unity + delta_unity);
926 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
930 * Initiate a move of the mark from the old location to the current
931 * location. This function queues the draw area of the old mark (to force
932 * drawing of the background map), then updates the mark, then queus the
933 * draw area of the new mark.
937 printf("%s()\n", __PRETTY_FUNCTION__);
939 /* Just queue the old and new draw areas. */
940 gtk_widget_queue_draw_area(_map_widget,
942 _mark_miny, _mark_width, _mark_height);
944 gtk_widget_queue_draw_area(_map_widget,
946 _mark_miny, _mark_width, _mark_height);
948 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
952 * Make sure the mark is up-to-date. This function triggers a panning of
953 * the view if the mark is appropriately close to the edge of the view.
957 printf("%s()\n", __PRETTY_FUNCTION__);
959 guint new_center_unitx;
960 guint new_center_unity;
962 MACRO_RECALC_CENTER(new_center_unitx, new_center_unity);
964 if ((new_center_unitx - _focus.unitx) < _focus_unitwidth
965 && (new_center_unity - _focus.unity) < _focus_unitheight)
966 /* We're not changing the view - just move the mark. */
969 map_center_unit(new_center_unitx, new_center_unity);
971 /* Draw speed info */
975 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
979 * Render a single track line to _map_pixmap. If either point on the line
980 * is a break (defined as unity == 0), a circle is drawn at the other point.
981 * IT IS AN ERROR FOR BOTH POINTS TO INDICATE A BREAK.
984 map_render_segment(GdkGC * gc_norm, GdkGC * gc_alt,
985 guint unitx1, guint unity1, guint unitx2, guint unity2)
987 vprintf("%s()\n", __PRETTY_FUNCTION__);
991 x2 = unit2bufx(unitx2);
992 y2 = unit2bufy(unity2);
993 /* Make sure this circle will be visible. */
994 if ((x2 < BUF_WIDTH_PIXELS)
995 && (y2 < BUF_HEIGHT_PIXELS))
996 gdk_draw_arc(_map_pixmap, gc_alt, FALSE, /* FALSE: not filled. */
997 x2 - _draw_width, y2 - _draw_width, 2 * _draw_width, 2 * _draw_width, 0, /* start at 0 degrees. */
999 } else if (!unity2) {
1001 x1 = unit2bufx(unitx1);
1002 y1 = unit2bufy(unity1);
1003 /* Make sure this circle will be visible. */
1004 if ((x1 < BUF_WIDTH_PIXELS)
1005 && ((unsigned)y1 < BUF_HEIGHT_PIXELS))
1006 gdk_draw_arc(_map_pixmap, gc_alt, FALSE, /* FALSE: not filled. */
1007 x1 - _draw_width, y1 - _draw_width, 2 * _draw_width, 2 * _draw_width, 0, /* start at 0 degrees. */
1010 gint x1, y1, x2, y2;
1011 x1 = unit2bufx(unitx1);
1012 y1 = unit2bufy(unity1);
1013 x2 = unit2bufx(unitx2);
1014 y2 = unit2bufy(unity2);
1015 /* Make sure this line could possibly be visible. */
1016 if (!((x1 > BUF_WIDTH_PIXELS && x2 > BUF_WIDTH_PIXELS)
1017 || (x1 < 0 && x2 < 0)
1018 || (y1 > BUF_HEIGHT_PIXELS && y2 > BUF_HEIGHT_PIXELS)
1019 || (y1 < 0 && y2 < 0)))
1020 gdk_draw_line(_map_pixmap, gc_norm, x1, y1, x2, y2);
1023 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
1027 * Render all track data onto the _map_pixmap. Note that this does not
1028 * clear the pixmap of previous track data (use map_force_redraw() for
1029 * that), and also note that this method does not queue any redraws, so it
1030 * is up to the caller to decide which part of the track really needs to be
1033 void map_render_path(Path * path, GdkGC ** gc)
1037 printf("%s()\n", __PRETTY_FUNCTION__);
1039 /* gc is a pointer to the first GC to use (for plain points). (gc + 1)
1040 * is a pointer to the GC to use for waypoints, and (gc + 2) is a pointer
1041 * to the GC to use for breaks. */
1043 /* else there is a route to draw. */
1044 for (curr = path->head, wcurr = path->whead; curr++ != path->tail;) {
1045 /* Draw the line from (curr - 1) to (curr). */
1046 map_render_segment(gc[0], gc[2],
1047 curr[-1].unitx, curr[-1].unity, curr->unitx,
1050 /* Now, check if curr is a waypoint. */
1051 if (wcurr && wcurr <= path->wtail && wcurr->point == curr) {
1052 guint x1 = unit2bufx(wcurr->point->unitx);
1053 guint y1 = unit2bufy(wcurr->point->unity);
1054 if ((x1 < BUF_WIDTH_PIXELS)
1055 && (y1 < BUF_HEIGHT_PIXELS)) {
1056 gdk_draw_arc(_map_pixmap, gc[1], FALSE, /* FALSE: not filled. */
1057 x1 - _draw_width, y1 - _draw_width, 2 * _draw_width, 2 * _draw_width, 0, /* start at 0 degrees. */
1064 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
1067 void map_render_paths()
1069 printf("%s()\n", __PRETTY_FUNCTION__);
1071 if ((_show_tracks & ROUTES_MASK) && _route.head != _route.tail) {
1072 map_render_path(&_route, _gc + COLORABLE_ROUTE);
1074 /* Now, draw the next waypoint on top of all other waypoints. */
1076 guint x1 = unit2bufx(_next_way->point->unitx);
1077 guint y1 = unit2bufy(_next_way->point->unity);
1078 if ((x1 < BUF_WIDTH_PIXELS)
1079 && (y1 < BUF_HEIGHT_PIXELS)) {
1080 /* Draw the next waypoint as a break. */
1081 gdk_draw_arc(_map_pixmap, _gc[COLORABLE_ROUTE_BREAK], FALSE, /* FALSE: not filled. */
1082 x1 - _draw_width, y1 - _draw_width, 2 * _draw_width, 2 * _draw_width, 0, /* start at 0 degrees. */
1087 if (_show_tracks & TRACKS_MASK)
1088 map_render_path(&_track, _gc + COLORABLE_TRACK);
1090 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
1093 static gboolean map_follow_move(GtkWidget * widget, GdkEventMotion * event)
1096 GdkModifierType state;
1100 if (event->is_hint) {
1101 gdk_window_get_pointer(event->window, &xx, &yy, &state);
1102 state = event->state;
1106 state = event->state;
1109 unitx = x2unit((gint) (xx - before[0]));
1110 unity = y2unit((gint) (yy - before[1]));
1111 cx = unit2x(_center.unitx);
1112 cy = unit2y(_center.unity);
1114 map_center_unit(x2unit((gint) (cx - (xx - before[0]))),
1115 y2unit((gint) (cy - (yy - before[1]))));
1121 gboolean map_key_zoom_timeout()
1123 printf("%s()\n", __PRETTY_FUNCTION__);
1124 if (_key_zoom_new < _zoom) {
1125 /* We're currently zooming in (_zoom is decreasing). */
1126 guint test = _key_zoom_new - _curr_repo->view_zoom_steps;
1127 if (test < MAX_ZOOM)
1128 /* We can zoom some more. Hurray! */
1129 _key_zoom_new = test;
1131 /* We can't zoom anymore. Booooo! */
1134 /* We're currently zooming out (_zoom is increasing). */
1135 guint test = _key_zoom_new + _curr_repo->view_zoom_steps;
1136 if (test < MAX_ZOOM)
1137 /* We can zoom some more. Hurray! */
1138 _key_zoom_new = test;
1140 /* We can't zoom anymore. Booooo! */
1144 /* We can zoom more - tell them how much they're zooming. */
1147 snprintf(buffer, sizeof(buffer),
1148 "%s %d", _("Zoom to Level"), _key_zoom_new);
1149 MACRO_BANNER_SHOW_INFO(_window, buffer);
1151 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
1155 gboolean map_cb_expose(GtkWidget * widget, GdkEventExpose * event)
1157 printf("%s(%d, %d, %d, %d)\n", __PRETTY_FUNCTION__,
1158 event->area.x, event->area.y,
1159 event->area.width, event->area.height);
1161 gdk_draw_drawable(_map_widget->window,
1162 _gc[COLORABLE_MARK],
1164 event->area.x + _offsetx, event->area.y + _offsety,
1165 event->area.x, event->area.y,
1166 event->area.width, event->area.height);
1169 /* Draw scale, if necessary. */
1171 gdk_rectangle_intersect(&event->area, &_scale_rect,
1173 if (event->area.width && event->area.height) {
1174 gdk_draw_rectangle(_map_widget->window,
1175 _map_widget->style->
1176 bg_gc[GTK_WIDGET_STATE(_map_widget)],
1177 TRUE, _scale_rect.x, _scale_rect.y,
1179 _scale_rect.height);
1180 gdk_draw_rectangle(_map_widget->window,
1181 _map_widget->style->
1182 fg_gc[GTK_WIDGET_STATE(_map_widget)],
1183 FALSE, _scale_rect.x, _scale_rect.y,
1185 _scale_rect.height);
1187 /* Now calculate and draw the distance. */
1191 gfloat lat1, lon1, lat2, lon2;
1194 unit2latlon(_center.unitx -
1195 pixel2unit(SCALE_WIDTH / 2 - 4),
1196 _center.unity, lat1, lon1);
1197 unit2latlon(_center.unitx +
1198 pixel2unit(SCALE_WIDTH / 2 - 4),
1199 _center.unity, lat2, lon2);
1201 calculate_distance(lat1, lon1, lat2, lon2)
1202 * UNITS_CONVERT[_units];
1205 snprintf(buffer, sizeof(buffer),
1206 "%0.2f %s", distance,
1207 UNITS_TEXT[_units]);
1208 else if (distance < 10.f)
1209 snprintf(buffer, sizeof(buffer),
1210 "%0.1f %s", distance,
1211 UNITS_TEXT[_units]);
1213 snprintf(buffer, sizeof(buffer),
1214 "%0.f %s", distance,
1215 UNITS_TEXT[_units]);
1216 pango_layout_set_text(_scale_layout, buffer,
1219 pango_layout_get_pixel_size(_scale_layout,
1222 /* Draw the layout itself. */
1223 gdk_draw_layout(_map_widget->window,
1224 _map_widget->style->
1225 fg_gc[GTK_WIDGET_STATE
1228 (_scale_rect.width - width) / 2,
1229 _scale_rect.y, _scale_layout);
1231 /* Draw little hashes on the ends. */
1232 gdk_draw_line(_map_widget->window,
1233 _map_widget->style->
1234 fg_gc[GTK_WIDGET_STATE
1238 _scale_rect.height / 2 - 4,
1241 _scale_rect.height / 2 + 4);
1242 gdk_draw_line(_map_widget->window,
1243 _map_widget->style->
1244 fg_gc[GTK_WIDGET_STATE
1248 _scale_rect.height / 2,
1250 (_scale_rect.width - width) / 2 -
1253 _scale_rect.height / 2);
1254 gdk_draw_line(_map_widget->window,
1255 _map_widget->style->
1256 fg_gc[GTK_WIDGET_STATE
1259 _scale_rect.width - 4,
1261 _scale_rect.height / 2 - 4,
1263 _scale_rect.width - 4,
1265 _scale_rect.height / 2 + 4);
1266 gdk_draw_line(_map_widget->window,
1267 _map_widget->style->
1268 fg_gc[GTK_WIDGET_STATE
1271 _scale_rect.width - 4,
1273 _scale_rect.height / 2,
1275 (_scale_rect.width + width) / 2 +
1278 _scale_rect.height / 2);
1283 vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
1287 gboolean map_cb_button_press(GtkWidget * widget, GdkEventButton * event)
1289 printf("%s()\n", __PRETTY_FUNCTION__);
1291 g_print("Pbutton %d\n", event->button);
1293 switch (event->button) {
1295 if (event->type == GDK_2BUTTON_PRESS) {
1296 map_set_zoom(_zoom - 1);
1300 if (event->type == GDK_3BUTTON_PRESS) {
1305 press[0] = event->x;
1306 press[1] = event->y;
1307 before[0] = press[0];
1308 before[1] = press[1];
1311 g_signal_connect(G_OBJECT(_map_widget),
1312 "motion_notify_event",
1313 G_CALLBACK(map_follow_move), NULL);
1316 _cmenu_position_x = event->x + 0.5;
1317 _cmenu_position_y = event->y + 0.5;
1319 gtk_menu_popup(GTK_MENU(_menu_map), NULL, NULL, NULL, NULL,
1320 event->button, gtk_get_current_event_time());
1325 /* Return FALSE to allow context menu to work. */
1326 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
1330 int map_zoom(gint zdir)
1335 nzoom = _zoom + zdir;
1337 if ((nzoom > 0) && (nzoom < MAX_ZOOM - 1)) {
1338 snprintf(buffer, sizeof(buffer), "%s %d",
1339 _("Zoom to Level"), nzoom);
1340 MACRO_BANNER_SHOW_INFO(_window, buffer);
1341 map_set_zoom(nzoom);
1347 static void map_draw_track(gint x, gint y)
1349 _pos.unitx = x2unit((gint) (x + 0.5));
1350 _pos.unity = y2unit((gint) (y + 0.5));
1351 unit2latlon(_pos.unitx, _pos.unity, _gps.lat, _gps.lon);
1354 track_add(time(NULL), FALSE);
1358 gboolean map_cb_scroll_event(GtkWidget * widget, GdkEventScroll * event)
1361 if (event->direction == GDK_SCROLL_UP)
1362 map_set_zoom(_zoom - 1);
1363 else if (event->direction == GDK_SCROLL_DOWN)
1364 map_set_zoom(_zoom + 1);
1369 gboolean map_cb_button_release(GtkWidget * widget, GdkEventButton * event)
1371 printf("%s()\n", __PRETTY_FUNCTION__);
1373 g_print("Rbutton %d\n", event->button);
1375 switch (event->button) {
1377 if (_center_mode > 0)
1378 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM
1379 (_menu_ac_none_item),
1382 map_center_unit(x2unit((gint) (event->x + 0.5)),
1383 y2unit((gint) (event->y + 0.5)));
1386 release[0] = event->x;
1387 release[1] = event->y;
1389 g_signal_handler_disconnect(G_OBJECT(_map_widget), _id);
1399 /* map_draw_track(event->x, event->y); */
1403 /* Return FALSE to avoid context menu (if it hasn't popped up already). */
1404 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);