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

Parent Directory Parent Directory | Revision Log Revision Log


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