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

Parent Directory Parent Directory | Revision Log Revision Log


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