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

Parent Directory Parent Directory | Revision Log Revision Log


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