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

Parent Directory Parent Directory | Revision Log Revision Log


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