Contents of /trunk/src/osm-gps-map.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 57 - (show annotations)
Sun Aug 16 19:29:01 2009 UTC (14 years, 8 months ago) by harbaum
File MIME type: text/plain
File size: 93226 byte(s)
osm-gps-map built-in balloon
1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */
2 /* vim:set et sw=4 ts=4 cino=t0,(0: */
3 /*
4 * osm-gps-map.c
5 * Copyright (C) Marcus Bauer 2008 <marcus.bauer@gmail.com>
6 * Copyright (C) John Stowers 2009 <john.stowers@gmail.com>
7 *
8 * Contributions by
9 * Everaldo Canuto 2009 <everaldo.canuto@gmail.com>
10 *
11 * osm-gps-map.c is free software: you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License as published by the
13 * Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * osm-gps-map.c is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 * See the GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along
22 * with this program. If not, see <http://www.gnu.org/licenses/>.
23 */
24
25 #include "config.h"
26
27 #include <fcntl.h>
28 #include <math.h>
29 #include <unistd.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34 #include <gdk/gdk.h>
35 #include <glib.h>
36 #include <glib/gstdio.h>
37 #include <glib/gprintf.h>
38 #include <libsoup/soup.h>
39
40 #include "converter.h"
41 #include "osm-gps-map-types.h"
42 #include "osm-gps-map.h"
43
44 #ifdef USE_CAIRO
45 #include <cairo.h>
46 #endif
47
48 #define ENABLE_DEBUG 0
49
50 #define EXTRA_BORDER (TILESIZE / 2)
51
52 struct _OsmGpsMapPrivate
53 {
54 GHashTable *tile_queue;
55 GHashTable *missing_tiles;
56 GHashTable *tile_cache;
57
58 int map_zoom;
59 int max_zoom;
60 int min_zoom;
61 gboolean map_auto_center;
62 gboolean map_auto_download;
63 int map_x;
64 int map_y;
65
66 /* Latitude and longitude of the center of the map, in radians */
67 gfloat center_rlat;
68 gfloat center_rlon;
69
70 guint max_tile_cache_size;
71 /* Incremented at each redraw */
72 guint redraw_cycle;
73 /* ID of the idle redraw operation */
74 guint idle_map_redraw;
75
76 //how we download tiles
77 SoupSession *soup_session;
78 char *proxy_uri;
79
80 //where downloaded tiles are cached
81 char *cache_dir;
82 gboolean cache_dir_is_full_path;
83
84 //contains flags indicating the various special characters
85 //the uri string contains, that will be replaced when calculating
86 //the uri to download.
87 OsmGpsMapSource_t map_source;
88 char *repo_uri;
89 char *image_format;
90 int uri_format;
91 //flag indicating if the map source is located on the google
92 gboolean the_google;
93
94 //gps tracking state
95 gboolean record_trip_history;
96 gboolean show_trip_history;
97 GSList *trip_history;
98 coord_t *gps;
99 gboolean gps_valid;
100
101 //a balloon with additional info
102 coord_t *balloon;
103 gboolean balloon_valid;
104
105 //additional images or tracks added to the map
106 GSList *tracks;
107 GSList *images;
108
109 //Used for storing the joined tiles
110 GdkPixmap *pixmap;
111 GdkGC *gc_map;
112
113 //The tile painted when one cannot be found
114 GdkPixbuf *null_tile;
115
116 //For tracking click and drag
117 int drag_counter;
118 int drag_mouse_dx;
119 int drag_mouse_dy;
120 int drag_start_mouse_x;
121 int drag_start_mouse_y;
122 int drag_start_map_x;
123 int drag_start_map_y;
124
125 //for customizing the redering of the gps track
126 int ui_gps_track_width;
127 int ui_gps_point_inner_radius;
128 int ui_gps_point_outer_radius;
129
130 guint is_disposed : 1;
131 guint dragging : 1;
132 guint center_coord_set : 1;
133 };
134
135 #define OSM_GPS_MAP_PRIVATE(o) (OSM_GPS_MAP (o)->priv)
136
137 typedef struct
138 {
139 GdkPixbuf *pixbuf;
140 /* We keep track of the number of the redraw cycle this tile was last used,
141 * so that osm_gps_map_purge_cache() can remove the older ones */
142 guint redraw_cycle;
143 } OsmCachedTile;
144
145 enum
146 {
147 PROP_0,
148
149 PROP_AUTO_CENTER,
150 PROP_RECORD_TRIP_HISTORY,
151 PROP_SHOW_TRIP_HISTORY,
152 PROP_AUTO_DOWNLOAD,
153 PROP_REPO_URI,
154 PROP_PROXY_URI,
155 PROP_TILE_CACHE_DIR,
156 PROP_TILE_CACHE_DIR_IS_FULL_PATH,
157 PROP_ZOOM,
158 PROP_MAX_ZOOM,
159 PROP_MIN_ZOOM,
160 PROP_LATITUDE,
161 PROP_LONGITUDE,
162 PROP_MAP_X,
163 PROP_MAP_Y,
164 PROP_TILES_QUEUED,
165 PROP_GPS_TRACK_WIDTH,
166 PROP_GPS_POINT_R1,
167 PROP_GPS_POINT_R2,
168 PROP_MAP_SOURCE,
169 PROP_IMAGE_FORMAT
170 };
171
172 G_DEFINE_TYPE (OsmGpsMap, osm_gps_map, GTK_TYPE_DRAWING_AREA);
173
174 /*
175 * Drawing function forward defintions
176 */
177 static gchar *replace_string(const gchar *src, const gchar *from, const gchar *to);
178 static void inspect_map_uri(OsmGpsMap *map);
179 static gchar *replace_map_uri(OsmGpsMap *map, const gchar *uri, int zoom, int x, int y);
180 static void osm_gps_map_print_images (OsmGpsMap *map);
181 static void osm_gps_map_draw_gps_point (OsmGpsMap *map);
182 static void osm_gps_map_blit_tile(OsmGpsMap *map, GdkPixbuf *pixbuf, int offset_x, int offset_y);
183 #ifdef LIBSOUP22
184 static void osm_gps_map_tile_download_complete (SoupMessage *msg, gpointer user_data);
185 #else
186 static void osm_gps_map_tile_download_complete (SoupSession *session, SoupMessage *msg, gpointer user_data);
187 #endif
188 static void osm_gps_map_download_tile (OsmGpsMap *map, int zoom, int x, int y, gboolean redraw);
189 static void osm_gps_map_load_tile (OsmGpsMap *map, int zoom, int x, int y, int offset_x, int offset_y);
190 static void osm_gps_map_fill_tiles_pixel (OsmGpsMap *map);
191 static gboolean osm_gps_map_map_redraw (OsmGpsMap *map);
192 static void osm_gps_map_map_redraw_idle (OsmGpsMap *map);
193
194 static void
195 cached_tile_free (OsmCachedTile *tile)
196 {
197 g_object_unref (tile->pixbuf);
198 g_slice_free (OsmCachedTile, tile);
199 }
200
201 /*
202 * Description:
203 * Find and replace text within a string.
204 *
205 * Parameters:
206 * src (in) - pointer to source string
207 * from (in) - pointer to search text
208 * to (in) - pointer to replacement text
209 *
210 * Returns:
211 * Returns a pointer to dynamically-allocated memory containing string
212 * with occurences of the text pointed to by 'from' replaced by with the
213 * text pointed to by 'to'.
214 */
215 static gchar *
216 replace_string(const gchar *src, const gchar *from, const gchar *to)
217 {
218 size_t size = strlen(src) + 1;
219 size_t fromlen = strlen(from);
220 size_t tolen = strlen(to);
221
222 /* Allocate the first chunk with enough for the original string. */
223 gchar *value = g_malloc(size);
224
225
226 /* We need to return 'value', so let's make a copy to mess around with. */
227 gchar *dst = value;
228
229 if ( value != NULL )
230 {
231 for ( ;; )
232 {
233 /* Try to find the search text. */
234 const gchar *match = g_strstr_len(src, size, from);
235 if ( match != NULL )
236 {
237 gchar *temp;
238 /* Find out how many characters to copy up to the 'match'. */
239 size_t count = match - src;
240
241
242 /* Calculate the total size the string will be after the
243 * replacement is performed. */
244 size += tolen - fromlen;
245
246 temp = g_realloc(value, size);
247 if ( temp == NULL )
248 {
249 g_free(value);
250 return NULL;
251 }
252
253 /* we'll want to return 'value' eventually, so let's point it
254 * to the memory that we are now working with.
255 * And let's not forget to point to the right location in
256 * the destination as well. */
257 dst = temp + (dst - value);
258 value = temp;
259
260 /*
261 * Copy from the source to the point where we matched. Then
262 * move the source pointer ahead by the amount we copied. And
263 * move the destination pointer ahead by the same amount.
264 */
265 g_memmove(dst, src, count);
266 src += count;
267 dst += count;
268
269 /* Now copy in the replacement text 'to' at the position of
270 * the match. Adjust the source pointer by the text we replaced.
271 * Adjust the destination pointer by the amount of replacement
272 * text. */
273 g_memmove(dst, to, tolen);
274 src += fromlen;
275 dst += tolen;
276 }
277 else
278 {
279 /*
280 * Copy any remaining part of the string. This includes the null
281 * termination character.
282 */
283 strcpy(dst, src);
284 break;
285 }
286 }
287 }
288 return value;
289 }
290
291 static void
292 map_convert_coords_to_quadtree_string(OsmGpsMap *map, gint x, gint y, gint zoomlevel,
293 gchar *buffer, const gchar initial,
294 const gchar *const quadrant)
295 {
296 gchar *ptr = buffer;
297 gint n;
298
299 if (initial)
300 *ptr++ = initial;
301
302 for(n = zoomlevel-1; n >= 0; n--)
303 {
304 gint xbit = (x >> n) & 1;
305 gint ybit = (y >> n) & 1;
306 *ptr++ = quadrant[xbit + 2 * ybit];
307 }
308
309 *ptr++ = '\0';
310 }
311
312
313 static void
314 inspect_map_uri(OsmGpsMap *map)
315 {
316 OsmGpsMapPrivate *priv = map->priv;
317
318 if (g_strrstr(priv->repo_uri, URI_MARKER_X))
319 priv->uri_format |= URI_HAS_X;
320
321 if (g_strrstr(priv->repo_uri, URI_MARKER_Y))
322 priv->uri_format |= URI_HAS_Y;
323
324 if (g_strrstr(priv->repo_uri, URI_MARKER_Z))
325 priv->uri_format |= URI_HAS_Z;
326
327 if (g_strrstr(priv->repo_uri, URI_MARKER_S))
328 priv->uri_format |= URI_HAS_S;
329
330 if (g_strrstr(priv->repo_uri, URI_MARKER_Q))
331 priv->uri_format |= URI_HAS_Q;
332
333 if (g_strrstr(priv->repo_uri, URI_MARKER_Q0))
334 priv->uri_format |= URI_HAS_Q0;
335
336 if (g_strrstr(priv->repo_uri, URI_MARKER_YS))
337 priv->uri_format |= URI_HAS_YS;
338
339 if (g_strrstr(priv->repo_uri, URI_MARKER_R))
340 priv->uri_format |= URI_HAS_R;
341
342 if (g_strrstr(priv->repo_uri, "google.com"))
343 priv->the_google = TRUE;
344
345 g_debug("URI Format: 0x%X (google: %X)", priv->uri_format, priv->the_google);
346
347 }
348
349 static gchar *
350 replace_map_uri(OsmGpsMap *map, const gchar *uri, int zoom, int x, int y)
351 {
352 OsmGpsMapPrivate *priv = map->priv;
353 char *url;
354 unsigned int i;
355 char location[22];
356
357 i = 1;
358 url = g_strdup(uri);
359 while (i < URI_FLAG_END)
360 {
361 char *s = NULL;
362 char *old;
363
364 old = url;
365 switch(i & priv->uri_format)
366 {
367 case URI_HAS_X:
368 s = g_strdup_printf("%d", x);
369 url = replace_string(url, URI_MARKER_X, s);
370 //g_debug("FOUND " URI_MARKER_X);
371 break;
372 case URI_HAS_Y:
373 s = g_strdup_printf("%d", y);
374 url = replace_string(url, URI_MARKER_Y, s);
375 //g_debug("FOUND " URI_MARKER_Y);
376 break;
377 case URI_HAS_Z:
378 s = g_strdup_printf("%d", zoom);
379 url = replace_string(url, URI_MARKER_Z, s);
380 //g_debug("FOUND " URI_MARKER_Z);
381 break;
382 case URI_HAS_S:
383 s = g_strdup_printf("%d", priv->max_zoom-zoom);
384 url = replace_string(url, URI_MARKER_S, s);
385 //g_debug("FOUND " URI_MARKER_S);
386 break;
387 case URI_HAS_Q:
388 map_convert_coords_to_quadtree_string(map,x,y,zoom,location,'t',"qrts");
389 s = g_strdup_printf("%s", location);
390 url = replace_string(url, URI_MARKER_Q, s);
391 //g_debug("FOUND " URI_MARKER_Q);
392 break;
393 case URI_HAS_Q0:
394 map_convert_coords_to_quadtree_string(map,x,y,zoom,location,'\0', "0123");
395 s = g_strdup_printf("%s", location);
396 url = replace_string(url, URI_MARKER_Q0, s);
397 //g_debug("FOUND " URI_MARKER_Q0);
398 break;
399 case URI_HAS_YS:
400 // s = g_strdup_printf("%d", y);
401 // url = replace_string(url, URI_MARKER_YS, s);
402 g_warning("FOUND " URI_MARKER_YS " NOT IMPLEMENTED");
403 // retval = g_strdup_printf(repo->url,
404 // tilex,
405 // (1 << (MAX_ZOOM - zoom)) - tiley - 1,
406 // zoom - (MAX_ZOOM - 17));
407 break;
408 case URI_HAS_R:
409 s = g_strdup_printf("%d", g_random_int_range(0,4));
410 url = replace_string(url, URI_MARKER_R, s);
411 //g_debug("FOUND " URI_MARKER_R);
412 break;
413 default:
414 s = NULL;
415 break;
416 }
417
418 if (s) {
419 g_free(s);
420 g_free(old);
421 }
422
423 i = (i << 1);
424
425 }
426
427 return url;
428 }
429
430 static void
431 my_log_handler (const gchar * log_domain, GLogLevelFlags log_level, const gchar * message, gpointer user_data)
432 {
433 if (!(log_level & G_LOG_LEVEL_DEBUG) || ENABLE_DEBUG)
434 g_log_default_handler (log_domain, log_level, message, user_data);
435 }
436
437 static float
438 osm_gps_map_get_scale_at_point(int zoom, float rlat, float rlon)
439 {
440 /* world at zoom 1 == 512 pixels */
441 return cos(rlat) * M_PI * OSM_EQ_RADIUS / (1<<(7+zoom));
442 }
443
444 /* clears the trip list and all resources */
445 static void
446 osm_gps_map_free_trip (OsmGpsMap *map)
447 {
448 OsmGpsMapPrivate *priv = map->priv;
449 if (priv->trip_history) {
450 g_slist_foreach(priv->trip_history, (GFunc) g_free, NULL);
451 g_slist_free(priv->trip_history);
452 priv->trip_history = NULL;
453 }
454 }
455
456 /* clears the tracks and all resources */
457 static void
458 osm_gps_map_free_tracks (OsmGpsMap *map)
459 {
460 OsmGpsMapPrivate *priv = map->priv;
461 if (priv->tracks)
462 {
463 GSList* tmp = priv->tracks;
464 while (tmp != NULL)
465 {
466 g_slist_foreach(tmp->data, (GFunc) g_free, NULL);
467 g_slist_free(tmp->data);
468 tmp = g_slist_next(tmp);
469 }
470 g_slist_free(priv->tracks);
471 priv->tracks = NULL;
472 }
473 }
474
475 /* free the poi image lists */
476 static void
477 osm_gps_map_free_images (OsmGpsMap *map)
478 {
479 OsmGpsMapPrivate *priv = map->priv;
480 if (priv->images) {
481 GSList *list;
482 for(list = priv->images; list != NULL; list = list->next)
483 {
484 image_t *im = list->data;
485 g_object_unref(im->image);
486 g_free(im);
487 }
488 g_slist_free(priv->images);
489 priv->images = NULL;
490 }
491 }
492
493 static void
494 osm_gps_map_print_images (OsmGpsMap *map)
495 {
496 GSList *list;
497 int x,y,pixel_x,pixel_y;
498 int min_x = 0,min_y = 0,max_x = 0,max_y = 0;
499 int map_x0, map_y0;
500 OsmGpsMapPrivate *priv = map->priv;
501
502 map_x0 = priv->map_x - EXTRA_BORDER;
503 map_y0 = priv->map_y - EXTRA_BORDER;
504 for(list = priv->images; list != NULL; list = list->next)
505 {
506 image_t *im = list->data;
507
508 // pixel_x,y, offsets
509 pixel_x = lon2pixel(priv->map_zoom, im->pt.rlon);
510 pixel_y = lat2pixel(priv->map_zoom, im->pt.rlat);
511
512 g_debug("Image %dx%d @: %f,%f (%d,%d)",
513 im->w, im->h,
514 im->pt.rlat, im->pt.rlon,
515 pixel_x, pixel_y);
516
517 x = pixel_x - map_x0;
518 y = pixel_y - map_y0;
519
520 gdk_draw_pixbuf (
521 priv->pixmap,
522 priv->gc_map,
523 im->image,
524 0,0,
525 x-(im->w/2),y-(im->h/2),
526 im->w,im->h,
527 GDK_RGB_DITHER_NONE, 0, 0);
528
529 max_x = MAX(x+im->w,max_x);
530 min_x = MIN(x-im->w,min_x);
531 max_y = MAX(y+im->h,max_y);
532 min_y = MIN(y-im->h,min_y);
533 }
534
535 gtk_widget_queue_draw_area (
536 GTK_WIDGET(map),
537 min_x + EXTRA_BORDER, min_y + EXTRA_BORDER,
538 max_x + EXTRA_BORDER, max_y + EXTRA_BORDER);
539
540 }
541
542 static void
543 osm_gps_map_draw_gps_point (OsmGpsMap *map)
544 {
545 OsmGpsMapPrivate *priv = map->priv;
546
547 //incase we get called before we have got a gps point
548 if (priv->gps_valid) {
549 int map_x0, map_y0;
550 int x, y;
551 int r = priv->ui_gps_point_inner_radius;
552 int r2 = priv->ui_gps_point_outer_radius;
553 // int lw = priv->ui_gps_track_width;
554 int mr = MAX(r,r2);
555
556 map_x0 = priv->map_x - EXTRA_BORDER;
557 map_y0 = priv->map_y - EXTRA_BORDER;
558 x = lon2pixel(priv->map_zoom, priv->gps->rlon) - map_x0;
559 y = lat2pixel(priv->map_zoom, priv->gps->rlat) - map_y0;
560 #ifdef USE_CAIRO
561 cairo_t *cr;
562 cairo_pattern_t *pat;
563 #else
564 GdkColor color;
565 GdkGC *marker;
566 #endif
567
568 #ifdef USE_CAIRO
569 cr = gdk_cairo_create(priv->pixmap);
570
571 // draw transparent area
572 if (r2 > 0) {
573 cairo_set_line_width (cr, 1.5);
574 cairo_set_source_rgba (cr, 0.75, 0.75, 0.75, 0.4);
575 cairo_arc (cr, x, y, r2, 0, 2 * M_PI);
576 cairo_fill (cr);
577 // draw transparent area border
578 cairo_set_source_rgba (cr, 0.55, 0.55, 0.55, 0.4);
579 cairo_arc (cr, x, y, r2, 0, 2 * M_PI);
580 cairo_stroke(cr);
581 }
582
583 // draw ball gradient
584 if (r > 0) {
585 pat = cairo_pattern_create_radial (x-(r/5), y-(r/5), (r/5), x, y, r);
586 cairo_pattern_add_color_stop_rgba (pat, 0, 1, 1, 1, 1.0);
587 cairo_pattern_add_color_stop_rgba (pat, 1, 0, 0, 1, 1.0);
588 cairo_set_source (cr, pat);
589 cairo_arc (cr, x, y, r, 0, 2 * M_PI);
590 cairo_fill (cr);
591 cairo_pattern_destroy (pat);
592 // draw ball border
593 cairo_set_line_width (cr, 1.0);
594 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
595 cairo_arc (cr, x, y, r, 0, 2 * M_PI);
596 cairo_stroke(cr);
597 }
598
599 cairo_destroy(cr);
600 gtk_widget_queue_draw_area (GTK_WIDGET(map),
601 x-mr,
602 y-mr,
603 mr*2,
604 mr*2);
605 #else
606 marker = gdk_gc_new(priv->pixmap);
607 color.red = 5000;
608 color.green = 5000;
609 color.blue = 55000;
610 gdk_gc_set_rgb_fg_color(marker, &color);
611 gdk_gc_set_line_attributes(marker, lw, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
612
613 if (r2 > 0) {
614 gdk_draw_arc (priv->pixmap,
615 marker,
616 FALSE, //filled
617 x-r2, y-r2, // x,y
618 r2*2,r2*2, // width, height
619 0, 360*64); // start-end angle 64th, from 3h, anti clockwise
620 }
621 if (r > 0) {
622 gdk_draw_arc (priv->pixmap,
623 marker,
624 TRUE, //filled
625 x-r, y-r, // x,y
626 r*2,r*2, // width, height
627 0, 360*64); // start-end angle 64th, from 3h, anti clockwise
628 }
629
630 g_object_unref(marker);
631 gtk_widget_queue_draw_area (GTK_WIDGET(map),
632 x-(mr+lw),
633 y-(mr+lw),
634 (mr*2)+lw+lw,
635 (mr*2)+lw+lw);
636 #endif
637 }
638 }
639
640 /* most visual effects are hardcoded by now, but may be made */
641 /* available via properties later */
642 #define BALLOON_CORNER_RADIUS 20
643 #define BALLOON_WIDTH 150
644 #define BALLOON_HEIGHT 75
645 #define BALLOON_TRANSPARENCY 0.8
646 #define POINTER_HEIGHT 20
647 #define POINTER_FOOT_WIDTH 20
648 #define POINTER_OFFSET (BALLOON_CORNER_RADIUS*3/4)
649 #define BALLOON_SHADOW 5
650 #define BALLOON_SHADOW_TRANSPARENCY 0.2
651
652 /* draw the bubble shape. this is used twice, once for the shape and once */
653 /* for the shadow */
654 static void
655 osm_gps_map_draw_balloon_shape (cairo_t *cr, int x0, int y0, int x1, int y1,
656 gboolean bottom, int px, int py, int px0, int px1) {
657
658 cairo_move_to (cr, x0, y0 + BALLOON_CORNER_RADIUS);
659 cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + BALLOON_CORNER_RADIUS, y0);
660 if(!bottom) {
661 /* insert bottom/left pointer */
662 cairo_line_to (cr, px1, y0);
663 cairo_line_to (cr, px, py);
664 cairo_line_to (cr, px0, y0);
665 }
666
667 cairo_line_to (cr, x1 - BALLOON_CORNER_RADIUS, y0);
668 cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + BALLOON_CORNER_RADIUS);
669 cairo_line_to (cr, x1 , y1 - BALLOON_CORNER_RADIUS);
670 cairo_curve_to (cr, x1, y1, x1, y1, x1 - BALLOON_CORNER_RADIUS, y1);
671 if(bottom) {
672 /* insert bottom/left pointer */
673 cairo_line_to (cr, px0, y1);
674 cairo_line_to (cr, px, py);
675 cairo_line_to (cr, px1, y1);
676 }
677
678 cairo_line_to (cr, x0 + BALLOON_CORNER_RADIUS, y1);
679 cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - BALLOON_CORNER_RADIUS);
680
681 cairo_close_path (cr);
682 }
683
684 /* http://cairographics.org/samples/ */
685 static void
686 osm_gps_map_draw_balloon_int (OsmGpsMap *map)
687 {
688 OsmGpsMapPrivate *priv = map->priv;
689
690 if (priv->balloon_valid) {
691
692 /* ------- convert given coordinate into screen position --------- */
693 int x0 = lon2pixel(priv->map_zoom, priv->balloon->rlon) -
694 priv->map_x + EXTRA_BORDER;
695 int y0 = lat2pixel(priv->map_zoom, priv->balloon->rlat) -
696 priv->map_y + EXTRA_BORDER;
697
698 /* check position of this relative to screen center to determine */
699 /* pointer direction ... */
700 int pointer_x = x0, pointer_x0, pointer_x1;
701 int pointer_y = y0;
702
703 /* ... and calculate position */
704 if((x0 - EXTRA_BORDER) > GTK_WIDGET(map)->allocation.width/2) {
705 x0 -= BALLOON_WIDTH - POINTER_OFFSET;
706 pointer_x0 = pointer_x - (BALLOON_CORNER_RADIUS - POINTER_OFFSET);
707 pointer_x1 = pointer_x0 - POINTER_FOOT_WIDTH;
708 } else {
709 x0 -= POINTER_OFFSET;
710 pointer_x1 = pointer_x + (BALLOON_CORNER_RADIUS - POINTER_OFFSET);
711 pointer_x0 = pointer_x1 + POINTER_FOOT_WIDTH;
712 }
713
714 gboolean bottom = FALSE;
715 if((y0 - EXTRA_BORDER) > GTK_WIDGET(map)->allocation.height/2) {
716 bottom = TRUE;
717 y0 -= BALLOON_HEIGHT + POINTER_HEIGHT;
718 } else
719 y0 += POINTER_HEIGHT;
720
721 /* calculate bottom/right of box */
722 int x1 = x0 + BALLOON_WIDTH, y1 = y0 + BALLOON_HEIGHT;
723
724 #ifdef USE_CAIRO
725 cairo_t *cr = gdk_cairo_create(priv->pixmap);
726
727 /* --------- draw shadow --------------- */
728 osm_gps_map_draw_balloon_shape (cr,
729 x0 + BALLOON_SHADOW, y0 + BALLOON_SHADOW,
730 x1 + BALLOON_SHADOW, y1 + BALLOON_SHADOW,
731 bottom, pointer_x, pointer_y,
732 pointer_x0 + BALLOON_SHADOW, pointer_x1 + BALLOON_SHADOW);
733
734 cairo_set_source_rgba (cr, 0, 0, 0, BALLOON_SHADOW_TRANSPARENCY);
735 cairo_fill_preserve (cr);
736 cairo_set_source_rgba (cr, 1, 0, 0, 1.0);
737 cairo_set_line_width (cr, 0);
738 cairo_stroke (cr);
739
740 /* --------- draw main shape ----------- */
741 osm_gps_map_draw_balloon_shape (cr, x0, y0, x1, y1,
742 bottom, pointer_x, pointer_y, pointer_x0, pointer_x1);
743
744 cairo_set_source_rgba (cr, 1, 1, 1, BALLOON_TRANSPARENCY);
745 cairo_fill_preserve (cr);
746 cairo_set_source_rgba (cr, 0, 0, 0, BALLOON_TRANSPARENCY);
747 cairo_set_line_width (cr, 1);
748 cairo_stroke (cr);
749
750
751 /* ---------- draw close button --------- */
752
753 int cx = x1 - BALLOON_CORNER_RADIUS*3/4;
754 int cy = y0 + BALLOON_CORNER_RADIUS*3/4;
755 int crad = BALLOON_CORNER_RADIUS/3;
756
757 cairo_arc (cr, cx, cy, crad, 0, 2 * M_PI);
758 cairo_set_source_rgba (cr, 0.8, 0, 0, 1.0);
759 cairo_fill_preserve (cr);
760 cairo_set_source_rgba (cr, 0.3, 0, 0, 1.0);
761 cairo_set_line_width (cr, 1);
762 cairo_stroke(cr);
763
764 int cs = crad/2;
765 cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
766 cairo_set_line_width (cr, 3);
767 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
768 cairo_move_to (cr, cx - cs, cy - cs);
769 cairo_line_to (cr, cx + cs, cy + cs);
770 cairo_stroke (cr);
771 cairo_move_to (cr, cx + cs, cy - cs);
772 cairo_line_to (cr, cx - cs, cy + cs);
773 cairo_stroke (cr);
774
775
776 gtk_widget_queue_draw_area (GTK_WIDGET(map),
777 x0, y0, BALLOON_WIDTH,
778 BALLOON_HEIGHT + POINTER_HEIGHT);
779 #else
780 #warning "Balloon display lacks a non-cairo implementation!"
781 #endif
782 }
783 }
784
785 static void
786 osm_gps_map_blit_tile(OsmGpsMap *map, GdkPixbuf *pixbuf, int offset_x, int offset_y)
787 {
788 OsmGpsMapPrivate *priv = map->priv;
789
790 g_debug("Queing redraw @ %d,%d (w:%d h:%d)", offset_x,offset_y, TILESIZE,TILESIZE);
791
792 /* draw pixbuf onto pixmap */
793 gdk_draw_pixbuf (priv->pixmap,
794 priv->gc_map,
795 pixbuf,
796 0,0,
797 offset_x,offset_y,
798 TILESIZE,TILESIZE,
799 GDK_RGB_DITHER_NONE, 0, 0);
800 }
801
802 /* libsoup-2.2 and libsoup-2.4 use different ways to store the body data */
803 #ifdef LIBSOUP22
804 #define soup_message_headers_append(a,b,c) soup_message_add_header(a,b,c)
805 #define MSG_RESPONSE_BODY(a) ((a)->response.body)
806 #define MSG_RESPONSE_LEN(a) ((a)->response.length)
807 #define MSG_RESPONSE_LEN_FORMAT "%u"
808 #else
809 #define MSG_RESPONSE_BODY(a) ((a)->response_body->data)
810 #define MSG_RESPONSE_LEN(a) ((a)->response_body->length)
811 #define MSG_RESPONSE_LEN_FORMAT "%lld"
812 #endif
813
814 #ifdef LIBSOUP22
815 static void
816 osm_gps_map_tile_download_complete (SoupMessage *msg, gpointer user_data)
817 #else
818 static void
819 osm_gps_map_tile_download_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
820 #endif
821 {
822 FILE *file;
823 tile_download_t *dl = (tile_download_t *)user_data;
824 OsmGpsMap *map = OSM_GPS_MAP(dl->map);
825 OsmGpsMapPrivate *priv = map->priv;
826 gboolean file_saved = FALSE;
827
828 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
829 {
830 /* save tile into cachedir if one has been specified */
831 if (priv->cache_dir)
832 {
833 if (g_mkdir_with_parents(dl->folder,0700) == 0)
834 {
835 file = g_fopen(dl->filename, "wb");
836 if (file != NULL)
837 {
838 fwrite (MSG_RESPONSE_BODY(msg), 1, MSG_RESPONSE_LEN(msg), file);
839 file_saved = TRUE;
840 g_debug("Wrote "MSG_RESPONSE_LEN_FORMAT" bytes to %s", MSG_RESPONSE_LEN(msg), dl->filename);
841 fclose (file);
842
843 }
844 }
845 else
846 {
847 g_warning("Error creating tile download directory: %s", dl->folder);
848 }
849 }
850
851 if (dl->redraw)
852 {
853 GdkPixbuf *pixbuf = NULL;
854
855 /* if the file was actually stored on disk, we can simply */
856 /* load and decode it from that file */
857 if (priv->cache_dir)
858 {
859 if (file_saved)
860 {
861 pixbuf = gdk_pixbuf_new_from_file (dl->filename, NULL);
862 }
863 }
864 else
865 {
866 GdkPixbufLoader *loader;
867 char *extension = strrchr (dl->filename, '.');
868
869 /* parse file directly from memory */
870 if (extension)
871 {
872 loader = gdk_pixbuf_loader_new_with_type (extension+1, NULL);
873 if (!gdk_pixbuf_loader_write (loader, (unsigned char*)MSG_RESPONSE_BODY(msg), MSG_RESPONSE_LEN(msg), NULL))
874 {
875 g_warning("Error: Decoding of image failed");
876 }
877 gdk_pixbuf_loader_close(loader, NULL);
878
879 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
880
881 /* give up loader but keep the pixbuf */
882 g_object_ref(pixbuf);
883 g_object_unref(loader);
884 }
885 else
886 {
887 g_warning("Error: Unable to determine image file format");
888 }
889 }
890
891 /* Store the tile into the cache */
892 if (G_LIKELY (pixbuf))
893 {
894 OsmCachedTile *tile = g_slice_new (OsmCachedTile);
895 tile->pixbuf = pixbuf;
896 tile->redraw_cycle = priv->redraw_cycle;
897 /* if the tile is already in the cache (it could be one
898 * rendered from another zoom level), it will be
899 * overwritten */
900 g_hash_table_insert (priv->tile_cache, dl->filename, tile);
901 /* NULL-ify dl->filename so that it won't be freed, as
902 * we are using it as a key in the hash table */
903 dl->filename = NULL;
904 }
905 osm_gps_map_map_redraw_idle (map);
906 }
907 g_hash_table_remove(priv->tile_queue, dl->uri);
908
909 g_free(dl->uri);
910 g_free(dl->folder);
911 g_free(dl->filename);
912 g_free(dl);
913 }
914 else
915 {
916 g_warning("Error downloading tile: %d - %s", msg->status_code, msg->reason_phrase);
917 if (msg->status_code == SOUP_STATUS_NOT_FOUND)
918 {
919 g_hash_table_insert(priv->missing_tiles, dl->uri, NULL);
920 g_hash_table_remove(priv->tile_queue, dl->uri);
921 }
922 else if (msg->status_code == SOUP_STATUS_CANCELLED)
923 {
924 ;//application exiting
925 }
926 else
927 {
928 #ifdef LIBSOUP22
929 soup_session_requeue_message(dl->session, msg);
930 #else
931 soup_session_requeue_message(session, msg);
932 #endif
933 return;
934 }
935 }
936
937
938 }
939
940 static void
941 osm_gps_map_download_tile (OsmGpsMap *map, int zoom, int x, int y, gboolean redraw)
942 {
943 SoupMessage *msg;
944 OsmGpsMapPrivate *priv = map->priv;
945 tile_download_t *dl = g_new0(tile_download_t,1);
946
947 //calculate the uri to download
948 dl->uri = replace_map_uri(map, priv->repo_uri, zoom, x, y);
949
950 #ifdef LIBSOUP22
951 dl->session = priv->soup_session;
952 #endif
953
954 //check the tile has not already been queued for download,
955 //or has been attempted, and its missing
956 if (g_hash_table_lookup_extended(priv->tile_queue, dl->uri, NULL, NULL) ||
957 g_hash_table_lookup_extended(priv->missing_tiles, dl->uri, NULL, NULL) )
958 {
959 g_debug("Tile already downloading (or missing)");
960 g_free(dl->uri);
961 g_free(dl);
962 } else {
963 dl->folder = g_strdup_printf("%s%c%d%c%d%c",
964 priv->cache_dir, G_DIR_SEPARATOR,
965 zoom, G_DIR_SEPARATOR,
966 x, G_DIR_SEPARATOR);
967 dl->filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
968 priv->cache_dir, G_DIR_SEPARATOR,
969 zoom, G_DIR_SEPARATOR,
970 x, G_DIR_SEPARATOR,
971 y,
972 priv->image_format);
973 dl->map = map;
974 dl->redraw = redraw;
975
976 g_debug("Download tile: %d,%d z:%d\n\t%s --> %s", x, y, zoom, dl->uri, dl->filename);
977
978 msg = soup_message_new (SOUP_METHOD_GET, dl->uri);
979 if (msg) {
980 if (priv->the_google) {
981 //Set maps.google.com as the referrer
982 g_debug("Setting Google Referrer");
983 soup_message_headers_append(msg->request_headers, "Referer", "http://maps.google.com/");
984 //For google satelite also set the appropriate cookie value
985 if (priv->uri_format & URI_HAS_Q) {
986 const char *cookie = g_getenv("GOOGLE_COOKIE");
987 if (cookie) {
988 g_debug("Adding Google Cookie");
989 soup_message_headers_append(msg->request_headers, "Cookie", cookie);
990 }
991 }
992 }
993
994 g_hash_table_insert (priv->tile_queue, dl->uri, msg);
995 soup_session_queue_message (priv->soup_session, msg, osm_gps_map_tile_download_complete, dl);
996 } else {
997 g_warning("Could not create soup message");
998 g_free(dl->uri);
999 g_free(dl->folder);
1000 g_free(dl->filename);
1001 g_free(dl);
1002 }
1003 }
1004 }
1005
1006 static GdkPixbuf *
1007 osm_gps_map_load_cached_tile (OsmGpsMap *map, int zoom, int x, int y)
1008 {
1009 OsmGpsMapPrivate *priv = map->priv;
1010 gchar *filename;
1011 GdkPixbuf *pixbuf = NULL;
1012 OsmCachedTile *tile;
1013
1014 filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
1015 priv->cache_dir, G_DIR_SEPARATOR,
1016 zoom, G_DIR_SEPARATOR,
1017 x, G_DIR_SEPARATOR,
1018 y,
1019 priv->image_format);
1020
1021 tile = g_hash_table_lookup (priv->tile_cache, filename);
1022 if (tile)
1023 {
1024 g_free (filename);
1025 }
1026 else
1027 {
1028 pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
1029 if (pixbuf)
1030 {
1031 tile = g_slice_new (OsmCachedTile);
1032 tile->pixbuf = pixbuf;
1033 g_hash_table_insert (priv->tile_cache, filename, tile);
1034 }
1035 }
1036
1037 /* set/update the redraw_cycle timestamp on the tile */
1038 if (tile)
1039 {
1040 tile->redraw_cycle = priv->redraw_cycle;
1041 pixbuf = g_object_ref (tile->pixbuf);
1042 }
1043
1044 return pixbuf;
1045 }
1046
1047 static GdkPixbuf *
1048 osm_gps_map_find_bigger_tile (OsmGpsMap *map, int zoom, int x, int y,
1049 int *zoom_found)
1050 {
1051 GdkPixbuf *pixbuf;
1052 int next_zoom, next_x, next_y;
1053
1054 if (zoom == 0) return NULL;
1055 next_zoom = zoom - 1;
1056 next_x = x / 2;
1057 next_y = y / 2;
1058 pixbuf = osm_gps_map_load_cached_tile (map, next_zoom, next_x, next_y);
1059 if (pixbuf)
1060 *zoom_found = next_zoom;
1061 else
1062 pixbuf = osm_gps_map_find_bigger_tile (map, next_zoom, next_x, next_y,
1063 zoom_found);
1064 return pixbuf;
1065 }
1066
1067 static GdkPixbuf *
1068 osm_gps_map_render_missing_tile_upscaled (OsmGpsMap *map, int zoom,
1069 int x, int y)
1070 {
1071 GdkPixbuf *pixbuf, *big, *area;
1072 int zoom_big, zoom_diff, area_size, area_x, area_y;
1073 int modulo;
1074
1075 big = osm_gps_map_find_bigger_tile (map, zoom, x, y, &zoom_big);
1076 if (!big) return NULL;
1077
1078 g_debug ("Found bigger tile (zoom = %d, wanted = %d)", zoom_big, zoom);
1079
1080 /* get a Pixbuf for the area to magnify */
1081 zoom_diff = zoom - zoom_big;
1082 area_size = TILESIZE >> zoom_diff;
1083 modulo = 1 << zoom_diff;
1084 area_x = (x % modulo) * area_size;
1085 area_y = (y % modulo) * area_size;
1086 area = gdk_pixbuf_new_subpixbuf (big, area_x, area_y,
1087 area_size, area_size);
1088 g_object_unref (big);
1089 pixbuf = gdk_pixbuf_scale_simple (area, TILESIZE, TILESIZE,
1090 GDK_INTERP_NEAREST);
1091 g_object_unref (area);
1092 return pixbuf;
1093 }
1094
1095 static GdkPixbuf *
1096 osm_gps_map_render_missing_tile (OsmGpsMap *map, int zoom, int x, int y)
1097 {
1098 /* maybe TODO: render from downscaled tiles, if the following fails */
1099 return osm_gps_map_render_missing_tile_upscaled (map, zoom, x, y);
1100 }
1101
1102 static void
1103 osm_gps_map_load_tile (OsmGpsMap *map, int zoom, int x, int y, int offset_x, int offset_y)
1104 {
1105 OsmGpsMapPrivate *priv = map->priv;
1106 gchar *filename;
1107 GdkPixbuf *pixbuf;
1108
1109 g_debug("Load tile %d,%d (%d,%d) z:%d", x, y, offset_x, offset_y, zoom);
1110
1111 if (priv->map_source == OSM_GPS_MAP_SOURCE_NULL) {
1112 osm_gps_map_blit_tile(map, priv->null_tile, offset_x,offset_y);
1113 return;
1114 }
1115
1116 filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
1117 priv->cache_dir, G_DIR_SEPARATOR,
1118 zoom, G_DIR_SEPARATOR,
1119 x, G_DIR_SEPARATOR,
1120 y,
1121 priv->image_format);
1122
1123 /* try to get file from internal cache first */
1124 if(!(pixbuf = osm_gps_map_load_cached_tile(map, zoom, x, y)))
1125 pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
1126
1127 if(pixbuf)
1128 {
1129 g_debug("Found tile %s", filename);
1130 osm_gps_map_blit_tile(map, pixbuf, offset_x,offset_y);
1131 g_object_unref (pixbuf);
1132 }
1133 else
1134 {
1135 if (priv->map_auto_download)
1136 osm_gps_map_download_tile(map, zoom, x, y, TRUE);
1137
1138 /* try to render the tile by scaling cached tiles from other zoom
1139 * levels */
1140 pixbuf = osm_gps_map_render_missing_tile (map, zoom, x, y);
1141 if (pixbuf)
1142 {
1143 gdk_draw_pixbuf (priv->pixmap,
1144 priv->gc_map,
1145 pixbuf,
1146 0,0,
1147 offset_x,offset_y,
1148 TILESIZE,TILESIZE,
1149 GDK_RGB_DITHER_NONE, 0, 0);
1150 g_object_unref (pixbuf);
1151 }
1152 else
1153 {
1154 //prevent some artifacts when drawing not yet loaded areas.
1155 gdk_draw_rectangle (priv->pixmap,
1156 GTK_WIDGET(map)->style->white_gc,
1157 TRUE, offset_x, offset_y, TILESIZE, TILESIZE);
1158 }
1159 }
1160 g_free(filename);
1161 }
1162
1163 static void
1164 osm_gps_map_fill_tiles_pixel (OsmGpsMap *map)
1165 {
1166 OsmGpsMapPrivate *priv = map->priv;
1167 int i,j, width, height, tile_x0, tile_y0, tiles_nx, tiles_ny;
1168 int offset_xn = 0;
1169 int offset_yn = 0;
1170 int offset_x;
1171 int offset_y;
1172
1173 g_debug("Fill tiles: %d,%d z:%d", priv->map_x, priv->map_y, priv->map_zoom);
1174
1175 offset_x = - priv->map_x % TILESIZE;
1176 offset_y = - priv->map_y % TILESIZE;
1177 if (offset_x > 0) offset_x -= TILESIZE;
1178 if (offset_y > 0) offset_y -= TILESIZE;
1179
1180 offset_xn = offset_x + EXTRA_BORDER;
1181 offset_yn = offset_y + EXTRA_BORDER;
1182
1183 width = GTK_WIDGET(map)->allocation.width;
1184 height = GTK_WIDGET(map)->allocation.height;
1185
1186 tiles_nx = (width - offset_x) / TILESIZE + 1;
1187 tiles_ny = (height - offset_y) / TILESIZE + 1;
1188
1189 tile_x0 = floor((float)priv->map_x / (float)TILESIZE);
1190 tile_y0 = floor((float)priv->map_y / (float)TILESIZE);
1191
1192 //TODO: implement wrap around
1193 for (i=tile_x0; i<(tile_x0+tiles_nx);i++)
1194 {
1195 for (j=tile_y0; j<(tile_y0+tiles_ny); j++)
1196 {
1197 if( j<0 || i<0 || i>=exp(priv->map_zoom * M_LN2) || j>=exp(priv->map_zoom * M_LN2))
1198 {
1199 gdk_draw_rectangle (priv->pixmap,
1200 GTK_WIDGET(map)->style->white_gc,
1201 TRUE,
1202 offset_xn, offset_yn,
1203 TILESIZE,TILESIZE);
1204 }
1205 else
1206 {
1207 osm_gps_map_load_tile(map,
1208 priv->map_zoom,
1209 i,j,
1210 offset_xn,offset_yn);
1211 }
1212 offset_yn += TILESIZE;
1213 }
1214 offset_xn += TILESIZE;
1215 offset_yn = offset_y + EXTRA_BORDER;
1216 }
1217 }
1218
1219 static void
1220 osm_gps_map_print_track (OsmGpsMap *map, GSList *trackpoint_list)
1221 {
1222 OsmGpsMapPrivate *priv = map->priv;
1223
1224 GSList *list;
1225 int x,y;
1226 int min_x = 0,min_y = 0,max_x = 0,max_y = 0;
1227 int lw = priv->ui_gps_track_width;
1228 int map_x0, map_y0;
1229 #ifdef USE_CAIRO
1230 cairo_t *cr;
1231 #else
1232 int last_x = 0, last_y = 0;
1233 GdkColor color;
1234 GdkGC *gc;
1235 #endif
1236
1237 #ifdef USE_CAIRO
1238 cr = gdk_cairo_create(priv->pixmap);
1239 cairo_set_line_width (cr, lw);
1240 cairo_set_source_rgba (cr, 60000.0/65535.0, 0.0, 0.0, 0.6);
1241 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
1242 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
1243 #else
1244 gc = gdk_gc_new(priv->pixmap);
1245 color.green = 0;
1246 color.blue = 0;
1247 color.red = 60000;
1248 gdk_gc_set_rgb_fg_color(gc, &color);
1249 gdk_gc_set_line_attributes(gc, lw, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
1250 #endif
1251
1252 map_x0 = priv->map_x - EXTRA_BORDER;
1253 map_y0 = priv->map_y - EXTRA_BORDER;
1254 for(list = trackpoint_list; list != NULL; list = list->next)
1255 {
1256 coord_t *tp = list->data;
1257
1258 x = lon2pixel(priv->map_zoom, tp->rlon) - map_x0;
1259 y = lat2pixel(priv->map_zoom, tp->rlat) - map_y0;
1260
1261 // first time through loop
1262 if (list == trackpoint_list) {
1263 #ifdef USE_CAIRO
1264 cairo_move_to(cr, x, y);
1265 #else
1266 last_x = x;
1267 last_y = y;
1268 #endif
1269 }
1270
1271 #ifdef USE_CAIRO
1272 cairo_line_to(cr, x, y);
1273 #else
1274 gdk_draw_line (priv->pixmap, gc, x, y, last_x, last_y);
1275 last_x = x;
1276 last_y = y;
1277 #endif
1278
1279 max_x = MAX(x,max_x);
1280 min_x = MIN(x,min_x);
1281 max_y = MAX(y,max_y);
1282 min_y = MIN(y,min_y);
1283 }
1284
1285 gtk_widget_queue_draw_area (
1286 GTK_WIDGET(map),
1287 min_x - lw,
1288 min_y - lw,
1289 max_x + (lw * 2),
1290 max_y + (lw * 2));
1291
1292 #ifdef USE_CAIRO
1293 cairo_stroke(cr);
1294 cairo_destroy(cr);
1295 #else
1296 g_object_unref(gc);
1297 #endif
1298 }
1299
1300 /* Prints the gps trip history, and any other tracks */
1301 static void
1302 osm_gps_map_print_tracks (OsmGpsMap *map)
1303 {
1304 OsmGpsMapPrivate *priv = map->priv;
1305
1306 if (priv->show_trip_history)
1307 osm_gps_map_print_track (map, priv->trip_history);
1308
1309 if (priv->tracks)
1310 {
1311 GSList* tmp = priv->tracks;
1312 while (tmp != NULL)
1313 {
1314 osm_gps_map_print_track (map, tmp->data);
1315 tmp = g_slist_next(tmp);
1316 }
1317 }
1318 }
1319
1320 static gboolean
1321 osm_gps_map_purge_cache_check(gpointer key, gpointer value, gpointer user)
1322 {
1323 return (((OsmCachedTile*)value)->redraw_cycle != ((OsmGpsMapPrivate*)user)->redraw_cycle);
1324 }
1325
1326 static void
1327 osm_gps_map_purge_cache (OsmGpsMap *map)
1328 {
1329 OsmGpsMapPrivate *priv = map->priv;
1330
1331 if (g_hash_table_size (priv->tile_cache) < priv->max_tile_cache_size)
1332 return;
1333
1334 /* run through the cache, and remove the tiles which have not been used
1335 * during the last redraw operation */
1336 g_hash_table_foreach_remove(priv->tile_cache, osm_gps_map_purge_cache_check, priv);
1337 }
1338
1339 static gboolean
1340 osm_gps_map_map_redraw (OsmGpsMap *map)
1341 {
1342 OsmGpsMapPrivate *priv = map->priv;
1343
1344 priv->idle_map_redraw = 0;
1345
1346 /* the motion_notify handler uses priv->pixmap to redraw the area; if we
1347 * change it while we are dragging, we will end up showing it in the wrong
1348 * place. This could be fixed by carefully recompute the coordinates, but
1349 * for now it's easier just to disable redrawing the map while dragging */
1350 if (priv->dragging)
1351 return FALSE;
1352
1353 priv->redraw_cycle++;
1354
1355 /* draw white background to initialise pixmap */
1356 gdk_draw_rectangle (
1357 priv->pixmap,
1358 GTK_WIDGET(map)->style->white_gc,
1359 TRUE,
1360 0, 0,
1361 GTK_WIDGET(map)->allocation.width + EXTRA_BORDER * 2,
1362 GTK_WIDGET(map)->allocation.height + EXTRA_BORDER * 2);
1363
1364 osm_gps_map_fill_tiles_pixel(map);
1365
1366 osm_gps_map_print_tracks(map);
1367 osm_gps_map_draw_gps_point(map);
1368 osm_gps_map_print_images(map);
1369 osm_gps_map_draw_balloon_int(map);
1370
1371 //osm_gps_map_osd_speed(map, 1.5);
1372 osm_gps_map_purge_cache(map);
1373 gtk_widget_queue_draw (GTK_WIDGET (map));
1374
1375 return FALSE;
1376 }
1377
1378 static void
1379 osm_gps_map_map_redraw_idle (OsmGpsMap *map)
1380 {
1381 OsmGpsMapPrivate *priv = map->priv;
1382
1383 if (priv->idle_map_redraw == 0)
1384 priv->idle_map_redraw = g_idle_add ((GSourceFunc)osm_gps_map_map_redraw, map);
1385 }
1386
1387 static void
1388 osm_gps_map_init (OsmGpsMap *object)
1389 {
1390 OsmGpsMapPrivate *priv;
1391
1392 priv = G_TYPE_INSTANCE_GET_PRIVATE (object, OSM_TYPE_GPS_MAP, OsmGpsMapPrivate);
1393 object->priv = priv;
1394
1395 priv->pixmap = NULL;
1396
1397 priv->trip_history = NULL;
1398 priv->gps = g_new0(coord_t, 1);
1399 priv->gps_valid = FALSE;
1400
1401 priv->balloon = g_new0(coord_t, 1);
1402 priv->balloon_valid = FALSE;
1403
1404 priv->tracks = NULL;
1405 priv->images = NULL;
1406
1407 priv->drag_counter = 0;
1408 priv->drag_mouse_dx = 0;
1409 priv->drag_mouse_dy = 0;
1410 priv->drag_start_mouse_x = 0;
1411 priv->drag_start_mouse_y = 0;
1412
1413 priv->uri_format = 0;
1414 priv->the_google = FALSE;
1415
1416 priv->map_source = -1;
1417
1418 #ifndef LIBSOUP22
1419 //Change naumber of concurrent connections option?
1420 priv->soup_session = soup_session_async_new_with_options(
1421 SOUP_SESSION_USER_AGENT,
1422 "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11",
1423 NULL);
1424 #else
1425 /* libsoup-2.2 seems not to be able to set the user agent */
1426 priv->soup_session = soup_session_async_new();
1427 #endif
1428
1429 //Hash table which maps tile d/l URIs to SoupMessage requests
1430 priv->tile_queue = g_hash_table_new (g_str_hash, g_str_equal);
1431
1432 //Some mapping providers (Google) have varying degrees of tiles at multiple
1433 //zoom levels
1434 priv->missing_tiles = g_hash_table_new (g_str_hash, g_str_equal);
1435
1436 /* memory cache for most recently used tiles */
1437 priv->tile_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
1438 g_free, (GDestroyNotify)cached_tile_free);
1439 priv->max_tile_cache_size = 20;
1440
1441 gtk_widget_add_events (GTK_WIDGET (object),
1442 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1443 GDK_POINTER_MOTION_MASK |
1444 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
1445 GTK_WIDGET_SET_FLAGS (object, GTK_CAN_FOCUS);
1446
1447 g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MASK, my_log_handler, NULL);
1448 }
1449
1450 #ifndef G_CHECKSUM_MD5
1451 /* simple hash algorithm hack if md5 is not present */
1452 static char *simple_hash(char *str) {
1453 union {
1454 char str[4];
1455 gulong val;
1456 } hash = { .val = 0x55555555 };
1457
1458 while(*str) {
1459 hash.str[(int)str & 3] ^= *str;
1460 str++;
1461 }
1462 return g_strdup_printf("%08lX", hash.val);
1463 }
1464 #endif
1465
1466 static GObject *
1467 osm_gps_map_constructor (GType gtype, guint n_properties, GObjectConstructParam *properties)
1468 {
1469 GObject *object;
1470 OsmGpsMapPrivate *priv;
1471 OsmGpsMap *map;
1472 const char *uri;
1473
1474 //Always chain up to the parent constructor
1475 object = G_OBJECT_CLASS(osm_gps_map_parent_class)->constructor(gtype, n_properties, properties);
1476 map = OSM_GPS_MAP(object);
1477 priv = OSM_GPS_MAP_PRIVATE(object);
1478
1479 //user can specify a map source ID, or a repo URI as the map source
1480 uri = osm_gps_map_source_get_repo_uri(OSM_GPS_MAP_SOURCE_NULL);
1481 if ( (priv->map_source == 0) || (strcmp(priv->repo_uri, uri) == 0) ) {
1482 g_debug("Using null source");
1483 priv->map_source = OSM_GPS_MAP_SOURCE_NULL;
1484
1485 priv->null_tile = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 256, 256);
1486 gdk_pixbuf_fill(priv->null_tile, 0xcccccc00);
1487 }
1488 else if (priv->map_source >= 0) {
1489 //check if the source given is valid
1490 uri = osm_gps_map_source_get_repo_uri(priv->map_source);
1491 if (uri) {
1492 g_debug("Setting map source from ID");
1493 g_free(priv->repo_uri);
1494
1495 priv->repo_uri = g_strdup(uri);
1496 priv->image_format = g_strdup(
1497 osm_gps_map_source_get_image_format(priv->map_source));
1498 priv->max_zoom = osm_gps_map_source_get_max_zoom(priv->map_source);
1499 priv->min_zoom = osm_gps_map_source_get_min_zoom(priv->map_source);
1500 }
1501 }
1502
1503 if (!priv->cache_dir_is_full_path) {
1504 #ifdef G_CHECKSUM_MD5
1505 char *md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, priv->repo_uri, -1);
1506 #else
1507 char *md5 = simple_hash(priv->repo_uri);
1508 #endif
1509
1510 if (priv->cache_dir) {
1511 char *old = priv->cache_dir;
1512 //the new cachedir is the given cache dir + the md5 of the repo_uri
1513 priv->cache_dir = g_strdup_printf("%s%c%s", old, G_DIR_SEPARATOR, md5);
1514 g_debug("Adjusting cache dir %s -> %s", old, priv->cache_dir);
1515 g_free(old);
1516 } else {
1517 //the new cachedir is the current dir + the md5 of the repo_uri
1518 priv->cache_dir = g_strdup(md5);
1519 }
1520
1521 g_free(md5);
1522 }
1523
1524 inspect_map_uri(map);
1525
1526 return object;
1527 }
1528
1529 static void
1530 osm_gps_map_dispose (GObject *object)
1531 {
1532 OsmGpsMap *map = OSM_GPS_MAP(object);
1533 OsmGpsMapPrivate *priv = map->priv;
1534
1535 if (priv->is_disposed)
1536 return;
1537
1538 priv->is_disposed = TRUE;
1539
1540 soup_session_abort(priv->soup_session);
1541 g_object_unref(priv->soup_session);
1542
1543 g_hash_table_destroy(priv->tile_queue);
1544 g_hash_table_destroy(priv->missing_tiles);
1545 g_hash_table_destroy(priv->tile_cache);
1546
1547 osm_gps_map_free_images(map);
1548
1549 if(priv->pixmap)
1550 g_object_unref (priv->pixmap);
1551
1552 if (priv->null_tile)
1553 g_object_unref (priv->null_tile);
1554
1555 if(priv->gc_map)
1556 g_object_unref(priv->gc_map);
1557
1558 if (priv->idle_map_redraw != 0)
1559 g_source_remove (priv->idle_map_redraw);
1560
1561 g_free(priv->gps);
1562 g_free(priv->balloon);
1563
1564 G_OBJECT_CLASS (osm_gps_map_parent_class)->dispose (object);
1565 }
1566
1567 static void
1568 osm_gps_map_finalize (GObject *object)
1569 {
1570 OsmGpsMap *map = OSM_GPS_MAP(object);
1571 OsmGpsMapPrivate *priv = map->priv;
1572
1573 g_free(priv->cache_dir);
1574 g_free(priv->repo_uri);
1575 g_free(priv->image_format);
1576
1577 osm_gps_map_free_trip(map);
1578 osm_gps_map_free_tracks(map);
1579
1580 G_OBJECT_CLASS (osm_gps_map_parent_class)->finalize (object);
1581 }
1582
1583 static void
1584 osm_gps_map_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
1585 {
1586 g_return_if_fail (OSM_IS_GPS_MAP (object));
1587 OsmGpsMap *map = OSM_GPS_MAP(object);
1588 OsmGpsMapPrivate *priv = map->priv;
1589
1590 switch (prop_id)
1591 {
1592 case PROP_AUTO_CENTER:
1593 priv->map_auto_center = g_value_get_boolean (value);
1594 break;
1595 case PROP_RECORD_TRIP_HISTORY:
1596 priv->record_trip_history = g_value_get_boolean (value);
1597 break;
1598 case PROP_SHOW_TRIP_HISTORY:
1599 priv->show_trip_history = g_value_get_boolean (value);
1600 break;
1601 case PROP_AUTO_DOWNLOAD:
1602 priv->map_auto_download = g_value_get_boolean (value);
1603 break;
1604 case PROP_REPO_URI:
1605 priv->repo_uri = g_value_dup_string (value);
1606 break;
1607 case PROP_PROXY_URI:
1608 if ( g_value_get_string(value) ) {
1609 priv->proxy_uri = g_value_dup_string (value);
1610 g_debug("Setting proxy server: %s", priv->proxy_uri);
1611
1612 #ifndef LIBSOUP22
1613 GValue val = {0};
1614
1615 SoupURI* uri = soup_uri_new(priv->proxy_uri);
1616 g_value_init(&val, SOUP_TYPE_URI);
1617 g_value_take_boxed(&val, uri);
1618
1619 g_object_set_property(G_OBJECT(priv->soup_session),SOUP_SESSION_PROXY_URI,&val);
1620 #else
1621 SoupUri* uri = soup_uri_new(priv->proxy_uri);
1622 g_object_set(G_OBJECT(priv->soup_session), SOUP_SESSION_PROXY_URI, uri, NULL);
1623 #endif
1624 } else
1625 priv->proxy_uri = NULL;
1626
1627 break;
1628 case PROP_TILE_CACHE_DIR:
1629 if ( g_value_get_string(value) )
1630 priv->cache_dir = g_value_dup_string (value);
1631 break;
1632 case PROP_TILE_CACHE_DIR_IS_FULL_PATH:
1633 priv->cache_dir_is_full_path = g_value_get_boolean (value);
1634 break;
1635 case PROP_ZOOM:
1636 priv->map_zoom = g_value_get_int (value);
1637 break;
1638 case PROP_MAX_ZOOM:
1639 priv->max_zoom = g_value_get_int (value);
1640 break;
1641 case PROP_MIN_ZOOM:
1642 priv->min_zoom = g_value_get_int (value);
1643 break;
1644 case PROP_MAP_X:
1645 priv->map_x = g_value_get_int (value);
1646 priv->center_coord_set = FALSE;
1647 break;
1648 case PROP_MAP_Y:
1649 priv->map_y = g_value_get_int (value);
1650 priv->center_coord_set = FALSE;
1651 break;
1652 case PROP_GPS_TRACK_WIDTH:
1653 priv->ui_gps_track_width = g_value_get_int (value);
1654 break;
1655 case PROP_GPS_POINT_R1:
1656 priv->ui_gps_point_inner_radius = g_value_get_int (value);
1657 break;
1658 case PROP_GPS_POINT_R2:
1659 priv->ui_gps_point_outer_radius = g_value_get_int (value);
1660 break;
1661 case PROP_MAP_SOURCE:
1662 priv->map_source = g_value_get_int (value);
1663 break;
1664 case PROP_IMAGE_FORMAT:
1665 priv->image_format = g_value_dup_string (value);
1666 break;
1667 default:
1668 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1669 break;
1670 }
1671 }
1672
1673 static void
1674 osm_gps_map_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
1675 {
1676 g_return_if_fail (OSM_IS_GPS_MAP (object));
1677 float lat,lon;
1678 OsmGpsMap *map = OSM_GPS_MAP(object);
1679 OsmGpsMapPrivate *priv = map->priv;
1680
1681 switch (prop_id)
1682 {
1683 case PROP_AUTO_CENTER:
1684 g_value_set_boolean(value, priv->map_auto_center);
1685 break;
1686 case PROP_RECORD_TRIP_HISTORY:
1687 g_value_set_boolean(value, priv->record_trip_history);
1688 break;
1689 case PROP_SHOW_TRIP_HISTORY:
1690 g_value_set_boolean(value, priv->show_trip_history);
1691 break;
1692 case PROP_AUTO_DOWNLOAD:
1693 g_value_set_boolean(value, priv->map_auto_download);
1694 break;
1695 case PROP_REPO_URI:
1696 g_value_set_string(value, priv->repo_uri);
1697 break;
1698 case PROP_PROXY_URI:
1699 g_value_set_string(value, priv->proxy_uri);
1700 break;
1701 case PROP_TILE_CACHE_DIR:
1702 g_value_set_string(value, priv->cache_dir);
1703 break;
1704 case PROP_TILE_CACHE_DIR_IS_FULL_PATH:
1705 g_value_set_boolean(value, priv->cache_dir_is_full_path);
1706 break;
1707 case PROP_ZOOM:
1708 g_value_set_int(value, priv->map_zoom);
1709 break;
1710 case PROP_MAX_ZOOM:
1711 g_value_set_int(value, priv->max_zoom);
1712 break;
1713 case PROP_MIN_ZOOM:
1714 g_value_set_int(value, priv->min_zoom);
1715 break;
1716 case PROP_LATITUDE:
1717 lat = pixel2lat(priv->map_zoom,
1718 priv->map_y + (GTK_WIDGET(map)->allocation.height / 2));
1719 g_value_set_float(value, rad2deg(lat));
1720 break;
1721 case PROP_LONGITUDE:
1722 lon = pixel2lon(priv->map_zoom,
1723 priv->map_x + (GTK_WIDGET(map)->allocation.width / 2));
1724 g_value_set_float(value, rad2deg(lon));
1725 break;
1726 case PROP_MAP_X:
1727 g_value_set_int(value, priv->map_x);
1728 break;
1729 case PROP_MAP_Y:
1730 g_value_set_int(value, priv->map_y);
1731 break;
1732 case PROP_TILES_QUEUED:
1733 g_value_set_int(value, g_hash_table_size(priv->tile_queue));
1734 break;
1735 case PROP_GPS_TRACK_WIDTH:
1736 g_value_set_int(value, priv->ui_gps_track_width);
1737 break;
1738 case PROP_GPS_POINT_R1:
1739 g_value_set_int(value, priv->ui_gps_point_inner_radius);
1740 break;
1741 case PROP_GPS_POINT_R2:
1742 g_value_set_int(value, priv->ui_gps_point_outer_radius);
1743 break;
1744 case PROP_MAP_SOURCE:
1745 g_value_set_int(value, priv->map_source);
1746 break;
1747 case PROP_IMAGE_FORMAT:
1748 g_value_set_string(value, priv->image_format);
1749 break;
1750 default:
1751 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1752 break;
1753 }
1754 }
1755
1756 static gboolean
1757 osm_gps_map_scroll_event (GtkWidget *widget, GdkEventScroll *event)
1758 {
1759 OsmGpsMap *map = OSM_GPS_MAP(widget);
1760 OsmGpsMapPrivate *priv = map->priv;
1761
1762 if (event->direction == GDK_SCROLL_UP)
1763 {
1764 osm_gps_map_set_zoom(map, priv->map_zoom+1);
1765 }
1766 else
1767 {
1768 osm_gps_map_set_zoom(map, priv->map_zoom-1);
1769 }
1770
1771 return FALSE;
1772 }
1773
1774 static gboolean
1775 osm_gps_map_button_press (GtkWidget *widget, GdkEventButton *event)
1776 {
1777 OsmGpsMapPrivate *priv = OSM_GPS_MAP_PRIVATE(widget);
1778
1779 priv->drag_counter = 0;
1780 priv->drag_start_mouse_x = (int) event->x;
1781 priv->drag_start_mouse_y = (int) event->y;
1782 priv->drag_start_map_x = priv->map_x;
1783 priv->drag_start_map_y = priv->map_y;
1784
1785 return FALSE;
1786 }
1787
1788 static gboolean
1789 osm_gps_map_button_release (GtkWidget *widget, GdkEventButton *event)
1790 {
1791 OsmGpsMapPrivate *priv = OSM_GPS_MAP_PRIVATE(widget);
1792
1793 if (priv->dragging)
1794 {
1795 priv->dragging = FALSE;
1796
1797 priv->map_x = priv->drag_start_map_x;
1798 priv->map_y = priv->drag_start_map_y;
1799
1800 priv->map_x += (priv->drag_start_mouse_x - (int) event->x);
1801 priv->map_y += (priv->drag_start_mouse_y - (int) event->y);
1802
1803 priv->center_coord_set = FALSE;
1804
1805 osm_gps_map_map_redraw_idle(OSM_GPS_MAP(widget));
1806 }
1807
1808 priv->drag_mouse_dx = 0;
1809 priv->drag_mouse_dy = 0;
1810 priv->drag_counter = 0;
1811
1812 return FALSE;
1813 }
1814
1815 static gboolean
1816 osm_gps_map_motion_notify (GtkWidget *widget, GdkEventMotion *event)
1817 {
1818 int x, y;
1819 GdkModifierType state;
1820 OsmGpsMapPrivate *priv = OSM_GPS_MAP_PRIVATE(widget);
1821
1822 if (event->is_hint)
1823 gdk_window_get_pointer (event->window, &x, &y, &state);
1824 else
1825 {
1826 x = event->x;
1827 y = event->y;
1828 state = event->state;
1829 }
1830
1831 // are we being dragged
1832 if (!(state & GDK_BUTTON1_MASK))
1833 return FALSE;
1834
1835 priv->drag_counter++;
1836
1837 // we havent dragged more than 6 pixels
1838 if (priv->drag_counter < 6)
1839 return FALSE;
1840
1841 priv->dragging = TRUE;
1842
1843 if (priv->map_auto_center)
1844 g_object_set(G_OBJECT(widget), "auto-center", FALSE, NULL);
1845
1846 priv->drag_mouse_dx = x - priv->drag_start_mouse_x;
1847 priv->drag_mouse_dy = y - priv->drag_start_mouse_y;
1848
1849 gdk_draw_drawable (
1850 widget->window,
1851 widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
1852 priv->pixmap,
1853 0,0,
1854 priv->drag_mouse_dx - EXTRA_BORDER, priv->drag_mouse_dy - EXTRA_BORDER,
1855 -1,-1);
1856
1857 //Paint white outside of the map if dragging. Its less
1858 //ugly than painting the corrupted map
1859 if(priv->drag_mouse_dx>EXTRA_BORDER) {
1860 gdk_draw_rectangle (
1861 widget->window,
1862 widget->style->white_gc,
1863 TRUE,
1864 0, 0,
1865 priv->drag_mouse_dx - EXTRA_BORDER,
1866 widget->allocation.height);
1867 }
1868 else if (-priv->drag_mouse_dx > EXTRA_BORDER)
1869 {
1870 gdk_draw_rectangle (
1871 widget->window,
1872 widget->style->white_gc,
1873 TRUE,
1874 priv->drag_mouse_dx + widget->allocation.width + EXTRA_BORDER, 0,
1875 -priv->drag_mouse_dx - EXTRA_BORDER,
1876 widget->allocation.height);
1877 }
1878
1879 if (priv->drag_mouse_dy>EXTRA_BORDER) {
1880 gdk_draw_rectangle (
1881 widget->window,
1882 widget->style->white_gc,
1883 TRUE,
1884 0, 0,
1885 widget->allocation.width,
1886 priv->drag_mouse_dy - EXTRA_BORDER);
1887 }
1888 else if (-priv->drag_mouse_dy > EXTRA_BORDER)
1889 {
1890 gdk_draw_rectangle (
1891 widget->window,
1892 widget->style->white_gc,
1893 TRUE,
1894 0, priv->drag_mouse_dy + widget->allocation.height + EXTRA_BORDER,
1895 widget->allocation.width,
1896 -priv->drag_mouse_dy - EXTRA_BORDER);
1897 }
1898
1899 return FALSE;
1900 }
1901
1902 static gboolean
1903 osm_gps_map_configure (GtkWidget *widget, GdkEventConfigure *event)
1904 {
1905 OsmGpsMapPrivate *priv = OSM_GPS_MAP_PRIVATE(widget);
1906
1907 /* create pixmap */
1908 if (priv->pixmap)
1909 g_object_unref (priv->pixmap);
1910
1911 priv->pixmap = gdk_pixmap_new (
1912 widget->window,
1913 widget->allocation.width + EXTRA_BORDER * 2,
1914 widget->allocation.height + EXTRA_BORDER * 2,
1915 -1);
1916
1917 /* and gc, used for clipping (I think......) */
1918 if(priv->gc_map)
1919 g_object_unref(priv->gc_map);
1920
1921 priv->gc_map = gdk_gc_new(priv->pixmap);
1922
1923 osm_gps_map_map_redraw(OSM_GPS_MAP(widget));
1924
1925 return FALSE;
1926 }
1927
1928 static gboolean
1929 osm_gps_map_expose (GtkWidget *widget, GdkEventExpose *event)
1930 {
1931 OsmGpsMapPrivate *priv = OSM_GPS_MAP_PRIVATE(widget);
1932
1933 gdk_draw_drawable (
1934 widget->window,
1935 widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
1936 priv->pixmap,
1937 event->area.x + EXTRA_BORDER, event->area.y + EXTRA_BORDER,
1938 event->area.x, event->area.y,
1939 event->area.width, event->area.height);
1940
1941 return FALSE;
1942 }
1943
1944 static void
1945 osm_gps_map_class_init (OsmGpsMapClass *klass)
1946 {
1947 GObjectClass* object_class = G_OBJECT_CLASS (klass);
1948 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1949
1950 g_type_class_add_private (klass, sizeof (OsmGpsMapPrivate));
1951
1952 object_class->dispose = osm_gps_map_dispose;
1953 object_class->finalize = osm_gps_map_finalize;
1954 object_class->constructor = osm_gps_map_constructor;
1955 object_class->set_property = osm_gps_map_set_property;
1956 object_class->get_property = osm_gps_map_get_property;
1957
1958 widget_class->expose_event = osm_gps_map_expose;
1959 widget_class->configure_event = osm_gps_map_configure;
1960 widget_class->button_press_event = osm_gps_map_button_press;
1961 widget_class->button_release_event = osm_gps_map_button_release;
1962 widget_class->motion_notify_event = osm_gps_map_motion_notify;
1963 widget_class->scroll_event = osm_gps_map_scroll_event;
1964
1965 g_object_class_install_property (object_class,
1966 PROP_AUTO_CENTER,
1967 g_param_spec_boolean ("auto-center",
1968 "auto center",
1969 "map auto center",
1970 TRUE,
1971 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
1972
1973 g_object_class_install_property (object_class,
1974 PROP_RECORD_TRIP_HISTORY,
1975 g_param_spec_boolean ("record-trip-history",
1976 "record trip history",
1977 "should all gps points be recorded in a trip history",
1978 TRUE,
1979 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
1980
1981 g_object_class_install_property (object_class,
1982 PROP_SHOW_TRIP_HISTORY,
1983 g_param_spec_boolean ("show-trip-history",
1984 "show trip history",
1985 "should the recorded trip history be shown on the map",
1986 TRUE,
1987 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
1988
1989 g_object_class_install_property (object_class,
1990 PROP_AUTO_DOWNLOAD,
1991 g_param_spec_boolean ("auto-download",
1992 "auto download",
1993 "map auto download",
1994 TRUE,
1995 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
1996
1997 g_object_class_install_property (object_class,
1998 PROP_REPO_URI,
1999 g_param_spec_string ("repo-uri",
2000 "repo uri",
2001 "map source tile repository uri",
2002 OSM_REPO_URI,
2003 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2004
2005 g_object_class_install_property (object_class,
2006 PROP_PROXY_URI,
2007 g_param_spec_string ("proxy-uri",
2008 "proxy uri",
2009 "http proxy uri on NULL",
2010 NULL,
2011 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2012
2013 g_object_class_install_property (object_class,
2014 PROP_TILE_CACHE_DIR,
2015 g_param_spec_string ("tile-cache",
2016 "tile cache",
2017 "osm local tile cache dir",
2018 NULL,
2019 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2020
2021 g_object_class_install_property (object_class,
2022 PROP_TILE_CACHE_DIR_IS_FULL_PATH,
2023 g_param_spec_boolean ("tile-cache-is-full-path",
2024 "tile cache is full path",
2025 "if true, the path passed to tile-cache is interpreted as the full cache path",
2026 FALSE,
2027 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2028
2029 g_object_class_install_property (object_class,
2030 PROP_ZOOM,
2031 g_param_spec_int ("zoom",
2032 "zoom",
2033 "zoom level",
2034 MIN_ZOOM, /* minimum property value */
2035 MAX_ZOOM, /* maximum property value */
2036 3,
2037 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2038
2039 g_object_class_install_property (object_class,
2040 PROP_MAX_ZOOM,
2041 g_param_spec_int ("max-zoom",
2042 "max zoom",
2043 "maximum zoom level",
2044 MIN_ZOOM, /* minimum property value */
2045 MAX_ZOOM, /* maximum property value */
2046 OSM_MAX_ZOOM,
2047 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2048
2049 g_object_class_install_property (object_class,
2050 PROP_MIN_ZOOM,
2051 g_param_spec_int ("min-zoom",
2052 "min zoom",
2053 "minimum zoom level",
2054 MIN_ZOOM, /* minimum property value */
2055 MAX_ZOOM, /* maximum property value */
2056 OSM_MIN_ZOOM,
2057 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2058
2059 g_object_class_install_property (object_class,
2060 PROP_LATITUDE,
2061 g_param_spec_float ("latitude",
2062 "latitude",
2063 "latitude in degrees",
2064 -90.0, /* minimum property value */
2065 90.0, /* maximum property value */
2066 0,
2067 G_PARAM_READABLE));
2068
2069 g_object_class_install_property (object_class,
2070 PROP_LONGITUDE,
2071 g_param_spec_float ("longitude",
2072 "longitude",
2073 "longitude in degrees",
2074 -180.0, /* minimum property value */
2075 180.0, /* maximum property value */
2076 0,
2077 G_PARAM_READABLE));
2078
2079 g_object_class_install_property (object_class,
2080 PROP_MAP_X,
2081 g_param_spec_int ("map-x",
2082 "map-x",
2083 "initial map x location",
2084 G_MININT, /* minimum property value */
2085 G_MAXINT, /* maximum property value */
2086 890,
2087 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2088
2089 g_object_class_install_property (object_class,
2090 PROP_MAP_Y,
2091 g_param_spec_int ("map-y",
2092 "map-y",
2093 "initial map y location",
2094 G_MININT, /* minimum property value */
2095 G_MAXINT, /* maximum property value */
2096 515,
2097 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2098
2099 g_object_class_install_property (object_class,
2100 PROP_TILES_QUEUED,
2101 g_param_spec_int ("tiles-queued",
2102 "tiles-queued",
2103 "number of tiles currently waiting to download",
2104 G_MININT, /* minimum property value */
2105 G_MAXINT, /* maximum property value */
2106 0,
2107 G_PARAM_READABLE));
2108
2109 g_object_class_install_property (object_class,
2110 PROP_GPS_TRACK_WIDTH,
2111 g_param_spec_int ("gps-track-width",
2112 "gps-track-width",
2113 "width of the lines drawn for the gps track",
2114 1, /* minimum property value */
2115 G_MAXINT, /* maximum property value */
2116 4,
2117 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2118
2119 g_object_class_install_property (object_class,
2120 PROP_GPS_POINT_R1,
2121 g_param_spec_int ("gps-track-point-radius",
2122 "gps-track-point-radius",
2123 "radius of the gps point inner circle",
2124 0, /* minimum property value */
2125 G_MAXINT, /* maximum property value */
2126 5,
2127 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2128
2129 g_object_class_install_property (object_class,
2130 PROP_GPS_POINT_R2,
2131 g_param_spec_int ("gps-track-highlight-radius",
2132 "gps-track-highlight-radius",
2133 "radius of the gps point highlight circle",
2134 0, /* minimum property value */
2135 G_MAXINT, /* maximum property value */
2136 20,
2137 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2138
2139 g_object_class_install_property (object_class,
2140 PROP_MAP_SOURCE,
2141 g_param_spec_int ("map-source",
2142 "map source",
2143 "map source ID",
2144 -1, /* minimum property value */
2145 G_MAXINT, /* maximum property value */
2146 -1,
2147 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2148
2149 g_object_class_install_property (object_class,
2150 PROP_IMAGE_FORMAT,
2151 g_param_spec_string ("image-format",
2152 "image format",
2153 "map source tile repository image format (jpg, png)",
2154 OSM_IMAGE_FORMAT,
2155 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2156 }
2157
2158 const char*
2159 osm_gps_map_source_get_friendly_name(OsmGpsMapSource_t source)
2160 {
2161 switch(source)
2162 {
2163 case OSM_GPS_MAP_SOURCE_NULL:
2164 return "None";
2165 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP:
2166 return "OpenStreetMap";
2167 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP_RENDERER:
2168 return "OpenStreetMap Renderer";
2169 case OSM_GPS_MAP_SOURCE_OPENAERIALMAP:
2170 return "OpenAerialMap";
2171 case OSM_GPS_MAP_SOURCE_MAPS_FOR_FREE:
2172 return "Maps-For-Free";
2173 case OSM_GPS_MAP_SOURCE_GOOGLE_STREET:
2174 return "Google Maps";
2175 case OSM_GPS_MAP_SOURCE_GOOGLE_SATELLITE:
2176 return "Google Satellite";
2177 case OSM_GPS_MAP_SOURCE_GOOGLE_HYBRID:
2178 return "Google Hybrid";
2179 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_STREET:
2180 return "Virtual Earth";
2181 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_SATELLITE:
2182 return "Virtual Earth Satellite";
2183 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_HYBRID:
2184 return "Virtual Earth Hybrid";
2185 case OSM_GPS_MAP_SOURCE_YAHOO_STREET:
2186 return "Yahoo Maps";
2187 case OSM_GPS_MAP_SOURCE_YAHOO_SATELLITE:
2188 return "Yahoo Satellite";
2189 case OSM_GPS_MAP_SOURCE_YAHOO_HYBRID:
2190 return "Yahoo Hybrid";
2191 default:
2192 return NULL;
2193 }
2194 return NULL;
2195 }
2196
2197 //http://www.internettablettalk.com/forums/showthread.php?t=5209
2198 //https://garage.maemo.org/plugins/scmsvn/viewcvs.php/trunk/src/maps.c?root=maemo-mapper&view=markup
2199 //http://www.ponies.me.uk/maps/GoogleTileUtils.java
2200 //http://www.mgmaps.com/cache/MapTileCacher.perl
2201 const char*
2202 osm_gps_map_source_get_repo_uri(OsmGpsMapSource_t source)
2203 {
2204 switch(source)
2205 {
2206 case OSM_GPS_MAP_SOURCE_NULL:
2207 return "none://";
2208 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP:
2209 return OSM_REPO_URI;
2210 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP_RENDERER:
2211 return "http://tah.openstreetmap.org/Tiles/tile/#Z/#X/#Y.png";
2212 case OSM_GPS_MAP_SOURCE_OPENAERIALMAP:
2213 return "http://tile.openaerialmap.org/tiles/1.0.0/openaerialmap-900913/#Z/#X/#Y.jpg";
2214 case OSM_GPS_MAP_SOURCE_MAPS_FOR_FREE:
2215 return "http://maps-for-free.com/layer/relief/z#Z/row#Y/#Z_#X-#Y.jpg";
2216 case OSM_GPS_MAP_SOURCE_GOOGLE_STREET:
2217 return "http://mt#R.google.com/vt/v=w2.97&x=#X&y=#Y&z=#Z";
2218 case OSM_GPS_MAP_SOURCE_GOOGLE_SATELLITE:
2219 return "http://khm#R.google.com/kh?n=404&v=3&t=#Q";
2220 case OSM_GPS_MAP_SOURCE_GOOGLE_HYBRID:
2221 return NULL; /* No longer working "http://mt#R.google.com/mt?n=404&v=w2t.99&x=#X&y=#Y&zoom=#S" */
2222 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_STREET:
2223 return "http://a#R.ortho.tiles.virtualearth.net/tiles/r#W.jpeg?g=50";
2224 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_SATELLITE:
2225 return "http://a#R.ortho.tiles.virtualearth.net/tiles/a#W.jpeg?g=50";
2226 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_HYBRID:
2227 return "http://a#R.ortho.tiles.virtualearth.net/tiles/h#W.jpeg?g=50";
2228 case OSM_GPS_MAP_SOURCE_YAHOO_STREET:
2229 case OSM_GPS_MAP_SOURCE_YAHOO_SATELLITE:
2230 case OSM_GPS_MAP_SOURCE_YAHOO_HYBRID:
2231 /* TODO: Implement signed Y, aka U
2232 * http://us.maps3.yimg.com/aerial.maps.yimg.com/ximg?v=1.7&t=a&s=256&x=%d&y=%-d&z=%d
2233 * x = tilex,
2234 * y = (1 << (MAX_ZOOM - zoom)) - tiley - 1,
2235 * z = zoom - (MAX_ZOOM - 17));
2236 */
2237 return NULL;
2238 default:
2239 return NULL;
2240 }
2241 return NULL;
2242 }
2243
2244 const char *
2245 osm_gps_map_source_get_image_format(OsmGpsMapSource_t source)
2246 {
2247 switch(source) {
2248 case OSM_GPS_MAP_SOURCE_NULL:
2249 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP:
2250 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP_RENDERER:
2251 return "png";
2252 case OSM_GPS_MAP_SOURCE_OPENAERIALMAP:
2253 case OSM_GPS_MAP_SOURCE_GOOGLE_STREET:
2254 case OSM_GPS_MAP_SOURCE_GOOGLE_HYBRID:
2255 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_STREET:
2256 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_SATELLITE:
2257 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_HYBRID:
2258 case OSM_GPS_MAP_SOURCE_YAHOO_STREET:
2259 case OSM_GPS_MAP_SOURCE_YAHOO_SATELLITE:
2260 case OSM_GPS_MAP_SOURCE_YAHOO_HYBRID:
2261 case OSM_GPS_MAP_SOURCE_MAPS_FOR_FREE:
2262 case OSM_GPS_MAP_SOURCE_GOOGLE_SATELLITE:
2263 return "jpg";
2264 default:
2265 return "bin";
2266 }
2267 return "bin";
2268 }
2269
2270
2271 int
2272 osm_gps_map_source_get_min_zoom(OsmGpsMapSource_t source)
2273 {
2274 return 1;
2275 }
2276
2277 int
2278 osm_gps_map_source_get_max_zoom(OsmGpsMapSource_t source)
2279 {
2280 switch(source) {
2281 case OSM_GPS_MAP_SOURCE_NULL:
2282 return 18;
2283 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP:
2284 return OSM_MAX_ZOOM;
2285 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP_RENDERER:
2286 case OSM_GPS_MAP_SOURCE_OPENAERIALMAP:
2287 case OSM_GPS_MAP_SOURCE_GOOGLE_STREET:
2288 case OSM_GPS_MAP_SOURCE_GOOGLE_HYBRID:
2289 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_STREET:
2290 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_SATELLITE:
2291 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_HYBRID:
2292 case OSM_GPS_MAP_SOURCE_YAHOO_STREET:
2293 case OSM_GPS_MAP_SOURCE_YAHOO_SATELLITE:
2294 case OSM_GPS_MAP_SOURCE_YAHOO_HYBRID:
2295 return 17;
2296 case OSM_GPS_MAP_SOURCE_MAPS_FOR_FREE:
2297 return 11;
2298 case OSM_GPS_MAP_SOURCE_GOOGLE_SATELLITE:
2299 return 18;
2300 default:
2301 return 17;
2302 }
2303 return 17;
2304 }
2305
2306 void
2307 osm_gps_map_download_maps (OsmGpsMap *map, coord_t *pt1, coord_t *pt2, int zoom_start, int zoom_end)
2308 {
2309 int i,j,zoom,num_tiles;
2310 OsmGpsMapPrivate *priv = map->priv;
2311
2312 if (pt1 && pt2)
2313 {
2314 gchar *filename;
2315 num_tiles = 0;
2316 zoom_end = CLAMP(zoom_end, priv->min_zoom, priv->max_zoom);
2317 g_debug("Download maps: z:%d->%d",zoom_start, zoom_end);
2318
2319 for(zoom=zoom_start; zoom<=zoom_end; zoom++)
2320 {
2321 int x1,y1,x2,y2;
2322
2323 x1 = (int)floor((float)lon2pixel(zoom, pt1->rlon) / (float)TILESIZE);
2324 y1 = (int)floor((float)lat2pixel(zoom, pt1->rlat) / (float)TILESIZE);
2325
2326 x2 = (int)floor((float)lon2pixel(zoom, pt2->rlon) / (float)TILESIZE);
2327 y2 = (int)floor((float)lat2pixel(zoom, pt2->rlat) / (float)TILESIZE);
2328
2329 // loop x1-x2
2330 for(i=x1; i<=x2; i++)
2331 {
2332 // loop y1 - y2
2333 for(j=y1; j<=y2; j++)
2334 {
2335 // x = i, y = j
2336 filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
2337 priv->cache_dir, G_DIR_SEPARATOR,
2338 zoom, G_DIR_SEPARATOR,
2339 i, G_DIR_SEPARATOR,
2340 j,
2341 priv->image_format);
2342 if (!g_file_test(filename, G_FILE_TEST_EXISTS))
2343 {
2344 osm_gps_map_download_tile(map, zoom, i, j, FALSE);
2345 num_tiles++;
2346 }
2347 g_free(filename);
2348 }
2349 }
2350 g_debug("DL @Z:%d = %d tiles",zoom,num_tiles);
2351 }
2352 }
2353 }
2354
2355 void
2356 osm_gps_map_get_bbox (OsmGpsMap *map, coord_t *pt1, coord_t *pt2)
2357 {
2358 OsmGpsMapPrivate *priv = map->priv;
2359
2360 if (pt1 && pt2) {
2361 pt1->rlat = pixel2lat(priv->map_zoom, priv->map_y);
2362 pt1->rlon = pixel2lon(priv->map_zoom, priv->map_x);
2363 pt2->rlat = pixel2lat(priv->map_zoom, priv->map_y + GTK_WIDGET(map)->allocation.height);
2364 pt2->rlon = pixel2lon(priv->map_zoom, priv->map_x + GTK_WIDGET(map)->allocation.width);
2365
2366 g_debug("BBOX: %f %f %f %f", pt1->rlat, pt1->rlon, pt2->rlat, pt2->rlon);
2367 }
2368 }
2369
2370 void
2371 osm_gps_map_set_mapcenter (OsmGpsMap *map, float latitude, float longitude, int zoom)
2372 {
2373 osm_gps_map_set_center (map, latitude, longitude);
2374 osm_gps_map_set_zoom (map, zoom);
2375 }
2376
2377 void
2378 osm_gps_map_set_center (OsmGpsMap *map, float latitude, float longitude)
2379 {
2380 int pixel_x, pixel_y;
2381 OsmGpsMapPrivate *priv;
2382
2383 g_return_if_fail (OSM_IS_GPS_MAP (map));
2384 priv = map->priv;
2385
2386 priv->center_rlat = deg2rad(latitude);
2387 priv->center_rlon = deg2rad(longitude);
2388 priv->center_coord_set = TRUE;
2389
2390 // pixel_x,y, offsets
2391 pixel_x = lon2pixel(priv->map_zoom, priv->center_rlon);
2392 pixel_y = lat2pixel(priv->map_zoom, priv->center_rlat);
2393
2394 priv->map_x = pixel_x - GTK_WIDGET(map)->allocation.width/2;
2395 priv->map_y = pixel_y - GTK_WIDGET(map)->allocation.height/2;
2396
2397 osm_gps_map_map_redraw_idle(map);
2398 }
2399
2400 int
2401 osm_gps_map_set_zoom (OsmGpsMap *map, int zoom)
2402 {
2403 int zoom_old;
2404 double factor = 0.0;
2405 int width_center, height_center;
2406 OsmGpsMapPrivate *priv;
2407
2408 g_return_val_if_fail (OSM_IS_GPS_MAP (map), 0);
2409 priv = map->priv;
2410
2411 if (zoom != priv->map_zoom)
2412 {
2413 width_center = GTK_WIDGET(map)->allocation.width / 2;
2414 height_center = GTK_WIDGET(map)->allocation.height / 2;
2415
2416 zoom_old = priv->map_zoom;
2417 //constrain zoom min_zoom -> max_zoom
2418 priv->map_zoom = CLAMP(zoom, priv->min_zoom, priv->max_zoom);
2419
2420 if (priv->center_coord_set)
2421 {
2422 priv->map_x = lon2pixel(priv->map_zoom, priv->center_rlon) - width_center;
2423 priv->map_y = lat2pixel(priv->map_zoom, priv->center_rlat) - height_center;
2424 }
2425 else
2426 {
2427 factor = exp(priv->map_zoom * M_LN2)/exp(zoom_old * M_LN2);
2428 priv->map_x = ((priv->map_x + width_center) * factor) - width_center;
2429 priv->map_y = ((priv->map_y + height_center) * factor) - height_center;
2430 }
2431
2432 g_debug("Zoom changed from %d to %d factor:%f x:%d",
2433 zoom_old, priv->map_zoom, factor, priv->map_x);
2434
2435 osm_gps_map_map_redraw_idle(map);
2436 }
2437 return priv->map_zoom;
2438 }
2439
2440 void
2441 osm_gps_map_add_track (OsmGpsMap *map, GSList *track)
2442 {
2443 OsmGpsMapPrivate *priv;
2444
2445 g_return_if_fail (OSM_IS_GPS_MAP (map));
2446 priv = map->priv;
2447
2448 if (track) {
2449 priv->tracks = g_slist_append(priv->tracks, track);
2450 osm_gps_map_map_redraw_idle(map);
2451 }
2452 }
2453
2454 void
2455 osm_gps_map_clear_tracks (OsmGpsMap *map)
2456 {
2457 g_return_if_fail (OSM_IS_GPS_MAP (map));
2458
2459 osm_gps_map_free_tracks(map);
2460 osm_gps_map_map_redraw_idle(map);
2461 }
2462
2463 void
2464 osm_gps_map_add_image (OsmGpsMap *map, float latitude, float longitude, GdkPixbuf *image)
2465 {
2466 g_return_if_fail (OSM_IS_GPS_MAP (map));
2467
2468 if (image) {
2469 OsmGpsMapPrivate *priv = map->priv;
2470 image_t *im;
2471
2472 //cache w/h for speed, and add image to list
2473 im = g_new0(image_t,1);
2474 im->w = gdk_pixbuf_get_width(image);
2475 im->h = gdk_pixbuf_get_height(image);
2476 im->pt.rlat = deg2rad(latitude);
2477 im->pt.rlon = deg2rad(longitude);
2478
2479 g_object_ref(image);
2480 im->image = image;
2481
2482 priv->images = g_slist_append(priv->images, im);
2483
2484 osm_gps_map_map_redraw_idle(map);
2485 }
2486 }
2487
2488 gboolean
2489 osm_gps_map_remove_image (OsmGpsMap *map, GdkPixbuf *image)
2490 {
2491 OsmGpsMapPrivate *priv = map->priv;
2492 if (priv->images) {
2493 GSList *list;
2494 for(list = priv->images; list != NULL; list = list->next)
2495 {
2496 image_t *im = list->data;
2497 if (im->image == image)
2498 {
2499 priv->images = g_slist_remove_link(priv->images, list);
2500 g_object_unref(im->image);
2501 g_free(im);
2502 osm_gps_map_map_redraw_idle(map);
2503 return TRUE;
2504 }
2505 }
2506 }
2507 return FALSE;
2508 }
2509
2510 void
2511 osm_gps_map_clear_images (OsmGpsMap *map)
2512 {
2513 g_return_if_fail (OSM_IS_GPS_MAP (map));
2514
2515 osm_gps_map_free_images(map);
2516 osm_gps_map_map_redraw_idle(map);
2517 }
2518
2519 void
2520 osm_gps_map_osd_speed (OsmGpsMap *map, float speed)
2521 {
2522 OsmGpsMapPrivate *priv;
2523
2524 PangoContext *context = NULL;
2525 PangoLayout *layout = NULL;
2526 PangoFontDescription *desc = NULL;
2527
2528 GdkColor color;
2529 GdkGC *gc;
2530
2531 gchar *buffer;
2532 //static int x = 10, y = 10;
2533 static int width = 0, height = 0;
2534
2535 g_return_if_fail (OSM_IS_GPS_MAP (map));
2536 priv = map->priv;
2537
2538 buffer = g_strdup_printf("%.0f", speed);
2539
2540 /* pango initialisation */
2541 context = gtk_widget_get_pango_context (GTK_WIDGET(map));
2542 layout = pango_layout_new (context);
2543 desc = pango_font_description_new();
2544
2545 pango_font_description_set_size (desc, 40 * PANGO_SCALE);
2546 pango_layout_set_font_description (layout, desc);
2547 pango_layout_set_text (layout, buffer, strlen(buffer));
2548
2549 gc = gdk_gc_new (GTK_WIDGET(map)->window);
2550
2551 color.red = (0 > 50) ? 0xffff : 0;
2552 color.green = 0;
2553 color.blue = 0;
2554
2555 gdk_gc_set_rgb_fg_color (gc, &color);
2556
2557 /* faster / less flicker alternative:*/
2558 gdk_draw_drawable (
2559 GTK_WIDGET(map)->window,
2560 GTK_WIDGET(map)->style->fg_gc[GTK_WIDGET_STATE(map)],
2561 priv->pixmap,
2562 0,0,
2563 0,0,
2564 width+10,width+10);
2565
2566 gdk_draw_layout(GTK_WIDGET(map)->window,
2567 gc,
2568 0, 0,
2569 layout);
2570
2571 /* set width and height */
2572 pango_layout_get_pixel_size(layout, &width, &height);
2573
2574 g_free(buffer);
2575 pango_font_description_free (desc);
2576 g_object_unref (layout);
2577 g_object_unref (gc);
2578 }
2579
2580 void
2581 osm_gps_map_draw_gps (OsmGpsMap *map, float latitude, float longitude, float heading)
2582 {
2583 int pixel_x, pixel_y;
2584 OsmGpsMapPrivate *priv;
2585
2586 g_return_if_fail (OSM_IS_GPS_MAP (map));
2587 priv = map->priv;
2588
2589 priv->gps->rlat = deg2rad(latitude);
2590 priv->gps->rlon = deg2rad(longitude);
2591 priv->gps_valid = TRUE;
2592
2593 // pixel_x,y, offsets
2594 pixel_x = lon2pixel(priv->map_zoom, priv->gps->rlon);
2595 pixel_y = lat2pixel(priv->map_zoom, priv->gps->rlat);
2596
2597 //If trip marker add to list of gps points.
2598 if (priv->record_trip_history) {
2599 coord_t *tp = g_new0(coord_t,1);
2600 tp->rlat = priv->gps->rlat;
2601 tp->rlon = priv->gps->rlon;
2602 priv->trip_history = g_slist_append(priv->trip_history, tp);
2603 }
2604
2605 // dont draw anything if we are dragging
2606 if (priv->dragging) {
2607 g_debug("Dragging");
2608 return;
2609 }
2610
2611 //Automatically center the map if the track approaches the edge
2612 if(priv->map_auto_center) {
2613 int x = pixel_x - priv->map_x;
2614 int y = pixel_y - priv->map_y;
2615 int width = GTK_WIDGET(map)->allocation.width;
2616 int height = GTK_WIDGET(map)->allocation.height;
2617 if( x < (width/2 - width/8) || x > (width/2 + width/8) ||
2618 y < (height/2 - height/8) || y > (height/2 + height/8)) {
2619
2620 priv->map_x = pixel_x - GTK_WIDGET(map)->allocation.width/2;
2621 priv->map_y = pixel_y - GTK_WIDGET(map)->allocation.height/2;
2622 priv->center_coord_set = FALSE;
2623 }
2624 }
2625
2626 // this redraws the map (including the gps track, and adjusts the
2627 // map center if it was changed
2628 osm_gps_map_map_redraw_idle(map);
2629 }
2630
2631 void
2632 osm_gps_map_clear_gps (OsmGpsMap *map)
2633 {
2634 osm_gps_map_free_trip(map);
2635 osm_gps_map_map_redraw_idle(map);
2636 }
2637
2638 coord_t
2639 osm_gps_map_get_co_ordinates (OsmGpsMap *map, int pixel_x, int pixel_y)
2640 {
2641 coord_t coord;
2642 OsmGpsMapPrivate *priv = map->priv;
2643
2644 coord.rlat = pixel2lat(priv->map_zoom, priv->map_y + pixel_y);
2645 coord.rlon = pixel2lon(priv->map_zoom, priv->map_x + pixel_x);
2646 return coord;
2647 }
2648
2649 GtkWidget *
2650 osm_gps_map_new (void)
2651 {
2652 return g_object_new (OSM_TYPE_GPS_MAP, NULL);
2653 }
2654
2655 void
2656 osm_gps_map_screen_to_geographic (OsmGpsMap *map, gint pixel_x, gint pixel_y,
2657 gfloat *latitude, gfloat *longitude)
2658 {
2659 OsmGpsMapPrivate *priv;
2660
2661 g_return_if_fail (OSM_IS_GPS_MAP (map));
2662 priv = map->priv;
2663
2664 if (latitude)
2665 *latitude = rad2deg(pixel2lat(priv->map_zoom, priv->map_y + pixel_y));
2666 if (longitude)
2667 *longitude = rad2deg(pixel2lon(priv->map_zoom, priv->map_x + pixel_x));
2668 }
2669
2670 void
2671 osm_gps_map_geographic_to_screen (OsmGpsMap *map,
2672 gfloat latitude, gfloat longitude,
2673 gint *pixel_x, gint *pixel_y)
2674 {
2675 OsmGpsMapPrivate *priv;
2676
2677 g_return_if_fail (OSM_IS_GPS_MAP (map));
2678 priv = map->priv;
2679
2680 if (pixel_x)
2681 *pixel_x = lon2pixel(priv->map_zoom, deg2rad(longitude)) - priv->map_x;
2682 if (pixel_y)
2683 *pixel_y = lat2pixel(priv->map_zoom, deg2rad(latitude)) - priv->map_y;
2684 }
2685
2686 void
2687 osm_gps_map_scroll (OsmGpsMap *map, gint dx, gint dy)
2688 {
2689 OsmGpsMapPrivate *priv;
2690
2691 g_return_if_fail (OSM_IS_GPS_MAP (map));
2692 priv = map->priv;
2693
2694 priv->center_coord_set = FALSE;
2695 priv->map_x += dx;
2696 priv->map_y += dy;
2697
2698 osm_gps_map_map_redraw_idle (map);
2699 }
2700
2701 float
2702 osm_gps_map_get_scale(OsmGpsMap *map)
2703 {
2704 OsmGpsMapPrivate *priv;
2705
2706 g_return_val_if_fail (OSM_IS_GPS_MAP (map), OSM_NAN);
2707 priv = map->priv;
2708
2709 return osm_gps_map_get_scale_at_point(priv->map_zoom, priv->center_rlat, priv->center_rlon);
2710 }
2711
2712 void
2713 osm_gps_map_draw_balloon (OsmGpsMap *map, float latitude, float longitude)
2714 {
2715 OsmGpsMapPrivate *priv;
2716
2717 /* remove and previously installed balloon */
2718 osm_gps_map_clear_balloon (map);
2719
2720 g_return_if_fail (OSM_IS_GPS_MAP (map));
2721 priv = map->priv;
2722
2723 priv->balloon->rlat = deg2rad(latitude);
2724 priv->balloon->rlon = deg2rad(longitude);
2725 priv->balloon_valid = TRUE;
2726
2727 // this redraws the map
2728 osm_gps_map_map_redraw_idle(map);
2729 }
2730
2731 void
2732 osm_gps_map_clear_balloon (OsmGpsMap *map)
2733 {
2734 OsmGpsMapPrivate *priv;
2735
2736 g_return_if_fail (OSM_IS_GPS_MAP (map));
2737 priv = map->priv;
2738
2739 priv->balloon_valid = FALSE;
2740
2741 osm_gps_map_map_redraw_idle(map);
2742 }