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

Parent Directory Parent Directory | Revision Log Revision Log


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