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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 58 - (show annotations)
Mon Aug 17 10:51:56 2009 UTC (14 years, 8 months ago) by harbaum
File MIME type: text/plain
File size: 96404 byte(s)
More balloon stuff
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 250
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 printf("clocked at %d %d\n", x, y);
820
821 if (!priv->balloon.valid)
822 return;
823
824 /* check if the close button was clicked */
825 if ((x > priv->balloon.rect.w - 2*CLOSE_BUTTON_RADIUS) &&
826 (x < priv->balloon.rect.w) &&
827 (y > 0) && (y < 2*CLOSE_BUTTON_RADIUS)) {
828
829 priv->balloon.valid = FALSE;
830 osm_gps_map_map_redraw_idle(map);
831 }
832 }
833
834 /* return true if balloon is being displayed and if */
835 /* the given coordinate is within this balloon */
836 static gboolean
837 osm_gps_map_in_balloon(OsmGpsMapPrivate *priv, gint x, gint y)
838 {
839 return (priv->balloon.valid &&
840 (x > priv->balloon.rect.x) &&
841 (x < priv->balloon.rect.x + priv->balloon.rect.w) &&
842 (y > priv->balloon.rect.y) &&
843 (y < priv->balloon.rect.y + priv->balloon.rect.h));
844 }
845
846 static void
847 osm_gps_map_blit_tile(OsmGpsMap *map, GdkPixbuf *pixbuf, int offset_x, int offset_y)
848 {
849 OsmGpsMapPrivate *priv = map->priv;
850
851 g_debug("Queing redraw @ %d,%d (w:%d h:%d)", offset_x,offset_y, TILESIZE,TILESIZE);
852
853 /* draw pixbuf onto pixmap */
854 gdk_draw_pixbuf (priv->pixmap,
855 priv->gc_map,
856 pixbuf,
857 0,0,
858 offset_x,offset_y,
859 TILESIZE,TILESIZE,
860 GDK_RGB_DITHER_NONE, 0, 0);
861 }
862
863 /* libsoup-2.2 and libsoup-2.4 use different ways to store the body data */
864 #ifdef LIBSOUP22
865 #define soup_message_headers_append(a,b,c) soup_message_add_header(a,b,c)
866 #define MSG_RESPONSE_BODY(a) ((a)->response.body)
867 #define MSG_RESPONSE_LEN(a) ((a)->response.length)
868 #define MSG_RESPONSE_LEN_FORMAT "%u"
869 #else
870 #define MSG_RESPONSE_BODY(a) ((a)->response_body->data)
871 #define MSG_RESPONSE_LEN(a) ((a)->response_body->length)
872 #define MSG_RESPONSE_LEN_FORMAT "%lld"
873 #endif
874
875 #ifdef LIBSOUP22
876 static void
877 osm_gps_map_tile_download_complete (SoupMessage *msg, gpointer user_data)
878 #else
879 static void
880 osm_gps_map_tile_download_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
881 #endif
882 {
883 FILE *file;
884 tile_download_t *dl = (tile_download_t *)user_data;
885 OsmGpsMap *map = OSM_GPS_MAP(dl->map);
886 OsmGpsMapPrivate *priv = map->priv;
887 gboolean file_saved = FALSE;
888
889 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
890 {
891 /* save tile into cachedir if one has been specified */
892 if (priv->cache_dir)
893 {
894 if (g_mkdir_with_parents(dl->folder,0700) == 0)
895 {
896 file = g_fopen(dl->filename, "wb");
897 if (file != NULL)
898 {
899 fwrite (MSG_RESPONSE_BODY(msg), 1, MSG_RESPONSE_LEN(msg), file);
900 file_saved = TRUE;
901 g_debug("Wrote "MSG_RESPONSE_LEN_FORMAT" bytes to %s", MSG_RESPONSE_LEN(msg), dl->filename);
902 fclose (file);
903
904 }
905 }
906 else
907 {
908 g_warning("Error creating tile download directory: %s", dl->folder);
909 }
910 }
911
912 if (dl->redraw)
913 {
914 GdkPixbuf *pixbuf = NULL;
915
916 /* if the file was actually stored on disk, we can simply */
917 /* load and decode it from that file */
918 if (priv->cache_dir)
919 {
920 if (file_saved)
921 {
922 pixbuf = gdk_pixbuf_new_from_file (dl->filename, NULL);
923 }
924 }
925 else
926 {
927 GdkPixbufLoader *loader;
928 char *extension = strrchr (dl->filename, '.');
929
930 /* parse file directly from memory */
931 if (extension)
932 {
933 loader = gdk_pixbuf_loader_new_with_type (extension+1, NULL);
934 if (!gdk_pixbuf_loader_write (loader, (unsigned char*)MSG_RESPONSE_BODY(msg), MSG_RESPONSE_LEN(msg), NULL))
935 {
936 g_warning("Error: Decoding of image failed");
937 }
938 gdk_pixbuf_loader_close(loader, NULL);
939
940 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
941
942 /* give up loader but keep the pixbuf */
943 g_object_ref(pixbuf);
944 g_object_unref(loader);
945 }
946 else
947 {
948 g_warning("Error: Unable to determine image file format");
949 }
950 }
951
952 /* Store the tile into the cache */
953 if (G_LIKELY (pixbuf))
954 {
955 OsmCachedTile *tile = g_slice_new (OsmCachedTile);
956 tile->pixbuf = pixbuf;
957 tile->redraw_cycle = priv->redraw_cycle;
958 /* if the tile is already in the cache (it could be one
959 * rendered from another zoom level), it will be
960 * overwritten */
961 g_hash_table_insert (priv->tile_cache, dl->filename, tile);
962 /* NULL-ify dl->filename so that it won't be freed, as
963 * we are using it as a key in the hash table */
964 dl->filename = NULL;
965 }
966 osm_gps_map_map_redraw_idle (map);
967 }
968 g_hash_table_remove(priv->tile_queue, dl->uri);
969
970 g_free(dl->uri);
971 g_free(dl->folder);
972 g_free(dl->filename);
973 g_free(dl);
974 }
975 else
976 {
977 g_warning("Error downloading tile: %d - %s", msg->status_code, msg->reason_phrase);
978 if (msg->status_code == SOUP_STATUS_NOT_FOUND)
979 {
980 g_hash_table_insert(priv->missing_tiles, dl->uri, NULL);
981 g_hash_table_remove(priv->tile_queue, dl->uri);
982 }
983 else if (msg->status_code == SOUP_STATUS_CANCELLED)
984 {
985 ;//application exiting
986 }
987 else
988 {
989 #ifdef LIBSOUP22
990 soup_session_requeue_message(dl->session, msg);
991 #else
992 soup_session_requeue_message(session, msg);
993 #endif
994 return;
995 }
996 }
997
998
999 }
1000
1001 static void
1002 osm_gps_map_download_tile (OsmGpsMap *map, int zoom, int x, int y, gboolean redraw)
1003 {
1004 SoupMessage *msg;
1005 OsmGpsMapPrivate *priv = map->priv;
1006 tile_download_t *dl = g_new0(tile_download_t,1);
1007
1008 //calculate the uri to download
1009 dl->uri = replace_map_uri(map, priv->repo_uri, zoom, x, y);
1010
1011 #ifdef LIBSOUP22
1012 dl->session = priv->soup_session;
1013 #endif
1014
1015 //check the tile has not already been queued for download,
1016 //or has been attempted, and its missing
1017 if (g_hash_table_lookup_extended(priv->tile_queue, dl->uri, NULL, NULL) ||
1018 g_hash_table_lookup_extended(priv->missing_tiles, dl->uri, NULL, NULL) )
1019 {
1020 g_debug("Tile already downloading (or missing)");
1021 g_free(dl->uri);
1022 g_free(dl);
1023 } else {
1024 dl->folder = g_strdup_printf("%s%c%d%c%d%c",
1025 priv->cache_dir, G_DIR_SEPARATOR,
1026 zoom, G_DIR_SEPARATOR,
1027 x, G_DIR_SEPARATOR);
1028 dl->filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
1029 priv->cache_dir, G_DIR_SEPARATOR,
1030 zoom, G_DIR_SEPARATOR,
1031 x, G_DIR_SEPARATOR,
1032 y,
1033 priv->image_format);
1034 dl->map = map;
1035 dl->redraw = redraw;
1036
1037 g_debug("Download tile: %d,%d z:%d\n\t%s --> %s", x, y, zoom, dl->uri, dl->filename);
1038
1039 msg = soup_message_new (SOUP_METHOD_GET, dl->uri);
1040 if (msg) {
1041 if (priv->the_google) {
1042 //Set maps.google.com as the referrer
1043 g_debug("Setting Google Referrer");
1044 soup_message_headers_append(msg->request_headers, "Referer", "http://maps.google.com/");
1045 //For google satelite also set the appropriate cookie value
1046 if (priv->uri_format & URI_HAS_Q) {
1047 const char *cookie = g_getenv("GOOGLE_COOKIE");
1048 if (cookie) {
1049 g_debug("Adding Google Cookie");
1050 soup_message_headers_append(msg->request_headers, "Cookie", cookie);
1051 }
1052 }
1053 }
1054
1055 g_hash_table_insert (priv->tile_queue, dl->uri, msg);
1056 soup_session_queue_message (priv->soup_session, msg, osm_gps_map_tile_download_complete, dl);
1057 } else {
1058 g_warning("Could not create soup message");
1059 g_free(dl->uri);
1060 g_free(dl->folder);
1061 g_free(dl->filename);
1062 g_free(dl);
1063 }
1064 }
1065 }
1066
1067 static GdkPixbuf *
1068 osm_gps_map_load_cached_tile (OsmGpsMap *map, int zoom, int x, int y)
1069 {
1070 OsmGpsMapPrivate *priv = map->priv;
1071 gchar *filename;
1072 GdkPixbuf *pixbuf = NULL;
1073 OsmCachedTile *tile;
1074
1075 filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
1076 priv->cache_dir, G_DIR_SEPARATOR,
1077 zoom, G_DIR_SEPARATOR,
1078 x, G_DIR_SEPARATOR,
1079 y,
1080 priv->image_format);
1081
1082 tile = g_hash_table_lookup (priv->tile_cache, filename);
1083 if (tile)
1084 {
1085 g_free (filename);
1086 }
1087 else
1088 {
1089 pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
1090 if (pixbuf)
1091 {
1092 tile = g_slice_new (OsmCachedTile);
1093 tile->pixbuf = pixbuf;
1094 g_hash_table_insert (priv->tile_cache, filename, tile);
1095 }
1096 }
1097
1098 /* set/update the redraw_cycle timestamp on the tile */
1099 if (tile)
1100 {
1101 tile->redraw_cycle = priv->redraw_cycle;
1102 pixbuf = g_object_ref (tile->pixbuf);
1103 }
1104
1105 return pixbuf;
1106 }
1107
1108 static GdkPixbuf *
1109 osm_gps_map_find_bigger_tile (OsmGpsMap *map, int zoom, int x, int y,
1110 int *zoom_found)
1111 {
1112 GdkPixbuf *pixbuf;
1113 int next_zoom, next_x, next_y;
1114
1115 if (zoom == 0) return NULL;
1116 next_zoom = zoom - 1;
1117 next_x = x / 2;
1118 next_y = y / 2;
1119 pixbuf = osm_gps_map_load_cached_tile (map, next_zoom, next_x, next_y);
1120 if (pixbuf)
1121 *zoom_found = next_zoom;
1122 else
1123 pixbuf = osm_gps_map_find_bigger_tile (map, next_zoom, next_x, next_y,
1124 zoom_found);
1125 return pixbuf;
1126 }
1127
1128 static GdkPixbuf *
1129 osm_gps_map_render_missing_tile_upscaled (OsmGpsMap *map, int zoom,
1130 int x, int y)
1131 {
1132 GdkPixbuf *pixbuf, *big, *area;
1133 int zoom_big, zoom_diff, area_size, area_x, area_y;
1134 int modulo;
1135
1136 big = osm_gps_map_find_bigger_tile (map, zoom, x, y, &zoom_big);
1137 if (!big) return NULL;
1138
1139 g_debug ("Found bigger tile (zoom = %d, wanted = %d)", zoom_big, zoom);
1140
1141 /* get a Pixbuf for the area to magnify */
1142 zoom_diff = zoom - zoom_big;
1143 area_size = TILESIZE >> zoom_diff;
1144 modulo = 1 << zoom_diff;
1145 area_x = (x % modulo) * area_size;
1146 area_y = (y % modulo) * area_size;
1147 area = gdk_pixbuf_new_subpixbuf (big, area_x, area_y,
1148 area_size, area_size);
1149 g_object_unref (big);
1150 pixbuf = gdk_pixbuf_scale_simple (area, TILESIZE, TILESIZE,
1151 GDK_INTERP_NEAREST);
1152 g_object_unref (area);
1153 return pixbuf;
1154 }
1155
1156 static GdkPixbuf *
1157 osm_gps_map_render_missing_tile (OsmGpsMap *map, int zoom, int x, int y)
1158 {
1159 /* maybe TODO: render from downscaled tiles, if the following fails */
1160 return osm_gps_map_render_missing_tile_upscaled (map, zoom, x, y);
1161 }
1162
1163 static void
1164 osm_gps_map_load_tile (OsmGpsMap *map, int zoom, int x, int y, int offset_x, int offset_y)
1165 {
1166 OsmGpsMapPrivate *priv = map->priv;
1167 gchar *filename;
1168 GdkPixbuf *pixbuf;
1169
1170 g_debug("Load tile %d,%d (%d,%d) z:%d", x, y, offset_x, offset_y, zoom);
1171
1172 if (priv->map_source == OSM_GPS_MAP_SOURCE_NULL) {
1173 osm_gps_map_blit_tile(map, priv->null_tile, offset_x,offset_y);
1174 return;
1175 }
1176
1177 filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
1178 priv->cache_dir, G_DIR_SEPARATOR,
1179 zoom, G_DIR_SEPARATOR,
1180 x, G_DIR_SEPARATOR,
1181 y,
1182 priv->image_format);
1183
1184 /* try to get file from internal cache first */
1185 if(!(pixbuf = osm_gps_map_load_cached_tile(map, zoom, x, y)))
1186 pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
1187
1188 if(pixbuf)
1189 {
1190 g_debug("Found tile %s", filename);
1191 osm_gps_map_blit_tile(map, pixbuf, offset_x,offset_y);
1192 g_object_unref (pixbuf);
1193 }
1194 else
1195 {
1196 if (priv->map_auto_download)
1197 osm_gps_map_download_tile(map, zoom, x, y, TRUE);
1198
1199 /* try to render the tile by scaling cached tiles from other zoom
1200 * levels */
1201 pixbuf = osm_gps_map_render_missing_tile (map, zoom, x, y);
1202 if (pixbuf)
1203 {
1204 gdk_draw_pixbuf (priv->pixmap,
1205 priv->gc_map,
1206 pixbuf,
1207 0,0,
1208 offset_x,offset_y,
1209 TILESIZE,TILESIZE,
1210 GDK_RGB_DITHER_NONE, 0, 0);
1211 g_object_unref (pixbuf);
1212 }
1213 else
1214 {
1215 //prevent some artifacts when drawing not yet loaded areas.
1216 gdk_draw_rectangle (priv->pixmap,
1217 GTK_WIDGET(map)->style->white_gc,
1218 TRUE, offset_x, offset_y, TILESIZE, TILESIZE);
1219 }
1220 }
1221 g_free(filename);
1222 }
1223
1224 static void
1225 osm_gps_map_fill_tiles_pixel (OsmGpsMap *map)
1226 {
1227 OsmGpsMapPrivate *priv = map->priv;
1228 int i,j, width, height, tile_x0, tile_y0, tiles_nx, tiles_ny;
1229 int offset_xn = 0;
1230 int offset_yn = 0;
1231 int offset_x;
1232 int offset_y;
1233
1234 g_debug("Fill tiles: %d,%d z:%d", priv->map_x, priv->map_y, priv->map_zoom);
1235
1236 offset_x = - priv->map_x % TILESIZE;
1237 offset_y = - priv->map_y % TILESIZE;
1238 if (offset_x > 0) offset_x -= TILESIZE;
1239 if (offset_y > 0) offset_y -= TILESIZE;
1240
1241 offset_xn = offset_x + EXTRA_BORDER;
1242 offset_yn = offset_y + EXTRA_BORDER;
1243
1244 width = GTK_WIDGET(map)->allocation.width;
1245 height = GTK_WIDGET(map)->allocation.height;
1246
1247 tiles_nx = (width - offset_x) / TILESIZE + 1;
1248 tiles_ny = (height - offset_y) / TILESIZE + 1;
1249
1250 tile_x0 = floor((float)priv->map_x / (float)TILESIZE);
1251 tile_y0 = floor((float)priv->map_y / (float)TILESIZE);
1252
1253 //TODO: implement wrap around
1254 for (i=tile_x0; i<(tile_x0+tiles_nx);i++)
1255 {
1256 for (j=tile_y0; j<(tile_y0+tiles_ny); j++)
1257 {
1258 if( j<0 || i<0 || i>=exp(priv->map_zoom * M_LN2) || j>=exp(priv->map_zoom * M_LN2))
1259 {
1260 gdk_draw_rectangle (priv->pixmap,
1261 GTK_WIDGET(map)->style->white_gc,
1262 TRUE,
1263 offset_xn, offset_yn,
1264 TILESIZE,TILESIZE);
1265 }
1266 else
1267 {
1268 osm_gps_map_load_tile(map,
1269 priv->map_zoom,
1270 i,j,
1271 offset_xn,offset_yn);
1272 }
1273 offset_yn += TILESIZE;
1274 }
1275 offset_xn += TILESIZE;
1276 offset_yn = offset_y + EXTRA_BORDER;
1277 }
1278 }
1279
1280 static void
1281 osm_gps_map_print_track (OsmGpsMap *map, GSList *trackpoint_list)
1282 {
1283 OsmGpsMapPrivate *priv = map->priv;
1284
1285 GSList *list;
1286 int x,y;
1287 int min_x = 0,min_y = 0,max_x = 0,max_y = 0;
1288 int lw = priv->ui_gps_track_width;
1289 int map_x0, map_y0;
1290 #ifdef USE_CAIRO
1291 cairo_t *cr;
1292 #else
1293 int last_x = 0, last_y = 0;
1294 GdkColor color;
1295 GdkGC *gc;
1296 #endif
1297
1298 #ifdef USE_CAIRO
1299 cr = gdk_cairo_create(priv->pixmap);
1300 cairo_set_line_width (cr, lw);
1301 cairo_set_source_rgba (cr, 60000.0/65535.0, 0.0, 0.0, 0.6);
1302 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
1303 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
1304 #else
1305 gc = gdk_gc_new(priv->pixmap);
1306 color.green = 0;
1307 color.blue = 0;
1308 color.red = 60000;
1309 gdk_gc_set_rgb_fg_color(gc, &color);
1310 gdk_gc_set_line_attributes(gc, lw, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
1311 #endif
1312
1313 map_x0 = priv->map_x - EXTRA_BORDER;
1314 map_y0 = priv->map_y - EXTRA_BORDER;
1315 for(list = trackpoint_list; list != NULL; list = list->next)
1316 {
1317 coord_t *tp = list->data;
1318
1319 x = lon2pixel(priv->map_zoom, tp->rlon) - map_x0;
1320 y = lat2pixel(priv->map_zoom, tp->rlat) - map_y0;
1321
1322 // first time through loop
1323 if (list == trackpoint_list) {
1324 #ifdef USE_CAIRO
1325 cairo_move_to(cr, x, y);
1326 #else
1327 last_x = x;
1328 last_y = y;
1329 #endif
1330 }
1331
1332 #ifdef USE_CAIRO
1333 cairo_line_to(cr, x, y);
1334 #else
1335 gdk_draw_line (priv->pixmap, gc, x, y, last_x, last_y);
1336 last_x = x;
1337 last_y = y;
1338 #endif
1339
1340 max_x = MAX(x,max_x);
1341 min_x = MIN(x,min_x);
1342 max_y = MAX(y,max_y);
1343 min_y = MIN(y,min_y);
1344 }
1345
1346 gtk_widget_queue_draw_area (
1347 GTK_WIDGET(map),
1348 min_x - lw,
1349 min_y - lw,
1350 max_x + (lw * 2),
1351 max_y + (lw * 2));
1352
1353 #ifdef USE_CAIRO
1354 cairo_stroke(cr);
1355 cairo_destroy(cr);
1356 #else
1357 g_object_unref(gc);
1358 #endif
1359 }
1360
1361 /* Prints the gps trip history, and any other tracks */
1362 static void
1363 osm_gps_map_print_tracks (OsmGpsMap *map)
1364 {
1365 OsmGpsMapPrivate *priv = map->priv;
1366
1367 if (priv->show_trip_history)
1368 osm_gps_map_print_track (map, priv->trip_history);
1369
1370 if (priv->tracks)
1371 {
1372 GSList* tmp = priv->tracks;
1373 while (tmp != NULL)
1374 {
1375 osm_gps_map_print_track (map, tmp->data);
1376 tmp = g_slist_next(tmp);
1377 }
1378 }
1379 }
1380
1381 static gboolean
1382 osm_gps_map_purge_cache_check(gpointer key, gpointer value, gpointer user)
1383 {
1384 return (((OsmCachedTile*)value)->redraw_cycle != ((OsmGpsMapPrivate*)user)->redraw_cycle);
1385 }
1386
1387 static void
1388 osm_gps_map_purge_cache (OsmGpsMap *map)
1389 {
1390 OsmGpsMapPrivate *priv = map->priv;
1391
1392 if (g_hash_table_size (priv->tile_cache) < priv->max_tile_cache_size)
1393 return;
1394
1395 /* run through the cache, and remove the tiles which have not been used
1396 * during the last redraw operation */
1397 g_hash_table_foreach_remove(priv->tile_cache, osm_gps_map_purge_cache_check, priv);
1398 }
1399
1400 static gboolean
1401 osm_gps_map_map_redraw (OsmGpsMap *map)
1402 {
1403 OsmGpsMapPrivate *priv = map->priv;
1404
1405 priv->idle_map_redraw = 0;
1406
1407 /* the motion_notify handler uses priv->pixmap to redraw the area; if we
1408 * change it while we are dragging, we will end up showing it in the wrong
1409 * place. This could be fixed by carefully recompute the coordinates, but
1410 * for now it's easier just to disable redrawing the map while dragging */
1411 if (priv->dragging)
1412 return FALSE;
1413
1414 priv->redraw_cycle++;
1415
1416 /* draw white background to initialise pixmap */
1417 gdk_draw_rectangle (
1418 priv->pixmap,
1419 GTK_WIDGET(map)->style->white_gc,
1420 TRUE,
1421 0, 0,
1422 GTK_WIDGET(map)->allocation.width + EXTRA_BORDER * 2,
1423 GTK_WIDGET(map)->allocation.height + EXTRA_BORDER * 2);
1424
1425 osm_gps_map_fill_tiles_pixel(map);
1426
1427 osm_gps_map_print_tracks(map);
1428 osm_gps_map_draw_gps_point(map);
1429 osm_gps_map_print_images(map);
1430 osm_gps_map_draw_balloon_int(map);
1431
1432 //osm_gps_map_osd_speed(map, 1.5);
1433 osm_gps_map_purge_cache(map);
1434 gtk_widget_queue_draw (GTK_WIDGET (map));
1435
1436 return FALSE;
1437 }
1438
1439 static void
1440 osm_gps_map_map_redraw_idle (OsmGpsMap *map)
1441 {
1442 OsmGpsMapPrivate *priv = map->priv;
1443
1444 if (priv->idle_map_redraw == 0)
1445 priv->idle_map_redraw = g_idle_add ((GSourceFunc)osm_gps_map_map_redraw, map);
1446 }
1447
1448 static void
1449 osm_gps_map_init (OsmGpsMap *object)
1450 {
1451 OsmGpsMapPrivate *priv;
1452
1453 priv = G_TYPE_INSTANCE_GET_PRIVATE (object, OSM_TYPE_GPS_MAP, OsmGpsMapPrivate);
1454 object->priv = priv;
1455
1456 priv->pixmap = NULL;
1457
1458 priv->trip_history = NULL;
1459 priv->gps = g_new0(coord_t, 1);
1460 priv->gps_valid = FALSE;
1461
1462 priv->balloon.coo = g_new0(coord_t, 1);
1463 priv->balloon.valid = FALSE;
1464 priv->balloon.cb = NULL;
1465
1466 priv->tracks = NULL;
1467 priv->images = NULL;
1468
1469 priv->drag_counter = 0;
1470 priv->drag_mouse_dx = 0;
1471 priv->drag_mouse_dy = 0;
1472 priv->drag_start_mouse_x = 0;
1473 priv->drag_start_mouse_y = 0;
1474
1475 priv->uri_format = 0;
1476 priv->the_google = FALSE;
1477
1478 priv->map_source = -1;
1479
1480 #ifndef LIBSOUP22
1481 //Change naumber of concurrent connections option?
1482 priv->soup_session = soup_session_async_new_with_options(
1483 SOUP_SESSION_USER_AGENT,
1484 "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11",
1485 NULL);
1486 #else
1487 /* libsoup-2.2 seems not to be able to set the user agent */
1488 priv->soup_session = soup_session_async_new();
1489 #endif
1490
1491 //Hash table which maps tile d/l URIs to SoupMessage requests
1492 priv->tile_queue = g_hash_table_new (g_str_hash, g_str_equal);
1493
1494 //Some mapping providers (Google) have varying degrees of tiles at multiple
1495 //zoom levels
1496 priv->missing_tiles = g_hash_table_new (g_str_hash, g_str_equal);
1497
1498 /* memory cache for most recently used tiles */
1499 priv->tile_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
1500 g_free, (GDestroyNotify)cached_tile_free);
1501 priv->max_tile_cache_size = 20;
1502
1503 gtk_widget_add_events (GTK_WIDGET (object),
1504 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1505 GDK_POINTER_MOTION_MASK |
1506 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
1507 GTK_WIDGET_SET_FLAGS (object, GTK_CAN_FOCUS);
1508
1509 g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MASK, my_log_handler, NULL);
1510 }
1511
1512 #ifndef G_CHECKSUM_MD5
1513 /* simple hash algorithm hack if md5 is not present */
1514 static char *simple_hash(char *str) {
1515 union {
1516 char str[4];
1517 gulong val;
1518 } hash = { .val = 0x55555555 };
1519
1520 while(*str) {
1521 hash.str[(int)str & 3] ^= *str;
1522 str++;
1523 }
1524 return g_strdup_printf("%08lX", hash.val);
1525 }
1526 #endif
1527
1528 static GObject *
1529 osm_gps_map_constructor (GType gtype, guint n_properties, GObjectConstructParam *properties)
1530 {
1531 GObject *object;
1532 OsmGpsMapPrivate *priv;
1533 OsmGpsMap *map;
1534 const char *uri;
1535
1536 //Always chain up to the parent constructor
1537 object = G_OBJECT_CLASS(osm_gps_map_parent_class)->constructor(gtype, n_properties, properties);
1538 map = OSM_GPS_MAP(object);
1539 priv = OSM_GPS_MAP_PRIVATE(object);
1540
1541 //user can specify a map source ID, or a repo URI as the map source
1542 uri = osm_gps_map_source_get_repo_uri(OSM_GPS_MAP_SOURCE_NULL);
1543 if ( (priv->map_source == 0) || (strcmp(priv->repo_uri, uri) == 0) ) {
1544 g_debug("Using null source");
1545 priv->map_source = OSM_GPS_MAP_SOURCE_NULL;
1546
1547 priv->null_tile = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 256, 256);
1548 gdk_pixbuf_fill(priv->null_tile, 0xcccccc00);
1549 }
1550 else if (priv->map_source >= 0) {
1551 //check if the source given is valid
1552 uri = osm_gps_map_source_get_repo_uri(priv->map_source);
1553 if (uri) {
1554 g_debug("Setting map source from ID");
1555 g_free(priv->repo_uri);
1556
1557 priv->repo_uri = g_strdup(uri);
1558 priv->image_format = g_strdup(
1559 osm_gps_map_source_get_image_format(priv->map_source));
1560 priv->max_zoom = osm_gps_map_source_get_max_zoom(priv->map_source);
1561 priv->min_zoom = osm_gps_map_source_get_min_zoom(priv->map_source);
1562 }
1563 }
1564
1565 if (!priv->cache_dir_is_full_path) {
1566 #ifdef G_CHECKSUM_MD5
1567 char *md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, priv->repo_uri, -1);
1568 #else
1569 char *md5 = simple_hash(priv->repo_uri);
1570 #endif
1571
1572 if (priv->cache_dir) {
1573 char *old = priv->cache_dir;
1574 //the new cachedir is the given cache dir + the md5 of the repo_uri
1575 priv->cache_dir = g_strdup_printf("%s%c%s", old, G_DIR_SEPARATOR, md5);
1576 g_debug("Adjusting cache dir %s -> %s", old, priv->cache_dir);
1577 g_free(old);
1578 } else {
1579 //the new cachedir is the current dir + the md5 of the repo_uri
1580 priv->cache_dir = g_strdup(md5);
1581 }
1582
1583 g_free(md5);
1584 }
1585
1586 inspect_map_uri(map);
1587
1588 return object;
1589 }
1590
1591 static void
1592 osm_gps_map_dispose (GObject *object)
1593 {
1594 OsmGpsMap *map = OSM_GPS_MAP(object);
1595 OsmGpsMapPrivate *priv = map->priv;
1596
1597 if (priv->is_disposed)
1598 return;
1599
1600 priv->is_disposed = TRUE;
1601
1602 soup_session_abort(priv->soup_session);
1603 g_object_unref(priv->soup_session);
1604
1605 g_hash_table_destroy(priv->tile_queue);
1606 g_hash_table_destroy(priv->missing_tiles);
1607 g_hash_table_destroy(priv->tile_cache);
1608
1609 osm_gps_map_free_images(map);
1610
1611 if(priv->pixmap)
1612 g_object_unref (priv->pixmap);
1613
1614 if (priv->null_tile)
1615 g_object_unref (priv->null_tile);
1616
1617 if(priv->gc_map)
1618 g_object_unref(priv->gc_map);
1619
1620 if (priv->idle_map_redraw != 0)
1621 g_source_remove (priv->idle_map_redraw);
1622
1623 g_free(priv->gps);
1624 g_free(priv->balloon.coo);
1625
1626 G_OBJECT_CLASS (osm_gps_map_parent_class)->dispose (object);
1627 }
1628
1629 static void
1630 osm_gps_map_finalize (GObject *object)
1631 {
1632 OsmGpsMap *map = OSM_GPS_MAP(object);
1633 OsmGpsMapPrivate *priv = map->priv;
1634
1635 g_free(priv->cache_dir);
1636 g_free(priv->repo_uri);
1637 g_free(priv->image_format);
1638
1639 osm_gps_map_free_trip(map);
1640 osm_gps_map_free_tracks(map);
1641
1642 G_OBJECT_CLASS (osm_gps_map_parent_class)->finalize (object);
1643 }
1644
1645 static void
1646 osm_gps_map_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
1647 {
1648 g_return_if_fail (OSM_IS_GPS_MAP (object));
1649 OsmGpsMap *map = OSM_GPS_MAP(object);
1650 OsmGpsMapPrivate *priv = map->priv;
1651
1652 switch (prop_id)
1653 {
1654 case PROP_AUTO_CENTER:
1655 priv->map_auto_center = g_value_get_boolean (value);
1656 break;
1657 case PROP_RECORD_TRIP_HISTORY:
1658 priv->record_trip_history = g_value_get_boolean (value);
1659 break;
1660 case PROP_SHOW_TRIP_HISTORY:
1661 priv->show_trip_history = g_value_get_boolean (value);
1662 break;
1663 case PROP_AUTO_DOWNLOAD:
1664 priv->map_auto_download = g_value_get_boolean (value);
1665 break;
1666 case PROP_REPO_URI:
1667 priv->repo_uri = g_value_dup_string (value);
1668 break;
1669 case PROP_PROXY_URI:
1670 if ( g_value_get_string(value) ) {
1671 priv->proxy_uri = g_value_dup_string (value);
1672 g_debug("Setting proxy server: %s", priv->proxy_uri);
1673
1674 #ifndef LIBSOUP22
1675 GValue val = {0};
1676
1677 SoupURI* uri = soup_uri_new(priv->proxy_uri);
1678 g_value_init(&val, SOUP_TYPE_URI);
1679 g_value_take_boxed(&val, uri);
1680
1681 g_object_set_property(G_OBJECT(priv->soup_session),SOUP_SESSION_PROXY_URI,&val);
1682 #else
1683 SoupUri* uri = soup_uri_new(priv->proxy_uri);
1684 g_object_set(G_OBJECT(priv->soup_session), SOUP_SESSION_PROXY_URI, uri, NULL);
1685 #endif
1686 } else
1687 priv->proxy_uri = NULL;
1688
1689 break;
1690 case PROP_TILE_CACHE_DIR:
1691 if ( g_value_get_string(value) )
1692 priv->cache_dir = g_value_dup_string (value);
1693 break;
1694 case PROP_TILE_CACHE_DIR_IS_FULL_PATH:
1695 priv->cache_dir_is_full_path = g_value_get_boolean (value);
1696 break;
1697 case PROP_ZOOM:
1698 priv->map_zoom = g_value_get_int (value);
1699 break;
1700 case PROP_MAX_ZOOM:
1701 priv->max_zoom = g_value_get_int (value);
1702 break;
1703 case PROP_MIN_ZOOM:
1704 priv->min_zoom = g_value_get_int (value);
1705 break;
1706 case PROP_MAP_X:
1707 priv->map_x = g_value_get_int (value);
1708 priv->center_coord_set = FALSE;
1709 break;
1710 case PROP_MAP_Y:
1711 priv->map_y = g_value_get_int (value);
1712 priv->center_coord_set = FALSE;
1713 break;
1714 case PROP_GPS_TRACK_WIDTH:
1715 priv->ui_gps_track_width = g_value_get_int (value);
1716 break;
1717 case PROP_GPS_POINT_R1:
1718 priv->ui_gps_point_inner_radius = g_value_get_int (value);
1719 break;
1720 case PROP_GPS_POINT_R2:
1721 priv->ui_gps_point_outer_radius = g_value_get_int (value);
1722 break;
1723 case PROP_MAP_SOURCE:
1724 priv->map_source = g_value_get_int (value);
1725 break;
1726 case PROP_IMAGE_FORMAT:
1727 priv->image_format = g_value_dup_string (value);
1728 break;
1729 default:
1730 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1731 break;
1732 }
1733 }
1734
1735 static void
1736 osm_gps_map_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
1737 {
1738 g_return_if_fail (OSM_IS_GPS_MAP (object));
1739 float lat,lon;
1740 OsmGpsMap *map = OSM_GPS_MAP(object);
1741 OsmGpsMapPrivate *priv = map->priv;
1742
1743 switch (prop_id)
1744 {
1745 case PROP_AUTO_CENTER:
1746 g_value_set_boolean(value, priv->map_auto_center);
1747 break;
1748 case PROP_RECORD_TRIP_HISTORY:
1749 g_value_set_boolean(value, priv->record_trip_history);
1750 break;
1751 case PROP_SHOW_TRIP_HISTORY:
1752 g_value_set_boolean(value, priv->show_trip_history);
1753 break;
1754 case PROP_AUTO_DOWNLOAD:
1755 g_value_set_boolean(value, priv->map_auto_download);
1756 break;
1757 case PROP_REPO_URI:
1758 g_value_set_string(value, priv->repo_uri);
1759 break;
1760 case PROP_PROXY_URI:
1761 g_value_set_string(value, priv->proxy_uri);
1762 break;
1763 case PROP_TILE_CACHE_DIR:
1764 g_value_set_string(value, priv->cache_dir);
1765 break;
1766 case PROP_TILE_CACHE_DIR_IS_FULL_PATH:
1767 g_value_set_boolean(value, priv->cache_dir_is_full_path);
1768 break;
1769 case PROP_ZOOM:
1770 g_value_set_int(value, priv->map_zoom);
1771 break;
1772 case PROP_MAX_ZOOM:
1773 g_value_set_int(value, priv->max_zoom);
1774 break;
1775 case PROP_MIN_ZOOM:
1776 g_value_set_int(value, priv->min_zoom);
1777 break;
1778 case PROP_LATITUDE:
1779 lat = pixel2lat(priv->map_zoom,
1780 priv->map_y + (GTK_WIDGET(map)->allocation.height / 2));
1781 g_value_set_float(value, rad2deg(lat));
1782 break;
1783 case PROP_LONGITUDE:
1784 lon = pixel2lon(priv->map_zoom,
1785 priv->map_x + (GTK_WIDGET(map)->allocation.width / 2));
1786 g_value_set_float(value, rad2deg(lon));
1787 break;
1788 case PROP_MAP_X:
1789 g_value_set_int(value, priv->map_x);
1790 break;
1791 case PROP_MAP_Y:
1792 g_value_set_int(value, priv->map_y);
1793 break;
1794 case PROP_TILES_QUEUED:
1795 g_value_set_int(value, g_hash_table_size(priv->tile_queue));
1796 break;
1797 case PROP_GPS_TRACK_WIDTH:
1798 g_value_set_int(value, priv->ui_gps_track_width);
1799 break;
1800 case PROP_GPS_POINT_R1:
1801 g_value_set_int(value, priv->ui_gps_point_inner_radius);
1802 break;
1803 case PROP_GPS_POINT_R2:
1804 g_value_set_int(value, priv->ui_gps_point_outer_radius);
1805 break;
1806 case PROP_MAP_SOURCE:
1807 g_value_set_int(value, priv->map_source);
1808 break;
1809 case PROP_IMAGE_FORMAT:
1810 g_value_set_string(value, priv->image_format);
1811 break;
1812 default:
1813 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1814 break;
1815 }
1816 }
1817
1818 static gboolean
1819 osm_gps_map_scroll_event (GtkWidget *widget, GdkEventScroll *event)
1820 {
1821 OsmGpsMap *map = OSM_GPS_MAP(widget);
1822 OsmGpsMapPrivate *priv = map->priv;
1823
1824 if (event->direction == GDK_SCROLL_UP)
1825 {
1826 osm_gps_map_set_zoom(map, priv->map_zoom+1);
1827 }
1828 else
1829 {
1830 osm_gps_map_set_zoom(map, priv->map_zoom-1);
1831 }
1832
1833 return FALSE;
1834 }
1835
1836 static gboolean
1837 osm_gps_map_button_press (GtkWidget *widget, GdkEventButton *event)
1838 {
1839 OsmGpsMapPrivate *priv = OSM_GPS_MAP_PRIVATE(widget);
1840
1841 /* don't drag if the user clicked within the balloon */
1842 if (osm_gps_map_in_balloon(priv,
1843 event->x + EXTRA_BORDER,
1844 event->y + EXTRA_BORDER))
1845 {
1846 priv->drag_counter = -1;
1847 return FALSE;
1848 }
1849
1850 priv->drag_counter = 0;
1851 priv->drag_start_mouse_x = (int) event->x;
1852 priv->drag_start_mouse_y = (int) event->y;
1853 priv->drag_start_map_x = priv->map_x;
1854 priv->drag_start_map_y = priv->map_y;
1855
1856 return FALSE;
1857 }
1858
1859 static gboolean
1860 osm_gps_map_button_release (GtkWidget *widget, GdkEventButton *event)
1861 {
1862 OsmGpsMapPrivate *priv = OSM_GPS_MAP_PRIVATE(widget);
1863
1864 /* released inside the balloon? */
1865 if (osm_gps_map_in_balloon(priv,
1866 event->x + EXTRA_BORDER,
1867 event->y + EXTRA_BORDER))
1868 {
1869 osm_gps_map_handle_balloon_click(OSM_GPS_MAP(widget),
1870 event->x - priv->balloon.rect.x + EXTRA_BORDER,
1871 event->y - priv->balloon.rect.y + EXTRA_BORDER);
1872 return FALSE;
1873 }
1874
1875 if (priv->drag_counter < 0)
1876 return FALSE;
1877
1878 if (priv->dragging)
1879 {
1880 priv->dragging = FALSE;
1881
1882 priv->map_x = priv->drag_start_map_x;
1883 priv->map_y = priv->drag_start_map_y;
1884
1885 priv->map_x += (priv->drag_start_mouse_x - (int) event->x);
1886 priv->map_y += (priv->drag_start_mouse_y - (int) event->y);
1887
1888 priv->center_coord_set = FALSE;
1889
1890 osm_gps_map_map_redraw_idle(OSM_GPS_MAP(widget));
1891 }
1892
1893 priv->drag_mouse_dx = 0;
1894 priv->drag_mouse_dy = 0;
1895 priv->drag_counter = -1;
1896
1897 return FALSE;
1898 }
1899
1900 static gboolean
1901 osm_gps_map_motion_notify (GtkWidget *widget, GdkEventMotion *event)
1902 {
1903 int x, y;
1904 GdkModifierType state;
1905 OsmGpsMapPrivate *priv = OSM_GPS_MAP_PRIVATE(widget);
1906
1907 if (event->is_hint)
1908 gdk_window_get_pointer (event->window, &x, &y, &state);
1909 else
1910 {
1911 x = event->x;
1912 y = event->y;
1913 state = event->state;
1914 }
1915
1916 // are we being dragged
1917 if (!(state & GDK_BUTTON1_MASK))
1918 return FALSE;
1919
1920 if (priv->drag_counter < 0)
1921 return FALSE;
1922
1923 priv->drag_counter++;
1924
1925 // we havent dragged more than 6 pixels
1926 if (priv->drag_counter < 6)
1927 return FALSE;
1928
1929 priv->dragging = TRUE;
1930
1931 if (priv->map_auto_center)
1932 g_object_set(G_OBJECT(widget), "auto-center", FALSE, NULL);
1933
1934 priv->drag_mouse_dx = x - priv->drag_start_mouse_x;
1935 priv->drag_mouse_dy = y - priv->drag_start_mouse_y;
1936
1937 gdk_draw_drawable (
1938 widget->window,
1939 widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
1940 priv->pixmap,
1941 0,0,
1942 priv->drag_mouse_dx - EXTRA_BORDER, priv->drag_mouse_dy - EXTRA_BORDER,
1943 -1,-1);
1944
1945 //Paint white outside of the map if dragging. Its less
1946 //ugly than painting the corrupted map
1947 if(priv->drag_mouse_dx>EXTRA_BORDER) {
1948 gdk_draw_rectangle (
1949 widget->window,
1950 widget->style->white_gc,
1951 TRUE,
1952 0, 0,
1953 priv->drag_mouse_dx - EXTRA_BORDER,
1954 widget->allocation.height);
1955 }
1956 else if (-priv->drag_mouse_dx > EXTRA_BORDER)
1957 {
1958 gdk_draw_rectangle (
1959 widget->window,
1960 widget->style->white_gc,
1961 TRUE,
1962 priv->drag_mouse_dx + widget->allocation.width + EXTRA_BORDER, 0,
1963 -priv->drag_mouse_dx - EXTRA_BORDER,
1964 widget->allocation.height);
1965 }
1966
1967 if (priv->drag_mouse_dy>EXTRA_BORDER) {
1968 gdk_draw_rectangle (
1969 widget->window,
1970 widget->style->white_gc,
1971 TRUE,
1972 0, 0,
1973 widget->allocation.width,
1974 priv->drag_mouse_dy - EXTRA_BORDER);
1975 }
1976 else if (-priv->drag_mouse_dy > EXTRA_BORDER)
1977 {
1978 gdk_draw_rectangle (
1979 widget->window,
1980 widget->style->white_gc,
1981 TRUE,
1982 0, priv->drag_mouse_dy + widget->allocation.height + EXTRA_BORDER,
1983 widget->allocation.width,
1984 -priv->drag_mouse_dy - EXTRA_BORDER);
1985 }
1986
1987 return FALSE;
1988 }
1989
1990 static gboolean
1991 osm_gps_map_configure (GtkWidget *widget, GdkEventConfigure *event)
1992 {
1993 OsmGpsMapPrivate *priv = OSM_GPS_MAP_PRIVATE(widget);
1994
1995 /* create pixmap */
1996 if (priv->pixmap)
1997 g_object_unref (priv->pixmap);
1998
1999 priv->pixmap = gdk_pixmap_new (
2000 widget->window,
2001 widget->allocation.width + EXTRA_BORDER * 2,
2002 widget->allocation.height + EXTRA_BORDER * 2,
2003 -1);
2004
2005 /* and gc, used for clipping (I think......) */
2006 if(priv->gc_map)
2007 g_object_unref(priv->gc_map);
2008
2009 priv->gc_map = gdk_gc_new(priv->pixmap);
2010
2011 osm_gps_map_map_redraw(OSM_GPS_MAP(widget));
2012
2013 return FALSE;
2014 }
2015
2016 static gboolean
2017 osm_gps_map_expose (GtkWidget *widget, GdkEventExpose *event)
2018 {
2019 OsmGpsMapPrivate *priv = OSM_GPS_MAP_PRIVATE(widget);
2020
2021 gdk_draw_drawable (
2022 widget->window,
2023 widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
2024 priv->pixmap,
2025 event->area.x + EXTRA_BORDER,
2026 event->area.y + EXTRA_BORDER,
2027 event->area.x, event->area.y,
2028 event->area.width, event->area.height);
2029
2030 return FALSE;
2031 }
2032
2033 static void
2034 osm_gps_map_class_init (OsmGpsMapClass *klass)
2035 {
2036 GObjectClass* object_class = G_OBJECT_CLASS (klass);
2037 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2038
2039 g_type_class_add_private (klass, sizeof (OsmGpsMapPrivate));
2040
2041 object_class->dispose = osm_gps_map_dispose;
2042 object_class->finalize = osm_gps_map_finalize;
2043 object_class->constructor = osm_gps_map_constructor;
2044 object_class->set_property = osm_gps_map_set_property;
2045 object_class->get_property = osm_gps_map_get_property;
2046
2047 widget_class->expose_event = osm_gps_map_expose;
2048 widget_class->configure_event = osm_gps_map_configure;
2049 widget_class->button_press_event = osm_gps_map_button_press;
2050 widget_class->button_release_event = osm_gps_map_button_release;
2051 widget_class->motion_notify_event = osm_gps_map_motion_notify;
2052 widget_class->scroll_event = osm_gps_map_scroll_event;
2053
2054 g_object_class_install_property (object_class,
2055 PROP_AUTO_CENTER,
2056 g_param_spec_boolean ("auto-center",
2057 "auto center",
2058 "map auto center",
2059 TRUE,
2060 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2061
2062 g_object_class_install_property (object_class,
2063 PROP_RECORD_TRIP_HISTORY,
2064 g_param_spec_boolean ("record-trip-history",
2065 "record trip history",
2066 "should all gps points be recorded in a trip history",
2067 TRUE,
2068 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2069
2070 g_object_class_install_property (object_class,
2071 PROP_SHOW_TRIP_HISTORY,
2072 g_param_spec_boolean ("show-trip-history",
2073 "show trip history",
2074 "should the recorded trip history be shown on the map",
2075 TRUE,
2076 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2077
2078 g_object_class_install_property (object_class,
2079 PROP_AUTO_DOWNLOAD,
2080 g_param_spec_boolean ("auto-download",
2081 "auto download",
2082 "map auto download",
2083 TRUE,
2084 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2085
2086 g_object_class_install_property (object_class,
2087 PROP_REPO_URI,
2088 g_param_spec_string ("repo-uri",
2089 "repo uri",
2090 "map source tile repository uri",
2091 OSM_REPO_URI,
2092 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2093
2094 g_object_class_install_property (object_class,
2095 PROP_PROXY_URI,
2096 g_param_spec_string ("proxy-uri",
2097 "proxy uri",
2098 "http proxy uri on NULL",
2099 NULL,
2100 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2101
2102 g_object_class_install_property (object_class,
2103 PROP_TILE_CACHE_DIR,
2104 g_param_spec_string ("tile-cache",
2105 "tile cache",
2106 "osm local tile cache dir",
2107 NULL,
2108 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2109
2110 g_object_class_install_property (object_class,
2111 PROP_TILE_CACHE_DIR_IS_FULL_PATH,
2112 g_param_spec_boolean ("tile-cache-is-full-path",
2113 "tile cache is full path",
2114 "if true, the path passed to tile-cache is interpreted as the full cache path",
2115 FALSE,
2116 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2117
2118 g_object_class_install_property (object_class,
2119 PROP_ZOOM,
2120 g_param_spec_int ("zoom",
2121 "zoom",
2122 "zoom level",
2123 MIN_ZOOM, /* minimum property value */
2124 MAX_ZOOM, /* maximum property value */
2125 3,
2126 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2127
2128 g_object_class_install_property (object_class,
2129 PROP_MAX_ZOOM,
2130 g_param_spec_int ("max-zoom",
2131 "max zoom",
2132 "maximum zoom level",
2133 MIN_ZOOM, /* minimum property value */
2134 MAX_ZOOM, /* maximum property value */
2135 OSM_MAX_ZOOM,
2136 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2137
2138 g_object_class_install_property (object_class,
2139 PROP_MIN_ZOOM,
2140 g_param_spec_int ("min-zoom",
2141 "min zoom",
2142 "minimum zoom level",
2143 MIN_ZOOM, /* minimum property value */
2144 MAX_ZOOM, /* maximum property value */
2145 OSM_MIN_ZOOM,
2146 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2147
2148 g_object_class_install_property (object_class,
2149 PROP_LATITUDE,
2150 g_param_spec_float ("latitude",
2151 "latitude",
2152 "latitude in degrees",
2153 -90.0, /* minimum property value */
2154 90.0, /* maximum property value */
2155 0,
2156 G_PARAM_READABLE));
2157
2158 g_object_class_install_property (object_class,
2159 PROP_LONGITUDE,
2160 g_param_spec_float ("longitude",
2161 "longitude",
2162 "longitude in degrees",
2163 -180.0, /* minimum property value */
2164 180.0, /* maximum property value */
2165 0,
2166 G_PARAM_READABLE));
2167
2168 g_object_class_install_property (object_class,
2169 PROP_MAP_X,
2170 g_param_spec_int ("map-x",
2171 "map-x",
2172 "initial map x location",
2173 G_MININT, /* minimum property value */
2174 G_MAXINT, /* maximum property value */
2175 890,
2176 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2177
2178 g_object_class_install_property (object_class,
2179 PROP_MAP_Y,
2180 g_param_spec_int ("map-y",
2181 "map-y",
2182 "initial map y location",
2183 G_MININT, /* minimum property value */
2184 G_MAXINT, /* maximum property value */
2185 515,
2186 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2187
2188 g_object_class_install_property (object_class,
2189 PROP_TILES_QUEUED,
2190 g_param_spec_int ("tiles-queued",
2191 "tiles-queued",
2192 "number of tiles currently waiting to download",
2193 G_MININT, /* minimum property value */
2194 G_MAXINT, /* maximum property value */
2195 0,
2196 G_PARAM_READABLE));
2197
2198 g_object_class_install_property (object_class,
2199 PROP_GPS_TRACK_WIDTH,
2200 g_param_spec_int ("gps-track-width",
2201 "gps-track-width",
2202 "width of the lines drawn for the gps track",
2203 1, /* minimum property value */
2204 G_MAXINT, /* maximum property value */
2205 4,
2206 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2207
2208 g_object_class_install_property (object_class,
2209 PROP_GPS_POINT_R1,
2210 g_param_spec_int ("gps-track-point-radius",
2211 "gps-track-point-radius",
2212 "radius of the gps point inner circle",
2213 0, /* minimum property value */
2214 G_MAXINT, /* maximum property value */
2215 5,
2216 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2217
2218 g_object_class_install_property (object_class,
2219 PROP_GPS_POINT_R2,
2220 g_param_spec_int ("gps-track-highlight-radius",
2221 "gps-track-highlight-radius",
2222 "radius of the gps point highlight circle",
2223 0, /* minimum property value */
2224 G_MAXINT, /* maximum property value */
2225 20,
2226 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2227
2228 g_object_class_install_property (object_class,
2229 PROP_MAP_SOURCE,
2230 g_param_spec_int ("map-source",
2231 "map source",
2232 "map source ID",
2233 -1, /* minimum property value */
2234 G_MAXINT, /* maximum property value */
2235 -1,
2236 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2237
2238 g_object_class_install_property (object_class,
2239 PROP_IMAGE_FORMAT,
2240 g_param_spec_string ("image-format",
2241 "image format",
2242 "map source tile repository image format (jpg, png)",
2243 OSM_IMAGE_FORMAT,
2244 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2245 }
2246
2247 const char*
2248 osm_gps_map_source_get_friendly_name(OsmGpsMapSource_t source)
2249 {
2250 switch(source)
2251 {
2252 case OSM_GPS_MAP_SOURCE_NULL:
2253 return "None";
2254 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP:
2255 return "OpenStreetMap";
2256 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP_RENDERER:
2257 return "OpenStreetMap Renderer";
2258 case OSM_GPS_MAP_SOURCE_OPENAERIALMAP:
2259 return "OpenAerialMap";
2260 case OSM_GPS_MAP_SOURCE_MAPS_FOR_FREE:
2261 return "Maps-For-Free";
2262 case OSM_GPS_MAP_SOURCE_GOOGLE_STREET:
2263 return "Google Maps";
2264 case OSM_GPS_MAP_SOURCE_GOOGLE_SATELLITE:
2265 return "Google Satellite";
2266 case OSM_GPS_MAP_SOURCE_GOOGLE_HYBRID:
2267 return "Google Hybrid";
2268 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_STREET:
2269 return "Virtual Earth";
2270 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_SATELLITE:
2271 return "Virtual Earth Satellite";
2272 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_HYBRID:
2273 return "Virtual Earth Hybrid";
2274 case OSM_GPS_MAP_SOURCE_YAHOO_STREET:
2275 return "Yahoo Maps";
2276 case OSM_GPS_MAP_SOURCE_YAHOO_SATELLITE:
2277 return "Yahoo Satellite";
2278 case OSM_GPS_MAP_SOURCE_YAHOO_HYBRID:
2279 return "Yahoo Hybrid";
2280 default:
2281 return NULL;
2282 }
2283 return NULL;
2284 }
2285
2286 //http://www.internettablettalk.com/forums/showthread.php?t=5209
2287 //https://garage.maemo.org/plugins/scmsvn/viewcvs.php/trunk/src/maps.c?root=maemo-mapper&view=markup
2288 //http://www.ponies.me.uk/maps/GoogleTileUtils.java
2289 //http://www.mgmaps.com/cache/MapTileCacher.perl
2290 const char*
2291 osm_gps_map_source_get_repo_uri(OsmGpsMapSource_t source)
2292 {
2293 switch(source)
2294 {
2295 case OSM_GPS_MAP_SOURCE_NULL:
2296 return "none://";
2297 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP:
2298 return OSM_REPO_URI;
2299 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP_RENDERER:
2300 return "http://tah.openstreetmap.org/Tiles/tile/#Z/#X/#Y.png";
2301 case OSM_GPS_MAP_SOURCE_OPENAERIALMAP:
2302 return "http://tile.openaerialmap.org/tiles/1.0.0/openaerialmap-900913/#Z/#X/#Y.jpg";
2303 case OSM_GPS_MAP_SOURCE_MAPS_FOR_FREE:
2304 return "http://maps-for-free.com/layer/relief/z#Z/row#Y/#Z_#X-#Y.jpg";
2305 case OSM_GPS_MAP_SOURCE_GOOGLE_STREET:
2306 return "http://mt#R.google.com/vt/v=w2.97&x=#X&y=#Y&z=#Z";
2307 case OSM_GPS_MAP_SOURCE_GOOGLE_SATELLITE:
2308 return "http://khm#R.google.com/kh?n=404&v=3&t=#Q";
2309 case OSM_GPS_MAP_SOURCE_GOOGLE_HYBRID:
2310 return NULL; /* No longer working "http://mt#R.google.com/mt?n=404&v=w2t.99&x=#X&y=#Y&zoom=#S" */
2311 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_STREET:
2312 return "http://a#R.ortho.tiles.virtualearth.net/tiles/r#W.jpeg?g=50";
2313 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_SATELLITE:
2314 return "http://a#R.ortho.tiles.virtualearth.net/tiles/a#W.jpeg?g=50";
2315 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_HYBRID:
2316 return "http://a#R.ortho.tiles.virtualearth.net/tiles/h#W.jpeg?g=50";
2317 case OSM_GPS_MAP_SOURCE_YAHOO_STREET:
2318 case OSM_GPS_MAP_SOURCE_YAHOO_SATELLITE:
2319 case OSM_GPS_MAP_SOURCE_YAHOO_HYBRID:
2320 /* TODO: Implement signed Y, aka U
2321 * http://us.maps3.yimg.com/aerial.maps.yimg.com/ximg?v=1.7&t=a&s=256&x=%d&y=%-d&z=%d
2322 * x = tilex,
2323 * y = (1 << (MAX_ZOOM - zoom)) - tiley - 1,
2324 * z = zoom - (MAX_ZOOM - 17));
2325 */
2326 return NULL;
2327 default:
2328 return NULL;
2329 }
2330 return NULL;
2331 }
2332
2333 const char *
2334 osm_gps_map_source_get_image_format(OsmGpsMapSource_t source)
2335 {
2336 switch(source) {
2337 case OSM_GPS_MAP_SOURCE_NULL:
2338 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP:
2339 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP_RENDERER:
2340 return "png";
2341 case OSM_GPS_MAP_SOURCE_OPENAERIALMAP:
2342 case OSM_GPS_MAP_SOURCE_GOOGLE_STREET:
2343 case OSM_GPS_MAP_SOURCE_GOOGLE_HYBRID:
2344 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_STREET:
2345 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_SATELLITE:
2346 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_HYBRID:
2347 case OSM_GPS_MAP_SOURCE_YAHOO_STREET:
2348 case OSM_GPS_MAP_SOURCE_YAHOO_SATELLITE:
2349 case OSM_GPS_MAP_SOURCE_YAHOO_HYBRID:
2350 case OSM_GPS_MAP_SOURCE_MAPS_FOR_FREE:
2351 case OSM_GPS_MAP_SOURCE_GOOGLE_SATELLITE:
2352 return "jpg";
2353 default:
2354 return "bin";
2355 }
2356 return "bin";
2357 }
2358
2359
2360 int
2361 osm_gps_map_source_get_min_zoom(OsmGpsMapSource_t source)
2362 {
2363 return 1;
2364 }
2365
2366 int
2367 osm_gps_map_source_get_max_zoom(OsmGpsMapSource_t source)
2368 {
2369 switch(source) {
2370 case OSM_GPS_MAP_SOURCE_NULL:
2371 return 18;
2372 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP:
2373 return OSM_MAX_ZOOM;
2374 case OSM_GPS_MAP_SOURCE_OPENSTREETMAP_RENDERER:
2375 case OSM_GPS_MAP_SOURCE_OPENAERIALMAP:
2376 case OSM_GPS_MAP_SOURCE_GOOGLE_STREET:
2377 case OSM_GPS_MAP_SOURCE_GOOGLE_HYBRID:
2378 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_STREET:
2379 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_SATELLITE:
2380 case OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_HYBRID:
2381 case OSM_GPS_MAP_SOURCE_YAHOO_STREET:
2382 case OSM_GPS_MAP_SOURCE_YAHOO_SATELLITE:
2383 case OSM_GPS_MAP_SOURCE_YAHOO_HYBRID:
2384 return 17;
2385 case OSM_GPS_MAP_SOURCE_MAPS_FOR_FREE:
2386 return 11;
2387 case OSM_GPS_MAP_SOURCE_GOOGLE_SATELLITE:
2388 return 18;
2389 default:
2390 return 17;
2391 }
2392 return 17;
2393 }
2394
2395 void
2396 osm_gps_map_download_maps (OsmGpsMap *map, coord_t *pt1, coord_t *pt2, int zoom_start, int zoom_end)
2397 {
2398 int i,j,zoom,num_tiles;
2399 OsmGpsMapPrivate *priv = map->priv;
2400
2401 if (pt1 && pt2)
2402 {
2403 gchar *filename;
2404 num_tiles = 0;
2405 zoom_end = CLAMP(zoom_end, priv->min_zoom, priv->max_zoom);
2406 g_debug("Download maps: z:%d->%d",zoom_start, zoom_end);
2407
2408 for(zoom=zoom_start; zoom<=zoom_end; zoom++)
2409 {
2410 int x1,y1,x2,y2;
2411
2412 x1 = (int)floor((float)lon2pixel(zoom, pt1->rlon) / (float)TILESIZE);
2413 y1 = (int)floor((float)lat2pixel(zoom, pt1->rlat) / (float)TILESIZE);
2414
2415 x2 = (int)floor((float)lon2pixel(zoom, pt2->rlon) / (float)TILESIZE);
2416 y2 = (int)floor((float)lat2pixel(zoom, pt2->rlat) / (float)TILESIZE);
2417
2418 // loop x1-x2
2419 for(i=x1; i<=x2; i++)
2420 {
2421 // loop y1 - y2
2422 for(j=y1; j<=y2; j++)
2423 {
2424 // x = i, y = j
2425 filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
2426 priv->cache_dir, G_DIR_SEPARATOR,
2427 zoom, G_DIR_SEPARATOR,
2428 i, G_DIR_SEPARATOR,
2429 j,
2430 priv->image_format);
2431 if (!g_file_test(filename, G_FILE_TEST_EXISTS))
2432 {
2433 osm_gps_map_download_tile(map, zoom, i, j, FALSE);
2434 num_tiles++;
2435 }
2436 g_free(filename);
2437 }
2438 }
2439 g_debug("DL @Z:%d = %d tiles",zoom,num_tiles);
2440 }
2441 }
2442 }
2443
2444 void
2445 osm_gps_map_get_bbox (OsmGpsMap *map, coord_t *pt1, coord_t *pt2)
2446 {
2447 OsmGpsMapPrivate *priv = map->priv;
2448
2449 if (pt1 && pt2) {
2450 pt1->rlat = pixel2lat(priv->map_zoom, priv->map_y);
2451 pt1->rlon = pixel2lon(priv->map_zoom, priv->map_x);
2452 pt2->rlat = pixel2lat(priv->map_zoom, priv->map_y + GTK_WIDGET(map)->allocation.height);
2453 pt2->rlon = pixel2lon(priv->map_zoom, priv->map_x + GTK_WIDGET(map)->allocation.width);
2454
2455 g_debug("BBOX: %f %f %f %f", pt1->rlat, pt1->rlon, pt2->rlat, pt2->rlon);
2456 }
2457 }
2458
2459 void
2460 osm_gps_map_set_mapcenter (OsmGpsMap *map, float latitude, float longitude, int zoom)
2461 {
2462 osm_gps_map_set_center (map, latitude, longitude);
2463 osm_gps_map_set_zoom (map, zoom);
2464 }
2465
2466 void
2467 osm_gps_map_set_center (OsmGpsMap *map, float latitude, float longitude)
2468 {
2469 int pixel_x, pixel_y;
2470 OsmGpsMapPrivate *priv;
2471
2472 g_return_if_fail (OSM_IS_GPS_MAP (map));
2473 priv = map->priv;
2474
2475 priv->center_rlat = deg2rad(latitude);
2476 priv->center_rlon = deg2rad(longitude);
2477 priv->center_coord_set = TRUE;
2478
2479 // pixel_x,y, offsets
2480 pixel_x = lon2pixel(priv->map_zoom, priv->center_rlon);
2481 pixel_y = lat2pixel(priv->map_zoom, priv->center_rlat);
2482
2483 priv->map_x = pixel_x - GTK_WIDGET(map)->allocation.width/2;
2484 priv->map_y = pixel_y - GTK_WIDGET(map)->allocation.height/2;
2485
2486 osm_gps_map_map_redraw_idle(map);
2487 }
2488
2489 int
2490 osm_gps_map_set_zoom (OsmGpsMap *map, int zoom)
2491 {
2492 int zoom_old;
2493 double factor = 0.0;
2494 int width_center, height_center;
2495 OsmGpsMapPrivate *priv;
2496
2497 g_return_val_if_fail (OSM_IS_GPS_MAP (map), 0);
2498 priv = map->priv;
2499
2500 if (zoom != priv->map_zoom)
2501 {
2502 width_center = GTK_WIDGET(map)->allocation.width / 2;
2503 height_center = GTK_WIDGET(map)->allocation.height / 2;
2504
2505 zoom_old = priv->map_zoom;
2506 //constrain zoom min_zoom -> max_zoom
2507 priv->map_zoom = CLAMP(zoom, priv->min_zoom, priv->max_zoom);
2508
2509 if (priv->center_coord_set)
2510 {
2511 priv->map_x = lon2pixel(priv->map_zoom, priv->center_rlon) - width_center;
2512 priv->map_y = lat2pixel(priv->map_zoom, priv->center_rlat) - height_center;
2513 }
2514 else
2515 {
2516 factor = exp(priv->map_zoom * M_LN2)/exp(zoom_old * M_LN2);
2517 priv->map_x = ((priv->map_x + width_center) * factor) - width_center;
2518 priv->map_y = ((priv->map_y + height_center) * factor) - height_center;
2519 }
2520
2521 g_debug("Zoom changed from %d to %d factor:%f x:%d",
2522 zoom_old, priv->map_zoom, factor, priv->map_x);
2523
2524 osm_gps_map_map_redraw_idle(map);
2525 }
2526 return priv->map_zoom;
2527 }
2528
2529 void
2530 osm_gps_map_add_track (OsmGpsMap *map, GSList *track)
2531 {
2532 OsmGpsMapPrivate *priv;
2533
2534 g_return_if_fail (OSM_IS_GPS_MAP (map));
2535 priv = map->priv;
2536
2537 if (track) {
2538 priv->tracks = g_slist_append(priv->tracks, track);
2539 osm_gps_map_map_redraw_idle(map);
2540 }
2541 }
2542
2543 void
2544 osm_gps_map_clear_tracks (OsmGpsMap *map)
2545 {
2546 g_return_if_fail (OSM_IS_GPS_MAP (map));
2547
2548 osm_gps_map_free_tracks(map);
2549 osm_gps_map_map_redraw_idle(map);
2550 }
2551
2552 void
2553 osm_gps_map_add_image (OsmGpsMap *map, float latitude, float longitude, GdkPixbuf *image)
2554 {
2555 g_return_if_fail (OSM_IS_GPS_MAP (map));
2556
2557 if (image) {
2558 OsmGpsMapPrivate *priv = map->priv;
2559 image_t *im;
2560
2561 //cache w/h for speed, and add image to list
2562 im = g_new0(image_t,1);
2563 im->w = gdk_pixbuf_get_width(image);
2564 im->h = gdk_pixbuf_get_height(image);
2565 im->pt.rlat = deg2rad(latitude);
2566 im->pt.rlon = deg2rad(longitude);
2567
2568 g_object_ref(image);
2569 im->image = image;
2570
2571 priv->images = g_slist_append(priv->images, im);
2572
2573 osm_gps_map_map_redraw_idle(map);
2574 }
2575 }
2576
2577 gboolean
2578 osm_gps_map_remove_image (OsmGpsMap *map, GdkPixbuf *image)
2579 {
2580 OsmGpsMapPrivate *priv = map->priv;
2581 if (priv->images) {
2582 GSList *list;
2583 for(list = priv->images; list != NULL; list = list->next)
2584 {
2585 image_t *im = list->data;
2586 if (im->image == image)
2587 {
2588 priv->images = g_slist_remove_link(priv->images, list);
2589 g_object_unref(im->image);
2590 g_free(im);
2591 osm_gps_map_map_redraw_idle(map);
2592 return TRUE;
2593 }
2594 }
2595 }
2596 return FALSE;
2597 }
2598
2599 void
2600 osm_gps_map_clear_images (OsmGpsMap *map)
2601 {
2602 g_return_if_fail (OSM_IS_GPS_MAP (map));
2603
2604 osm_gps_map_free_images(map);
2605 osm_gps_map_map_redraw_idle(map);
2606 }
2607
2608 void
2609 osm_gps_map_osd_speed (OsmGpsMap *map, float speed)
2610 {
2611 OsmGpsMapPrivate *priv;
2612
2613 PangoContext *context = NULL;
2614 PangoLayout *layout = NULL;
2615 PangoFontDescription *desc = NULL;
2616
2617 GdkColor color;
2618 GdkGC *gc;
2619
2620 gchar *buffer;
2621 //static int x = 10, y = 10;
2622 static int width = 0, height = 0;
2623
2624 g_return_if_fail (OSM_IS_GPS_MAP (map));
2625 priv = map->priv;
2626
2627 buffer = g_strdup_printf("%.0f", speed);
2628
2629 /* pango initialisation */
2630 context = gtk_widget_get_pango_context (GTK_WIDGET(map));
2631 layout = pango_layout_new (context);
2632 desc = pango_font_description_new();
2633
2634 pango_font_description_set_size (desc, 40 * PANGO_SCALE);
2635 pango_layout_set_font_description (layout, desc);
2636 pango_layout_set_text (layout, buffer, strlen(buffer));
2637
2638 gc = gdk_gc_new (GTK_WIDGET(map)->window);
2639
2640 color.red = (0 > 50) ? 0xffff : 0;
2641 color.green = 0;
2642 color.blue = 0;
2643
2644 gdk_gc_set_rgb_fg_color (gc, &color);
2645
2646 /* faster / less flicker alternative:*/
2647 gdk_draw_drawable (
2648 GTK_WIDGET(map)->window,
2649 GTK_WIDGET(map)->style->fg_gc[GTK_WIDGET_STATE(map)],
2650 priv->pixmap,
2651 0,0,
2652 0,0,
2653 width+10,width+10);
2654
2655 gdk_draw_layout(GTK_WIDGET(map)->window,
2656 gc,
2657 0, 0,
2658 layout);
2659
2660 /* set width and height */
2661 pango_layout_get_pixel_size(layout, &width, &height);
2662
2663 g_free(buffer);
2664 pango_font_description_free (desc);
2665 g_object_unref (layout);
2666 g_object_unref (gc);
2667 }
2668
2669 void
2670 osm_gps_map_draw_gps (OsmGpsMap *map, float latitude, float longitude, float heading)
2671 {
2672 int pixel_x, pixel_y;
2673 OsmGpsMapPrivate *priv;
2674
2675 g_return_if_fail (OSM_IS_GPS_MAP (map));
2676 priv = map->priv;
2677
2678 priv->gps->rlat = deg2rad(latitude);
2679 priv->gps->rlon = deg2rad(longitude);
2680 priv->gps_valid = TRUE;
2681
2682 // pixel_x,y, offsets
2683 pixel_x = lon2pixel(priv->map_zoom, priv->gps->rlon);
2684 pixel_y = lat2pixel(priv->map_zoom, priv->gps->rlat);
2685
2686 //If trip marker add to list of gps points.
2687 if (priv->record_trip_history) {
2688 coord_t *tp = g_new0(coord_t,1);
2689 tp->rlat = priv->gps->rlat;
2690 tp->rlon = priv->gps->rlon;
2691 priv->trip_history = g_slist_append(priv->trip_history, tp);
2692 }
2693
2694 // dont draw anything if we are dragging
2695 if (priv->dragging) {
2696 g_debug("Dragging");
2697 return;
2698 }
2699
2700 //Automatically center the map if the track approaches the edge
2701 if(priv->map_auto_center) {
2702 int x = pixel_x - priv->map_x;
2703 int y = pixel_y - priv->map_y;
2704 int width = GTK_WIDGET(map)->allocation.width;
2705 int height = GTK_WIDGET(map)->allocation.height;
2706 if( x < (width/2 - width/8) || x > (width/2 + width/8) ||
2707 y < (height/2 - height/8) || y > (height/2 + height/8)) {
2708
2709 priv->map_x = pixel_x - GTK_WIDGET(map)->allocation.width/2;
2710 priv->map_y = pixel_y - GTK_WIDGET(map)->allocation.height/2;
2711 priv->center_coord_set = FALSE;
2712 }
2713 }
2714
2715 // this redraws the map (including the gps track, and adjusts the
2716 // map center if it was changed
2717 osm_gps_map_map_redraw_idle(map);
2718 }
2719
2720 void
2721 osm_gps_map_clear_gps (OsmGpsMap *map)
2722 {
2723 osm_gps_map_free_trip(map);
2724 osm_gps_map_map_redraw_idle(map);
2725 }
2726
2727 coord_t
2728 osm_gps_map_get_co_ordinates (OsmGpsMap *map, int pixel_x, int pixel_y)
2729 {
2730 coord_t coord;
2731 OsmGpsMapPrivate *priv = map->priv;
2732
2733 coord.rlat = pixel2lat(priv->map_zoom, priv->map_y + pixel_y);
2734 coord.rlon = pixel2lon(priv->map_zoom, priv->map_x + pixel_x);
2735 return coord;
2736 }
2737
2738 GtkWidget *
2739 osm_gps_map_new (void)
2740 {
2741 return g_object_new (OSM_TYPE_GPS_MAP, NULL);
2742 }
2743
2744 void
2745 osm_gps_map_screen_to_geographic (OsmGpsMap *map, gint pixel_x, gint pixel_y,
2746 gfloat *latitude, gfloat *longitude)
2747 {
2748 OsmGpsMapPrivate *priv;
2749
2750 g_return_if_fail (OSM_IS_GPS_MAP (map));
2751 priv = map->priv;
2752
2753 if (latitude)
2754 *latitude = rad2deg(pixel2lat(priv->map_zoom, priv->map_y + pixel_y));
2755 if (longitude)
2756 *longitude = rad2deg(pixel2lon(priv->map_zoom, priv->map_x + pixel_x));
2757 }
2758
2759 void
2760 osm_gps_map_geographic_to_screen (OsmGpsMap *map,
2761 gfloat latitude, gfloat longitude,
2762 gint *pixel_x, gint *pixel_y)
2763 {
2764 OsmGpsMapPrivate *priv;
2765
2766 g_return_if_fail (OSM_IS_GPS_MAP (map));
2767 priv = map->priv;
2768
2769 if (pixel_x)
2770 *pixel_x = lon2pixel(priv->map_zoom, deg2rad(longitude)) - priv->map_x;
2771 if (pixel_y)
2772 *pixel_y = lat2pixel(priv->map_zoom, deg2rad(latitude)) - priv->map_y;
2773 }
2774
2775 void
2776 osm_gps_map_scroll (OsmGpsMap *map, gint dx, gint dy)
2777 {
2778 OsmGpsMapPrivate *priv;
2779
2780 g_return_if_fail (OSM_IS_GPS_MAP (map));
2781 priv = map->priv;
2782
2783 priv->center_coord_set = FALSE;
2784 priv->map_x += dx;
2785 priv->map_y += dy;
2786
2787 osm_gps_map_map_redraw_idle (map);
2788 }
2789
2790 float
2791 osm_gps_map_get_scale(OsmGpsMap *map)
2792 {
2793 OsmGpsMapPrivate *priv;
2794
2795 g_return_val_if_fail (OSM_IS_GPS_MAP (map), OSM_NAN);
2796 priv = map->priv;
2797
2798 return osm_gps_map_get_scale_at_point(priv->map_zoom, priv->center_rlat, priv->center_rlon);
2799 }
2800
2801 void
2802 osm_gps_map_draw_balloon (OsmGpsMap *map, float latitude, float longitude,
2803 OsmGpsMapBalloonCallback cb, gpointer data)
2804 {
2805 OsmGpsMapPrivate *priv;
2806
2807 /* remove and previously installed balloon */
2808 osm_gps_map_clear_balloon (map);
2809
2810 g_return_if_fail (OSM_IS_GPS_MAP (map));
2811 priv = map->priv;
2812
2813 priv->balloon.coo->rlat = deg2rad(latitude);
2814 priv->balloon.coo->rlon = deg2rad(longitude);
2815 priv->balloon.valid = TRUE;
2816
2817 priv->balloon.cb = cb;
2818 priv->balloon.data = data;
2819
2820 // this redraws the map
2821 osm_gps_map_map_redraw_idle(map);
2822 }
2823
2824 void
2825 osm_gps_map_clear_balloon (OsmGpsMap *map)
2826 {
2827 OsmGpsMapPrivate *priv;
2828
2829 g_return_if_fail (OSM_IS_GPS_MAP (map));
2830 priv = map->priv;
2831
2832 priv->balloon.valid = FALSE;
2833
2834 osm_gps_map_map_redraw_idle(map);
2835 }