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

Parent Directory Parent Directory | Revision Log Revision Log


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