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

Parent Directory Parent Directory | Revision Log Revision Log


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