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

Parent Directory Parent Directory | Revision Log Revision Log


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