1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */
2 /* vim:set et sw=4 ts=4 */
3 /*
4 * Copyright (C) 2013 John Stowers <john.stowers@gmail.com>
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 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <http://www.gnu.org/licenses/>.
24 */
25
26 /**
27 * SECTION:osm-gps-map
28 * @short_description: The map display widget
29 * @stability: Stable
30 * @include: osm-gps-map.h
31 *
32 * #OsmGpsMap is a widget for displaying a map, optionally overlaid with a
33 * track(s) of GPS co-ordinates, images, points of interest or on screen display
34 * controls. #OsmGpsMap downloads (and caches for offline use) map data from a
35 * number of websites, including
36 * <ulink url="http://www.openstreetmap.org"><citetitle>OpenStreetMap</citetitle></ulink>
37 *
38 * <example>
39 * <title>Showing a map</title>
40 * <programlisting>
41 * int main (int argc, char **argv)
42 * {
43 * g_thread_init(NULL);
44 * gtk_init (&argc, &argv);
45 *
46 * GtkWidget *map = osm_gps_map_new ();
47 * GtkWidget *w = gtk_window_new (GTK_WINDOW_TOPLEVEL);
48 * gtk_container_add (GTK_CONTAINER(w), map);
49 * gtk_widget_show_all (w);
50 *
51 * gtk_main ();
52 * return 0;
53 * }
54 * </programlisting>
55 * </example>
56 *
57 * #OsmGpsMap allows great flexibility in customizing how the map tiles are
58 * cached, see #OsmGpsMap:tile-cache-base and #OsmGpsMap:tile-cache for more
59 * information.
60 *
61 * A number of different map sources are supported, see #OsmGpsMapSource_t. The
62 * default source, %OSM_GPS_MAP_SOURCE_OPENSTREETMAP always works. Other sources,
63 * particular those from proprietary providers may work occasionally, and then
64 * cease to work. To check if a source is supported for the given version of
65 * this library, call osm_gps_map_source_is_valid().
66 *
67 * <example>
68 * <title>Map with custom source and cache dir</title>
69 * <programlisting>
70 * int main (int argc, char **argv)
71 * {
72 * g_thread_init(NULL);
73 * gtk_init (&argc, &argv);
74 * OsmGpsMapSource_t source = OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_SATELLITE;
75 *
76 * if ( !osm_gps_map_source_is_valid(source) )
77 * return 1;
78 *
79 * GtkWidget *map = g_object_new (OSM_TYPE_GPS_MAP,
80 * "map-source", source,
81 * "tile-cache", "/tmp/",
82 * NULL);
83 * GtkWidget *w = gtk_window_new (GTK_WINDOW_TOPLEVEL);
84 * gtk_container_add (GTK_CONTAINER(w), map);
85 * gtk_widget_show_all (w);
86 *
87 * gtk_main ();
88 * return 0;
89 * }
90 * </programlisting>
91 * </example>
92 *
93 * Finally, if you wish to use a custom map source not supported by #OsmGpsMap,
94 * such as a custom map created with
95 * <ulink url="http://www.cloudmade.com"><citetitle>CloudMade</citetitle></ulink>
96 * then you can also pass a specially formatted string to #OsmGpsMap:repo-uri.
97 *
98 * <example>
99 * <title>Map using custom CloudMade map and on screen display</title>
100 * <programlisting>
101 * int main (int argc, char **argv)
102 * {
103 * g_thread_init(NULL);
104 * gtk_init (&argc, &argv);
105 * const gchar *cloudmate = "http://a.tile.cloudmade.com/YOUR_API_KEY/1/256/#Z/#X/#Y.png";
106 *
107 * GtkWidget *map = g_object_new (OSM_TYPE_GPS_MAP,
108 * "repo-uri", cloudmate,
109 * NULL);
110 * OsmGpsMapOsd *osd = osm_gps_map_osd_new ();
111 * GtkWidget *w = gtk_window_new (GTK_WINDOW_TOPLEVEL);
112 * osm_gps_map_layer_add (OSM_GPS_MAP(map), OSM_GPS_MAP_LAYER(osd));
113 * gtk_container_add (GTK_CONTAINER(w), map);
114 * gtk_widget_show_all (w);
115 *
116 * gtk_main ();
117 * return 0;
118 * }
119 * </programlisting>
120 * </example>
121 **/
122
123 #include "config.h"
124
125 #include <fcntl.h>
126 #include <math.h>
127 #include <unistd.h>
128 #include <stdio.h>
129 #include <stdlib.h>
130 #include <string.h>
131
132 #include <gdk/gdk.h>
133
134 #include <glib.h>
135 #include <glib/gstdio.h>
136 #include <glib/gprintf.h>
137 #include <libsoup/soup.h>
138
139 #include "converter.h"
140 #include "private.h"
141 #include "osm-gps-map-source.h"
142 #include "osm-gps-map-widget.h"
143 #include "osm-gps-map-compat.h"
144
145 #define ENABLE_DEBUG (0)
146 #define EXTRA_BORDER (0)
147 #define OSM_GPS_MAP_SCROLL_STEP (10)
148 #define USER_AGENT "libosmgpsmap/1.0"
149 #define DOWNLOAD_RETRIES 3
150 #define MAX_DOWNLOAD_TILES 10000
151 #define DOT_RADIUS 4.0
152
153 struct _OsmGpsMapPrivate
154 {
155 GHashTable *tile_queue;
156 GHashTable *missing_tiles;
157 GHashTable *tile_cache;
158
159 int map_zoom;
160 int max_zoom;
161 int min_zoom;
162
163 int tile_zoom_offset;
164
165 int map_x;
166 int map_y;
167
168 /* Controls auto centering the map when a new GPS position arrives */
169 gfloat map_auto_center_threshold;
170
171 /* Latitude and longitude of the center of the map, in radians */
172 gfloat center_rlat;
173 gfloat center_rlon;
174
175 guint max_tile_cache_size;
176 /* Incremented at each redraw */
177 guint redraw_cycle;
178 /* ID of the idle redraw operation */
179 guint idle_map_redraw;
180
181 //how we download tiles
182 SoupSession *soup_session;
183 char *proxy_uri;
184
185 //where downloaded tiles are cached
186 char *tile_dir;
187 char *tile_base_dir;
188 char *cache_dir;
189
190 //contains flags indicating the various special characters
191 //the uri string contains, that will be replaced when calculating
192 //the uri to download.
193 OsmGpsMapSource_t map_source;
194 char *repo_uri;
195 char *image_format;
196 int uri_format;
197
198 //gps tracking state
199 GSList *trip_history;
200 float gps_heading;
201
202 OsmGpsMapPoint *gps;
203 OsmGpsMapTrack *gps_track;
204 gboolean gps_track_used;
205
206 //additional images or tracks added to the map
207 GSList *tracks;
208 GSList *images;
209 GSList *polygons;
210
211 //Used for storing the joined tiles
212 cairo_surface_t *pixmap;
213
214 //The tile painted when one cannot be found
215 GdkPixbuf *null_tile;
216
217 //A list of OsmGpsMapLayer* layers, such as the OSD
218 GSList *layers;
219
220 //For tracking click and drag
221 int drag_counter;
222 int drag_mouse_dx;
223 int drag_mouse_dy;
224 int drag_start_mouse_x;
225 int drag_start_mouse_y;
226 int drag_start_map_x;
227 int drag_start_map_y;
228 int drag_limit;
229 guint drag_expose_source;
230
231 /* Properties for dragging a point with right mouse button. */
232 OsmGpsMapPoint* drag_point;
233 OsmGpsMapTrack* drag_track;
234
235 /* for customizing the redering of the gps track */
236 int ui_gps_point_inner_radius;
237 int ui_gps_point_outer_radius;
238
239 /* For storing keybindings */
240 guint keybindings[OSM_GPS_MAP_KEY_MAX];
241
242 /* flags controlling which features are enabled */
243 guint keybindings_enabled : 1;
244 guint map_auto_download_enabled : 1;
245 guint map_auto_center_enabled : 1;
246 guint trip_history_record_enabled : 1;
247 guint trip_history_show_enabled : 1;
248 guint gps_point_enabled : 1;
249
250 /* state flags */
251 guint is_disposed : 1;
252 guint is_constructed : 1;
253 guint is_dragging : 1;
254 guint is_button_down : 1;
255 guint is_fullscreen : 1;
256 guint is_google : 1;
257 guint is_dragging_point : 1;
258 };
259
260 typedef struct
261 {
262 GdkPixbuf *pixbuf;
263 /* We keep track of the number of the redraw cycle this tile was last used,
264 * so that osm_gps_map_purge_cache() can remove the older ones */
265 guint redraw_cycle;
266 } OsmCachedTile;
267
268 typedef struct {
269 /* The details of the tile to download */
270 char *uri;
271 char *folder;
272 char *filename;
273 OsmGpsMap *map;
274 /* whether to redraw the map when the tile arrives */
275 gboolean redraw;
276 int ttl;
277 } OsmTileDownload;
278
279 enum
280 {
281 PROP_0,
282 PROP_AUTO_CENTER,
283 PROP_RECORD_TRIP_HISTORY,
284 PROP_SHOW_TRIP_HISTORY,
285 PROP_AUTO_DOWNLOAD,
286 PROP_REPO_URI,
287 PROP_PROXY_URI,
288 PROP_TILE_CACHE_DIR,
289 PROP_TILE_CACHE_BASE_DIR,
290 PROP_TILE_ZOOM_OFFSET,
291 PROP_ZOOM,
292 PROP_MAX_ZOOM,
293 PROP_MIN_ZOOM,
294 PROP_LATITUDE,
295 PROP_LONGITUDE,
296 PROP_MAP_X,
297 PROP_MAP_Y,
298 PROP_TILES_QUEUED,
299 PROP_GPS_TRACK_WIDTH,
300 PROP_GPS_POINT_R1,
301 PROP_GPS_POINT_R2,
302 PROP_MAP_SOURCE,
303 PROP_IMAGE_FORMAT,
304 PROP_DRAG_LIMIT,
305 PROP_AUTO_CENTER_THRESHOLD,
306 PROP_SHOW_GPS_POINT
307 };
308
309 G_DEFINE_TYPE (OsmGpsMap, osm_gps_map, GTK_TYPE_DRAWING_AREA);
310
311 /*
312 * Drawing function forward defintions
313 */
314 static gchar *replace_string(const gchar *src, const gchar *from, const gchar *to);
315 static gchar *replace_map_uri(OsmGpsMap *map, const gchar *uri, int zoom, int x, int y);
316 static void osm_gps_map_tile_download_complete (SoupSession *session, SoupMessage *msg, gpointer user_data);
317 static void osm_gps_map_download_tile (OsmGpsMap *map, int zoom, int x, int y, gboolean redraw);
318 static GdkPixbuf* osm_gps_map_render_tile_upscaled (OsmGpsMap *map, GdkPixbuf *tile, int tile_zoom, int zoom, int x, int y);
319
320 static void
cached_tile_free(OsmCachedTile * tile)321 cached_tile_free (OsmCachedTile *tile)
322 {
323 g_object_unref (tile->pixbuf);
324 g_slice_free (OsmCachedTile, tile);
325 }
326
327 /*
328 * Description:
329 * Find and replace text within a string.
330 *
331 * Parameters:
332 * src (in) - pointer to source string
333 * from (in) - pointer to search text
334 * to (in) - pointer to replacement text
335 *
336 * Returns:
337 * Returns a pointer to dynamically-allocated memory containing string
338 * with occurences of the text pointed to by 'from' replaced by with the
339 * text pointed to by 'to'.
340 */
341 static gchar *
replace_string(const gchar * src,const gchar * from,const gchar * to)342 replace_string(const gchar *src, const gchar *from, const gchar *to)
343 {
344 size_t size = strlen(src) + 1;
345 size_t fromlen = strlen(from);
346 size_t tolen = strlen(to);
347
348 /* Allocate the first chunk with enough for the original string. */
349 gchar *value = g_malloc(size);
350
351
352 /* We need to return 'value', so let's make a copy to mess around with. */
353 gchar *dst = value;
354
355 if ( value != NULL )
356 {
357 for ( ;; )
358 {
359 /* Try to find the search text. */
360 const gchar *match = g_strstr_len(src, size, from);
361 if ( match != NULL )
362 {
363 gchar *temp;
364 /* Find out how many characters to copy up to the 'match'. */
365 size_t count = match - src;
366
367
368 /* Calculate the total size the string will be after the
369 * replacement is performed. */
370 size += tolen - fromlen;
371
372 temp = g_realloc(value, size);
373 if ( temp == NULL )
374 {
375 g_free(value);
376 return NULL;
377 }
378
379 /* we'll want to return 'value' eventually, so let's point it
380 * to the memory that we are now working with.
381 * And let's not forget to point to the right location in
382 * the destination as well. */
383 dst = temp + (dst - value);
384 value = temp;
385
386 /*
387 * Copy from the source to the point where we matched. Then
388 * move the source pointer ahead by the amount we copied. And
389 * move the destination pointer ahead by the same amount.
390 */
391 g_memmove(dst, src, count);
392 src += count;
393 dst += count;
394
395 /* Now copy in the replacement text 'to' at the position of
396 * the match. Adjust the source pointer by the text we replaced.
397 * Adjust the destination pointer by the amount of replacement
398 * text. */
399 g_memmove(dst, to, tolen);
400 src += fromlen;
401 dst += tolen;
402 }
403 else
404 {
405 /*
406 * Copy any remaining part of the string. This includes the null
407 * termination character.
408 */
409 strcpy(dst, src);
410 break;
411 }
412 }
413 }
414 return value;
415 }
416
417 static void
map_convert_coords_to_quadtree_string(OsmGpsMap * map,gint x,gint y,gint zoomlevel,gchar * buffer,const gchar initial,const gchar * const quadrant)418 map_convert_coords_to_quadtree_string(OsmGpsMap *map, gint x, gint y, gint zoomlevel,
419 gchar *buffer, const gchar initial,
420 const gchar *const quadrant)
421 {
422 gchar *ptr = buffer;
423 gint n;
424
425 if (initial)
426 *ptr++ = initial;
427
428 for(n = zoomlevel-1; n >= 0; n--)
429 {
430 gint xbit = (x >> n) & 1;
431 gint ybit = (y >> n) & 1;
432 *ptr++ = quadrant[xbit + 2 * ybit];
433 }
434
435 *ptr++ = '\0';
436 }
437
438
439 static void
inspect_map_uri(OsmGpsMapPrivate * priv)440 inspect_map_uri(OsmGpsMapPrivate *priv)
441 {
442 priv->uri_format = 0;
443 priv->is_google = FALSE;
444
445 if (g_strrstr(priv->repo_uri, URI_MARKER_X))
446 priv->uri_format |= URI_HAS_X;
447
448 if (g_strrstr(priv->repo_uri, URI_MARKER_Y))
449 priv->uri_format |= URI_HAS_Y;
450
451 if (g_strrstr(priv->repo_uri, URI_MARKER_Z))
452 priv->uri_format |= URI_HAS_Z;
453
454 if (g_strrstr(priv->repo_uri, URI_MARKER_S))
455 priv->uri_format |= URI_HAS_S;
456
457 if (g_strrstr(priv->repo_uri, URI_MARKER_Q))
458 priv->uri_format |= URI_HAS_Q;
459
460 if (g_strrstr(priv->repo_uri, URI_MARKER_Q0))
461 priv->uri_format |= URI_HAS_Q0;
462
463 if (g_strrstr(priv->repo_uri, URI_MARKER_YS))
464 priv->uri_format |= URI_HAS_YS;
465
466 if (g_strrstr(priv->repo_uri, URI_MARKER_R))
467 priv->uri_format |= URI_HAS_R;
468
469 if (g_strrstr(priv->repo_uri, "google.com"))
470 priv->is_google = TRUE;
471
472 g_debug("URI Format: 0x%X (google: %X)", priv->uri_format, priv->is_google);
473
474 }
475
476 static gchar *
replace_map_uri(OsmGpsMap * map,const gchar * uri,int zoom,int x,int y)477 replace_map_uri(OsmGpsMap *map, const gchar *uri, int zoom, int x, int y)
478 {
479 OsmGpsMapPrivate *priv = map->priv;
480 char *url;
481 unsigned int i;
482 char location[22];
483
484 i = 1;
485 url = g_strdup(uri);
486 while (i < URI_FLAG_END)
487 {
488 char *s = NULL;
489 char *old;
490
491 old = url;
492 switch(i & priv->uri_format)
493 {
494 case URI_HAS_X:
495 s = g_strdup_printf("%d", x);
496 url = replace_string(url, URI_MARKER_X, s);
497 break;
498 case URI_HAS_Y:
499 s = g_strdup_printf("%d", y);
500 url = replace_string(url, URI_MARKER_Y, s);
501 break;
502 case URI_HAS_Z:
503 s = g_strdup_printf("%d", zoom);
504 url = replace_string(url, URI_MARKER_Z, s);
505 break;
506 case URI_HAS_S:
507 s = g_strdup_printf("%d", priv->max_zoom-zoom);
508 url = replace_string(url, URI_MARKER_S, s);
509 break;
510 case URI_HAS_Q:
511 map_convert_coords_to_quadtree_string(map,x,y,zoom,location,'t',"qrts");
512 s = g_strdup_printf("%s", location);
513 url = replace_string(url, URI_MARKER_Q, s);
514 break;
515 case URI_HAS_Q0:
516 map_convert_coords_to_quadtree_string(map,x,y,zoom,location,'\0', "0123");
517 s = g_strdup_printf("%s", location);
518 url = replace_string(url, URI_MARKER_Q0, s);
519 //g_debug("FOUND " URI_MARKER_Q0);
520 break;
521 case URI_HAS_YS:
522 // s = g_strdup_printf("%d", y);
523 // url = replace_string(url, URI_MARKER_YS, s);
524 g_warning("FOUND " URI_MARKER_YS " NOT IMPLEMENTED");
525 // retval = g_strdup_printf(repo->url,
526 // tilex,
527 // (1 << (MAX_ZOOM - zoom)) - tiley - 1,
528 // zoom - (MAX_ZOOM - 17));
529 break;
530 case URI_HAS_R:
531 s = g_strdup_printf("%d", g_random_int_range(0,4));
532 url = replace_string(url, URI_MARKER_R, s);
533 break;
534 default:
535 s = NULL;
536 break;
537 }
538
539 if (s) {
540 g_free(s);
541 g_free(old);
542 }
543
544 i = (i << 1);
545
546 }
547
548 return url;
549 }
550
551 static void
my_log_handler(const gchar * log_domain,GLogLevelFlags log_level,const gchar * message,gpointer user_data)552 my_log_handler (const gchar * log_domain, GLogLevelFlags log_level, const gchar * message, gpointer user_data)
553 {
554 if (!(log_level & G_LOG_LEVEL_DEBUG) || ENABLE_DEBUG)
555 g_log_default_handler (log_domain, log_level, message, user_data);
556 }
557
558 static float
osm_gps_map_get_scale_at_point(int zoom,float rlat,float rlon)559 osm_gps_map_get_scale_at_point(int zoom, float rlat, float rlon)
560 {
561 /* world at zoom 1 == 512 pixels */
562 return cos(rlat) * M_PI * OSM_EQ_RADIUS / (1<<(7+zoom));
563 }
564
565 static GSList *
gslist_remove_one_gobject(GSList ** list,GObject * gobj)566 gslist_remove_one_gobject(GSList **list, GObject *gobj)
567 {
568 GSList *data = g_slist_find(*list, gobj);
569 if (data) {
570 g_object_unref(gobj);
571 *list = g_slist_delete_link(*list, data);
572 }
573 return data;
574 }
575
576 static void
gslist_of_gobjects_free(GSList ** list)577 gslist_of_gobjects_free(GSList **list)
578 {
579 if (list) {
580 g_slist_foreach(*list, (GFunc) g_object_unref, NULL);
581 g_slist_free(*list);
582 *list = NULL;
583 }
584 }
585
586 static void
gslist_of_data_free(GSList ** list)587 gslist_of_data_free (GSList **list)
588 {
589 if (list) {
590 g_slist_foreach(*list, (GFunc) g_free, NULL);
591 g_slist_free(*list);
592 *list = NULL;
593 }
594 }
595
596 static void
draw_white_rectangle(cairo_t * cr,double x,double y,double width,double height)597 draw_white_rectangle(cairo_t *cr, double x, double y, double width, double height)
598 {
599 cairo_save (cr);
600 cairo_set_source_rgb (cr, 1, 1, 1);
601 cairo_rectangle (cr, x, y, width, height);
602 cairo_fill (cr);
603 cairo_restore (cr);
604 }
605
606 static void
osm_gps_map_print_images(OsmGpsMap * map,cairo_t * cr)607 osm_gps_map_print_images (OsmGpsMap *map, cairo_t *cr)
608 {
609 GSList *list;
610 int min_x = 0,min_y = 0,max_x = 0,max_y = 0;
611 int map_x0, map_y0;
612 OsmGpsMapPrivate *priv = map->priv;
613
614 map_x0 = priv->map_x - EXTRA_BORDER;
615 map_y0 = priv->map_y - EXTRA_BORDER;
616 for(list = priv->images; list != NULL; list = list->next)
617 {
618 GdkRectangle loc;
619 OsmGpsMapImage *im = OSM_GPS_MAP_IMAGE(list->data);
620 const OsmGpsMapPoint *pt = osm_gps_map_image_get_point(im);
621
622 /* pixel_x,y, offsets */
623 loc.x = lon2pixel(priv->map_zoom, pt->rlon) - map_x0;
624 loc.y = lat2pixel(priv->map_zoom, pt->rlat) - map_y0;
625
626 osm_gps_map_image_draw (
627 im,
628 cr,
629 &loc);
630
631 max_x = MAX(loc.x + loc.width, max_x);
632 min_x = MIN(loc.x - loc.width, min_x);
633 max_y = MAX(loc.y + loc.height, max_y);
634 min_y = MIN(loc.y - loc.height, min_y);
635 }
636
637 gtk_widget_queue_draw_area (
638 GTK_WIDGET(map),
639 min_x + EXTRA_BORDER, min_y + EXTRA_BORDER,
640 max_x + EXTRA_BORDER, max_y + EXTRA_BORDER);
641
642 }
643
644 static void
osm_gps_map_draw_gps_point(OsmGpsMap * map,cairo_t * cr)645 osm_gps_map_draw_gps_point (OsmGpsMap *map, cairo_t *cr)
646 {
647 OsmGpsMapPrivate *priv = map->priv;
648 int map_x0, map_y0;
649 int x, y;
650 int r, r2, mr;
651
652 r = priv->ui_gps_point_inner_radius;
653 r2 = priv->ui_gps_point_outer_radius;
654 mr = MAX(3*r,r2);
655 map_x0 = priv->map_x - EXTRA_BORDER;
656 map_y0 = priv->map_y - EXTRA_BORDER;
657 x = lon2pixel(priv->map_zoom, priv->gps->rlon) - map_x0;
658 y = lat2pixel(priv->map_zoom, priv->gps->rlat) - map_y0;
659
660 /* draw transparent area */
661 if (r2 > 0) {
662 cairo_set_line_width (cr, 1.5);
663 cairo_set_source_rgba (cr, 0.75, 0.75, 0.75, 0.4);
664 cairo_arc (cr, x, y, r2, 0, 2 * M_PI);
665 cairo_fill (cr);
666 /* draw transparent area border */
667 cairo_set_source_rgba (cr, 0.55, 0.55, 0.55, 0.4);
668 cairo_arc (cr, x, y, r2, 0, 2 * M_PI);
669 cairo_stroke(cr);
670 }
671
672 /* draw ball gradient */
673 if (r > 0) {
674 cairo_pattern_t *pat;
675 /* draw direction arrow */
676 if(!isnan(priv->gps_heading)) {
677 cairo_move_to (cr, x-r*cos(priv->gps_heading), y-r*sin(priv->gps_heading));
678 cairo_line_to (cr, x+3*r*sin(priv->gps_heading), y-3*r*cos(priv->gps_heading));
679 cairo_line_to (cr, x+r*cos(priv->gps_heading), y+r*sin(priv->gps_heading));
680 cairo_close_path (cr);
681
682 cairo_set_source_rgba (cr, 0.3, 0.3, 1.0, 0.5);
683 cairo_fill_preserve (cr);
684
685 cairo_set_line_width (cr, 1.0);
686 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
687 cairo_stroke(cr);
688 }
689
690 pat = cairo_pattern_create_radial (x-(r/5), y-(r/5), (r/5), x, y, r);
691 cairo_pattern_add_color_stop_rgba (pat, 0, 1, 1, 1, 1.0);
692 cairo_pattern_add_color_stop_rgba (pat, 1, 0, 0, 1, 1.0);
693 cairo_set_source (cr, pat);
694 cairo_arc (cr, x, y, r, 0, 2 * M_PI);
695 cairo_fill (cr);
696 cairo_pattern_destroy (pat);
697 /* draw ball border */
698 cairo_set_line_width (cr, 1.0);
699 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
700 cairo_arc (cr, x, y, r, 0, 2 * M_PI);
701 cairo_stroke(cr);
702 }
703
704 gtk_widget_queue_draw_area (GTK_WIDGET(map),
705 x-mr,
706 y-mr,
707 mr*2,
708 mr*2);
709 }
710
711 static void
osm_gps_map_blit_tile(OsmGpsMap * map,GdkPixbuf * pixbuf,cairo_t * cr,int offset_x,int offset_y,int tile_zoom,int target_x,int target_y)712 osm_gps_map_blit_tile(OsmGpsMap *map, GdkPixbuf *pixbuf, cairo_t *cr, int offset_x, int offset_y,
713 int tile_zoom, int target_x, int target_y)
714 {
715 OsmGpsMapPrivate *priv = map->priv;
716 int target_zoom = priv->map_zoom;
717
718 if (tile_zoom == target_zoom) {
719 g_debug("Blit @ %d,%d", offset_x,offset_y);
720 /* draw pixbuf */
721 gdk_cairo_set_source_pixbuf (cr, pixbuf, offset_x, offset_y);
722 cairo_paint (cr);
723 } else {
724 /* get an upscaled version of the pixbuf */
725 GdkPixbuf *pixmap_scaled = osm_gps_map_render_tile_upscaled (
726 map, pixbuf, tile_zoom,
727 target_zoom, target_x, target_y);
728
729 osm_gps_map_blit_tile (map, pixmap_scaled, cr, offset_x, offset_y,
730 target_zoom, target_x, target_y);
731
732 g_object_unref (pixmap_scaled);
733 }
734 }
735
736 #define MSG_RESPONSE_BODY(a) ((a)->response_body->data)
737 #define MSG_RESPONSE_LEN(a) ((a)->response_body->length)
738 #define MSG_RESPONSE_LEN_FORMAT "%"G_GOFFSET_FORMAT
739
740 static void
osm_gps_map_tile_download_complete(SoupSession * session,SoupMessage * msg,gpointer user_data)741 osm_gps_map_tile_download_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
742 {
743 FILE *file;
744 OsmTileDownload *dl = (OsmTileDownload *)user_data;
745 OsmGpsMap *map = OSM_GPS_MAP(dl->map);
746 OsmGpsMapPrivate *priv = map->priv;
747 gboolean file_saved = FALSE;
748
749 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
750 /* save tile into cachedir if one has been specified */
751 if (priv->cache_dir) {
752 if (g_mkdir_with_parents(dl->folder,0700) == 0) {
753 file = g_fopen(dl->filename, "wb");
754 if (file != NULL) {
755 fwrite (MSG_RESPONSE_BODY(msg), 1, MSG_RESPONSE_LEN(msg), file);
756 file_saved = TRUE;
757 g_debug("Wrote "MSG_RESPONSE_LEN_FORMAT" bytes to %s", MSG_RESPONSE_LEN(msg), dl->filename);
758 fclose (file);
759
760 }
761 } else {
762 g_warning("Error creating tile download directory: %s", dl->folder);
763 }
764 }
765
766 if (dl->redraw) {
767 GdkPixbuf *pixbuf = NULL;
768
769 /* if the file was actually stored on disk, we can simply */
770 /* load and decode it from that file */
771 if (priv->cache_dir) {
772 if (file_saved) {
773 pixbuf = gdk_pixbuf_new_from_file (dl->filename, NULL);
774 }
775 } else {
776 GdkPixbufLoader *loader;
777 char *extension = strrchr (dl->filename, '.');
778
779 /* parse file directly from memory */
780 if (extension) {
781 loader = gdk_pixbuf_loader_new_with_type (extension+1, NULL);
782 if (!gdk_pixbuf_loader_write (loader, (unsigned char*)MSG_RESPONSE_BODY(msg), MSG_RESPONSE_LEN(msg), NULL))
783 {
784 g_warning("Error: Decoding of image failed");
785 }
786 gdk_pixbuf_loader_close(loader, NULL);
787
788 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
789
790 /* give up loader but keep the pixbuf */
791 g_object_ref(pixbuf);
792 g_object_unref(loader);
793 } else {
794 g_warning("Error: Unable to determine image file format");
795 }
796 }
797
798 /* Store the tile into the cache */
799 if (G_LIKELY (pixbuf)) {
800 OsmCachedTile *tile = g_slice_new (OsmCachedTile);
801 tile->pixbuf = pixbuf;
802 tile->redraw_cycle = priv->redraw_cycle;
803 /* if the tile is already in the cache (it could be one
804 * rendered from another zoom level), it will be
805 * overwritten */
806 g_hash_table_insert (priv->tile_cache, dl->filename, tile);
807 /* NULL-ify dl->filename so that it won't be freed, as
808 * we are using it as a key in the hash table */
809 dl->filename = NULL;
810 }
811 osm_gps_map_map_redraw_idle (map);
812 }
813 g_hash_table_remove(priv->tile_queue, dl->uri);
814 g_object_notify(G_OBJECT(map), "tiles-queued");
815
816 g_free(dl->folder);
817 g_free(dl->filename);
818 g_free(dl);
819 } else {
820 if ((msg->status_code == SOUP_STATUS_NOT_FOUND) || (msg->status_code == SOUP_STATUS_FORBIDDEN)) {
821 g_hash_table_insert(priv->missing_tiles, dl->uri, NULL);
822 g_hash_table_remove(priv->tile_queue, dl->uri);
823 g_object_notify(G_OBJECT(map), "tiles-queued");
824 } else if (msg->status_code == SOUP_STATUS_CANCELLED) {
825 /* called as application exit or after osm_gps_map_download_cancel_all */
826 g_hash_table_remove(priv->tile_queue, dl->uri);
827 g_object_notify(G_OBJECT(map), "tiles-queued");
828 } else {
829 g_warning("Error downloading tile: %d - %s", msg->status_code, msg->reason_phrase);
830 dl->ttl--;
831 if (dl->ttl) {
832 soup_session_requeue_message(session, msg);
833 return;
834 }
835
836 g_hash_table_remove(priv->tile_queue, dl->uri);
837 g_object_notify(G_OBJECT(map), "tiles-queued");
838 }
839 }
840
841
842 }
843
844 static void
osm_gps_map_download_tile(OsmGpsMap * map,int zoom,int x,int y,gboolean redraw)845 osm_gps_map_download_tile (OsmGpsMap *map, int zoom, int x, int y, gboolean redraw)
846 {
847 SoupMessage *msg;
848 OsmGpsMapPrivate *priv = map->priv;
849 OsmTileDownload *dl = g_new0(OsmTileDownload,1);
850
851 // set retries
852 dl->ttl = DOWNLOAD_RETRIES;
853
854 //calculate the uri to download
855 dl->uri = replace_map_uri(map, priv->repo_uri, zoom, x, y);
856
857 //check the tile has not already been queued for download,
858 //or has been attempted, and its missing
859 if (g_hash_table_lookup_extended(priv->tile_queue, dl->uri, NULL, NULL) ||
860 g_hash_table_lookup_extended(priv->missing_tiles, dl->uri, NULL, NULL) )
861 {
862 g_debug("Tile already downloading (or missing)");
863 g_free(dl->uri);
864 g_free(dl);
865 } else {
866 dl->folder = g_strdup_printf("%s%c%d%c%d%c",
867 priv->cache_dir, G_DIR_SEPARATOR,
868 zoom, G_DIR_SEPARATOR,
869 x, G_DIR_SEPARATOR);
870 dl->filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
871 priv->cache_dir, G_DIR_SEPARATOR,
872 zoom, G_DIR_SEPARATOR,
873 x, G_DIR_SEPARATOR,
874 y,
875 priv->image_format);
876 dl->map = map;
877 dl->redraw = redraw;
878
879 g_debug("Download tile: %d,%d z:%d\n\t%s --> %s", x, y, zoom, dl->uri, dl->filename);
880
881 msg = soup_message_new (SOUP_METHOD_GET, dl->uri);
882 if (msg) {
883 if (priv->is_google) {
884 //Set maps.google.com as the referrer
885 g_debug("Setting Google Referrer");
886 soup_message_headers_append(msg->request_headers, "Referer", "http://maps.google.com/");
887 //For google satelite also set the appropriate cookie value
888 if (priv->uri_format & URI_HAS_Q) {
889 const char *cookie = g_getenv("GOOGLE_COOKIE");
890 if (cookie) {
891 g_debug("Adding Google Cookie");
892 soup_message_headers_append(msg->request_headers, "Cookie", cookie);
893 }
894 }
895 }
896
897 g_hash_table_insert (priv->tile_queue, dl->uri, msg);
898 g_object_notify (G_OBJECT (map), "tiles-queued");
899 /* the soup session unrefs the message when the download finishes */
900 soup_session_queue_message (priv->soup_session, msg, osm_gps_map_tile_download_complete, dl);
901 } else {
902 g_warning("Could not create soup message");
903 g_free(dl->uri);
904 g_free(dl->folder);
905 g_free(dl->filename);
906 g_free(dl);
907 }
908 }
909 }
910
911 static GdkPixbuf *
osm_gps_map_load_cached_tile(OsmGpsMap * map,int zoom,int x,int y)912 osm_gps_map_load_cached_tile (OsmGpsMap *map, int zoom, int x, int y)
913 {
914 OsmGpsMapPrivate *priv = map->priv;
915 gchar *filename;
916 GdkPixbuf *pixbuf = NULL;
917 OsmCachedTile *tile;
918
919 filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
920 priv->cache_dir, G_DIR_SEPARATOR,
921 zoom, G_DIR_SEPARATOR,
922 x, G_DIR_SEPARATOR,
923 y,
924 priv->image_format);
925
926 tile = g_hash_table_lookup (priv->tile_cache, filename);
927 if (tile)
928 {
929 g_free (filename);
930 }
931 else
932 {
933 pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
934 if (pixbuf)
935 {
936 tile = g_slice_new (OsmCachedTile);
937 tile->pixbuf = pixbuf;
938 g_hash_table_insert (priv->tile_cache, filename, tile);
939 }
940 else
941 {
942 g_free (filename);
943 }
944 }
945
946 /* set/update the redraw_cycle timestamp on the tile */
947 if (tile)
948 {
949 tile->redraw_cycle = priv->redraw_cycle;
950 pixbuf = g_object_ref (tile->pixbuf);
951 }
952
953 return pixbuf;
954 }
955
956 static GdkPixbuf *
osm_gps_map_find_bigger_tile(OsmGpsMap * map,int zoom,int x,int y,int * zoom_found)957 osm_gps_map_find_bigger_tile (OsmGpsMap *map, int zoom, int x, int y,
958 int *zoom_found)
959 {
960 GdkPixbuf *pixbuf;
961 int next_zoom, next_x, next_y;
962
963 if (zoom == 0) return NULL;
964 next_zoom = zoom - 1;
965 next_x = x / 2;
966 next_y = y / 2;
967 pixbuf = osm_gps_map_load_cached_tile (map, next_zoom, next_x, next_y);
968 if (pixbuf)
969 *zoom_found = next_zoom;
970 else
971 pixbuf = osm_gps_map_find_bigger_tile (map, next_zoom, next_x, next_y,
972 zoom_found);
973 return pixbuf;
974 }
975
976 static GdkPixbuf *
osm_gps_map_render_missing_tile_upscaled(OsmGpsMap * map,int zoom,int x,int y)977 osm_gps_map_render_missing_tile_upscaled (OsmGpsMap *map, int zoom,
978 int x, int y)
979 {
980 GdkPixbuf *pixbuf, *big;
981 int zoom_big;
982
983 big = osm_gps_map_find_bigger_tile (map, zoom, x, y, &zoom_big);
984 if (!big) return NULL;
985
986 g_debug ("Found bigger tile (zoom = %d, wanted = %d)", zoom_big, zoom);
987
988 pixbuf = osm_gps_map_render_tile_upscaled (map, big, zoom_big,
989 zoom, x, y);
990 g_object_unref (big);
991
992 return pixbuf;
993 }
994 static GdkPixbuf*
osm_gps_map_render_tile_upscaled(OsmGpsMap * map,GdkPixbuf * big,int zoom_big,int zoom,int x,int y)995 osm_gps_map_render_tile_upscaled (OsmGpsMap *map, GdkPixbuf *big, int zoom_big,
996 int zoom, int x, int y)
997 {
998 GdkPixbuf *pixbuf, *area;
999 int area_size, area_x, area_y;
1000 int modulo;
1001 int zoom_diff;
1002
1003 /* get a Pixbuf for the area to magnify */
1004 zoom_diff = zoom - zoom_big;
1005
1006 g_debug ("Upscaling by %d levels into tile %d,%d", zoom_diff, x, y);
1007
1008 area_size = TILESIZE >> zoom_diff;
1009 modulo = 1 << zoom_diff;
1010 area_x = (x % modulo) * area_size;
1011 area_y = (y % modulo) * area_size;
1012 area = gdk_pixbuf_new_subpixbuf (big, area_x, area_y,
1013 area_size, area_size);
1014 pixbuf = gdk_pixbuf_scale_simple (area, TILESIZE, TILESIZE,
1015 GDK_INTERP_NEAREST);
1016 g_object_unref (area);
1017 return pixbuf;
1018 }
1019
1020 static GdkPixbuf *
osm_gps_map_render_missing_tile(OsmGpsMap * map,int zoom,int x,int y)1021 osm_gps_map_render_missing_tile (OsmGpsMap *map, int zoom, int x, int y)
1022 {
1023 /* maybe TODO: render from downscaled tiles, if the following fails */
1024 return osm_gps_map_render_missing_tile_upscaled (map, zoom, x, y);
1025 }
1026
1027 static void
osm_gps_map_load_tile(OsmGpsMap * map,cairo_t * cr,int zoom,int x,int y,int offset_x,int offset_y)1028 osm_gps_map_load_tile (OsmGpsMap *map, cairo_t *cr, int zoom, int x, int y, int offset_x, int offset_y)
1029 {
1030 OsmGpsMapPrivate *priv = map->priv;
1031 gchar *filename;
1032 GdkPixbuf *pixbuf;
1033 int zoom_offset = priv->tile_zoom_offset;
1034 int target_x, target_y;
1035
1036 g_debug("Load virtual tile %d,%d (%d,%d) z:%d", x, y, offset_x, offset_y, zoom);
1037
1038 if (zoom > MIN_ZOOM) {
1039 zoom -= zoom_offset;
1040 x >>= zoom_offset;
1041 y >>= zoom_offset;
1042 }
1043
1044 target_x = x;
1045 target_y = y;
1046
1047 g_debug("Load actual tile %d,%d (%d,%d) z:%d", x, y, offset_x, offset_y, zoom);
1048
1049 if (priv->map_source == OSM_GPS_MAP_SOURCE_NULL) {
1050 osm_gps_map_blit_tile(map, priv->null_tile, cr, offset_x, offset_y,
1051 priv->map_zoom, target_x, target_y);
1052 return;
1053 }
1054
1055 filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
1056 priv->cache_dir, G_DIR_SEPARATOR,
1057 zoom, G_DIR_SEPARATOR,
1058 x, G_DIR_SEPARATOR,
1059 y,
1060 priv->image_format);
1061
1062 /* try to get file from internal cache first */
1063 if(!(pixbuf = osm_gps_map_load_cached_tile(map, zoom, x, y)))
1064 pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
1065
1066 if(pixbuf) {
1067 g_debug("Found tile %s", filename);
1068 osm_gps_map_blit_tile(map, pixbuf, cr, offset_x, offset_y,
1069 zoom, target_x, target_y);
1070 g_object_unref (pixbuf);
1071 } else {
1072 if (priv->map_auto_download_enabled) {
1073 osm_gps_map_download_tile(map, zoom, x, y, TRUE);
1074 }
1075
1076 /* try to render the tile by scaling cached tiles from other zoom
1077 * levels */
1078 pixbuf = osm_gps_map_render_missing_tile (map, zoom, x, y);
1079 if (pixbuf) {
1080 osm_gps_map_blit_tile(map, pixbuf, cr, offset_x, offset_y,
1081 zoom, target_x, target_y);
1082 g_object_unref (pixbuf);
1083 } else {
1084 /* prevent some artifacts when drawing not yet loaded areas. */
1085 g_warning ("Error getting missing tile"); /* FIXME: is this a warning? */
1086 draw_white_rectangle (cr, offset_x, offset_y, TILESIZE, TILESIZE);
1087 }
1088 }
1089 g_free(filename);
1090 }
1091
1092 static void
osm_gps_map_fill_tiles_pixel(OsmGpsMap * map,cairo_t * cr)1093 osm_gps_map_fill_tiles_pixel (OsmGpsMap *map, cairo_t *cr)
1094 {
1095 OsmGpsMapPrivate *priv = map->priv;
1096 GtkAllocation allocation;
1097 int i,j, tile_x0, tile_y0, tiles_nx, tiles_ny;
1098 int offset_xn = 0;
1099 int offset_yn = 0;
1100 int offset_x;
1101 int offset_y;
1102
1103 g_debug("Fill tiles: %d,%d z:%d", priv->map_x, priv->map_y, priv->map_zoom);
1104
1105 gtk_widget_get_allocation(GTK_WIDGET(map), &allocation);
1106
1107 offset_x = - priv->map_x % TILESIZE;
1108 offset_y = - priv->map_y % TILESIZE;
1109 if (offset_x > 0) offset_x -= TILESIZE;
1110 if (offset_y > 0) offset_y -= TILESIZE;
1111
1112 offset_xn = offset_x + EXTRA_BORDER;
1113 offset_yn = offset_y + EXTRA_BORDER;
1114
1115 tiles_nx = (allocation.width - offset_x) / TILESIZE + 1;
1116 tiles_ny = (allocation.height - offset_y) / TILESIZE + 1;
1117
1118 tile_x0 = floor((float)priv->map_x / (float)TILESIZE);
1119 tile_y0 = floor((float)priv->map_y / (float)TILESIZE);
1120
1121 for (i=tile_x0; i<(tile_x0+tiles_nx);i++)
1122 {
1123 for (j=tile_y0; j<(tile_y0+tiles_ny); j++)
1124 {
1125 if( j<0 || i<0 || i>=exp(priv->map_zoom * M_LN2) || j>=exp(priv->map_zoom * M_LN2))
1126 {
1127 /* draw white in areas outside map (i.e. when zoomed right out) */
1128 draw_white_rectangle (cr, offset_xn, offset_yn, TILESIZE, TILESIZE);
1129 }
1130 else
1131 {
1132 osm_gps_map_load_tile(map,
1133 cr,
1134 priv->map_zoom,
1135 i,j,
1136 offset_xn - EXTRA_BORDER,offset_yn - EXTRA_BORDER);
1137 }
1138 offset_yn += TILESIZE;
1139 }
1140 offset_xn += TILESIZE;
1141 offset_yn = offset_y + EXTRA_BORDER;
1142 }
1143 }
1144
1145 static void
osm_gps_map_print_track(OsmGpsMap * map,OsmGpsMapTrack * track,cairo_t * cr)1146 osm_gps_map_print_track (OsmGpsMap *map, OsmGpsMapTrack *track, cairo_t *cr)
1147 {
1148 OsmGpsMapPrivate *priv = map->priv;
1149
1150 GSList *pt,*points;
1151 int x,y;
1152 int min_x = 0,min_y = 0,max_x = 0,max_y = 0;
1153 gfloat lw, alpha;
1154 int map_x0, map_y0;
1155 GdkRGBA color;
1156
1157 g_object_get (track,
1158 "track", &points,
1159 "line-width", &lw,
1160 "alpha", &alpha,
1161 NULL);
1162 osm_gps_map_track_get_color(track, &color);
1163
1164 if (points == NULL)
1165 return;
1166
1167 gboolean path_editable = FALSE;
1168 g_object_get(track, "editable", &path_editable, NULL);
1169
1170 cairo_set_line_width (cr, lw);
1171 cairo_set_source_rgba (cr, color.red, color.green, color.blue, alpha);
1172 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
1173 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
1174
1175 map_x0 = priv->map_x - EXTRA_BORDER;
1176 map_y0 = priv->map_y - EXTRA_BORDER;
1177
1178 int last_x = 0, last_y = 0;
1179 for(pt = points; pt != NULL; pt = pt->next)
1180 {
1181 OsmGpsMapPoint *tp = pt->data;
1182
1183 x = lon2pixel(priv->map_zoom, tp->rlon) - map_x0;
1184 y = lat2pixel(priv->map_zoom, tp->rlat) - map_y0;
1185
1186 /* first time through loop */
1187 if (pt == points)
1188 cairo_move_to(cr, x, y);
1189
1190 cairo_line_to(cr, x, y);
1191 cairo_stroke(cr);
1192 if(path_editable)
1193 {
1194 cairo_arc (cr, x, y, DOT_RADIUS, 0.0, 2 * M_PI);
1195 cairo_stroke(cr);
1196
1197 if(pt != points)
1198 {
1199 cairo_set_source_rgba (cr, color.red, color.green, color.blue, alpha*0.75);
1200 cairo_arc(cr, (last_x + x)/2.0, (last_y+y)/2.0, DOT_RADIUS, 0.0, 2*M_PI);
1201 cairo_stroke(cr);
1202 cairo_set_source_rgba (cr, color.red, color.green, color.blue, alpha);
1203 }
1204 }
1205
1206 cairo_move_to(cr, x, y);
1207
1208 max_x = MAX(x,max_x);
1209 min_x = MIN(x,min_x);
1210 max_y = MAX(y,max_y);
1211 min_y = MIN(y,min_y);
1212
1213 last_x = x;
1214 last_y = y;
1215 }
1216
1217 gtk_widget_queue_draw_area (
1218 GTK_WIDGET(map),
1219 min_x - lw,
1220 min_y - lw,
1221 max_x + (lw * 2),
1222 max_y + (lw * 2));
1223
1224 cairo_stroke(cr);
1225 }
1226
1227 /* Prints the gps trip history, and any other tracks */
1228 static void
osm_gps_map_print_tracks(OsmGpsMap * map,cairo_t * cr)1229 osm_gps_map_print_tracks (OsmGpsMap *map, cairo_t *cr)
1230 {
1231 GSList *tmp;
1232 OsmGpsMapPrivate *priv = map->priv;
1233
1234 if (priv->trip_history_show_enabled) {
1235 osm_gps_map_print_track (map, priv->gps_track, cr);
1236 }
1237
1238 if (priv->tracks) {
1239 tmp = priv->tracks;
1240 while (tmp != NULL) {
1241 osm_gps_map_print_track (map, OSM_GPS_MAP_TRACK(tmp->data), cr);
1242 tmp = g_slist_next(tmp);
1243 }
1244 }
1245 }
1246
1247 static void
osm_gps_map_print_polygon(OsmGpsMap * map,OsmGpsMapPolygon * poly,cairo_t * cr)1248 osm_gps_map_print_polygon (OsmGpsMap *map, OsmGpsMapPolygon *poly, cairo_t *cr)
1249 {
1250 OsmGpsMapPrivate *priv = map->priv;
1251
1252 GSList *pt,*points;
1253 int x,y;
1254 int min_x = 0,min_y = 0,max_x = 0,max_y = 0;
1255 gfloat lw, alpha;
1256 int map_x0, map_y0;
1257 GdkRGBA color;
1258
1259 OsmGpsMapTrack* track = osm_gps_map_polygon_get_track(poly);
1260
1261 if(!track)
1262 return;
1263 g_object_get (track,
1264 "track", &points,
1265 "line-width", &lw,
1266 "alpha", &alpha,
1267 NULL);
1268 osm_gps_map_track_get_color(track, &color);
1269
1270 if (points == NULL)
1271 return;
1272
1273 gboolean path_editable = FALSE;
1274 gboolean poly_shaded = FALSE;
1275 g_object_get(poly, "editable", &path_editable, NULL);
1276 g_object_get(poly, "shaded", &poly_shaded, NULL);
1277
1278 cairo_set_line_width (cr, lw);
1279 cairo_set_source_rgba (cr, color.red, color.green, color.blue, alpha);
1280 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
1281 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
1282
1283 map_x0 = priv->map_x - EXTRA_BORDER;
1284 map_y0 = priv->map_y - EXTRA_BORDER;
1285
1286 int first_x = 0, first_y = 0;
1287 for(pt = points; pt != NULL; pt = pt->next)
1288 {
1289 OsmGpsMapPoint *tp = pt->data;
1290
1291 x = lon2pixel(priv->map_zoom, tp->rlon) - map_x0;
1292 y = lat2pixel(priv->map_zoom, tp->rlat) - map_y0;
1293
1294 /* first time through loop */
1295 if (pt == points)
1296 {
1297 cairo_move_to(cr, x, y);
1298 first_x = x; first_y = y;
1299 }
1300
1301 cairo_line_to(cr, x, y);
1302 }
1303 //close off polygon
1304 cairo_line_to(cr, first_x, first_y);
1305 if(poly_shaded)
1306 cairo_fill(cr);
1307 else
1308 cairo_stroke(cr);
1309
1310 if(path_editable)
1311 {
1312 int last_x = 0, last_y = 0;
1313 for(pt = points; pt != NULL; pt = pt->next)
1314 {
1315 OsmGpsMapPoint *tp = pt->data;
1316
1317 x = lon2pixel(priv->map_zoom, tp->rlon) - map_x0;
1318 y = lat2pixel(priv->map_zoom, tp->rlat) - map_y0;
1319
1320 cairo_arc (cr, x, y, DOT_RADIUS, 0.0, 2 * M_PI);
1321 cairo_stroke(cr);
1322
1323 if(pt != points)
1324 {
1325 cairo_set_source_rgba (cr, color.red, color.green, color.blue, alpha*0.75);
1326 cairo_arc(cr, (last_x + x)/2.0, (last_y+y)/2.0, DOT_RADIUS, 0.0, 2*M_PI);
1327 cairo_stroke(cr);
1328 cairo_set_source_rgba (cr, color.red, color.green, color.blue, alpha);
1329 }
1330 last_x = x; last_y = y;
1331 }
1332
1333 x = first_x; y = first_y;
1334 cairo_set_source_rgba (cr, color.red, color.green, color.blue, alpha*0.75);
1335 cairo_arc(cr, (last_x + x)/2.0, (last_y+y)/2.0, DOT_RADIUS, 0.0, 2*M_PI);
1336 cairo_stroke(cr);
1337 cairo_set_source_rgba (cr, color.red, color.green, color.blue, alpha);
1338 }
1339
1340 gtk_widget_queue_draw_area (
1341 GTK_WIDGET(map),
1342 min_x - lw,
1343 min_y - lw,
1344 max_x + (lw * 2),
1345 max_y + (lw * 2));
1346
1347 }
1348
1349 static void
osm_gps_map_print_polygons(OsmGpsMap * map,cairo_t * cr)1350 osm_gps_map_print_polygons (OsmGpsMap *map, cairo_t* cr)
1351 {
1352 GSList *tmp;
1353 OsmGpsMapPrivate *priv = map->priv;
1354
1355 if (priv->polygons) {
1356 tmp = priv->polygons;
1357 while (tmp != NULL) {
1358 osm_gps_map_print_polygon (map, OSM_GPS_MAP_POLYGON(tmp->data), cr);
1359 tmp = g_slist_next(tmp);
1360 }
1361 }
1362 }
1363
1364
1365 static gboolean
osm_gps_map_purge_cache_check(gpointer key,gpointer value,gpointer user)1366 osm_gps_map_purge_cache_check(gpointer key, gpointer value, gpointer user)
1367 {
1368 return (((OsmCachedTile*)value)->redraw_cycle != ((OsmGpsMapPrivate*)user)->redraw_cycle);
1369 }
1370
1371 static void
osm_gps_map_purge_cache(OsmGpsMap * map)1372 osm_gps_map_purge_cache (OsmGpsMap *map)
1373 {
1374 OsmGpsMapPrivate *priv = map->priv;
1375
1376 if (g_hash_table_size (priv->tile_cache) < priv->max_tile_cache_size)
1377 return;
1378
1379 /* run through the cache, and remove the tiles which have not been used
1380 * during the last redraw operation */
1381 g_hash_table_foreach_remove(priv->tile_cache, osm_gps_map_purge_cache_check, priv);
1382 }
1383
1384 gboolean
osm_gps_map_map_redraw(OsmGpsMap * map)1385 osm_gps_map_map_redraw (OsmGpsMap *map)
1386 {
1387 cairo_t *cr;
1388 int w, h;
1389 OsmGpsMapPrivate *priv = map->priv;
1390 GtkWidget *widget = GTK_WIDGET(map);
1391
1392 priv->idle_map_redraw = 0;
1393
1394 /* dont't redraw if we have not been shown yet */
1395 if (!priv->pixmap)
1396 return FALSE;
1397
1398 /* don't redraw the entire map while the OSD is doing */
1399 /* some animation or the like. This is to keep the animation */
1400 /* fluid */
1401 if (priv->layers) {
1402 GSList *list;
1403 for(list = priv->layers; list != NULL; list = list->next) {
1404 OsmGpsMapLayer *layer = list->data;
1405 if (osm_gps_map_layer_busy(layer))
1406 return FALSE;
1407 }
1408 }
1409
1410 /* the motion_notify handler uses priv->surface to redraw the area; if we
1411 * change it while we are dragging, we will end up showing it in the wrong
1412 * place. This could be fixed by carefully recompute the coordinates, but
1413 * for now it's easier just to disable redrawing the map while dragging */
1414 if (priv->is_dragging)
1415 return FALSE;
1416
1417 /* paint to the backing surface */
1418 cr = cairo_create (priv->pixmap);
1419
1420 /* undo all offsets that may have happened when dragging */
1421 priv->drag_mouse_dx = 0;
1422 priv->drag_mouse_dy = 0;
1423
1424 priv->redraw_cycle++;
1425
1426 /* clear white background */
1427 w = gtk_widget_get_allocated_width (widget);
1428 h = gtk_widget_get_allocated_width (widget);
1429 draw_white_rectangle(cr, 0, 0, w + EXTRA_BORDER * 2, h + EXTRA_BORDER * 2);
1430
1431 osm_gps_map_fill_tiles_pixel(map, cr);
1432
1433 osm_gps_map_print_tracks(map, cr);
1434 osm_gps_map_print_polygons(map, cr);
1435 osm_gps_map_print_images(map, cr);
1436
1437 /* draw the gps point using the appropriate virtual private method */
1438 if (priv->gps_track_used && priv->gps_point_enabled) {
1439 OsmGpsMapClass *klass = OSM_GPS_MAP_GET_CLASS(map);
1440 if (klass->draw_gps_point)
1441 klass->draw_gps_point (map, cr);
1442 }
1443
1444 if (priv->layers) {
1445 GSList *list;
1446 for(list = priv->layers; list != NULL; list = list->next) {
1447 OsmGpsMapLayer *layer = list->data;
1448 osm_gps_map_layer_render (layer, map);
1449 }
1450 }
1451
1452 osm_gps_map_purge_cache(map);
1453 gtk_widget_queue_draw (GTK_WIDGET (map));
1454
1455 cairo_destroy (cr);
1456
1457 return FALSE;
1458 }
1459
1460 void
osm_gps_map_map_redraw_idle(OsmGpsMap * map)1461 osm_gps_map_map_redraw_idle (OsmGpsMap *map)
1462 {
1463 OsmGpsMapPrivate *priv = map->priv;
1464
1465 if (priv->idle_map_redraw == 0)
1466 priv->idle_map_redraw = g_idle_add ((GSourceFunc)osm_gps_map_map_redraw, map);
1467 }
1468
1469 /* call this to update center_rlat and center_rlon after
1470 * changin map_x or map_y */
1471 static void
center_coord_update(OsmGpsMap * map)1472 center_coord_update(OsmGpsMap *map) {
1473
1474 GtkWidget *widget = GTK_WIDGET(map);
1475 OsmGpsMapPrivate *priv = map->priv;
1476 GtkAllocation allocation;
1477
1478 gtk_widget_get_allocation(widget, &allocation);
1479 gint pixel_x = priv->map_x + allocation.width/2;
1480 gint pixel_y = priv->map_y + allocation.height/2;
1481
1482 priv->center_rlon = pixel2lon(priv->map_zoom, pixel_x);
1483 priv->center_rlat = pixel2lat(priv->map_zoom, pixel_y);
1484
1485 g_signal_emit_by_name(widget, "changed");
1486 }
1487
1488 /* Automatically center the map if the current point, i.e the most recent
1489 * gps point, approaches the edge, and map_auto_center is set. Does not
1490 * request the map be redrawn */
1491 static void
maybe_autocenter_map(OsmGpsMap * map)1492 maybe_autocenter_map (OsmGpsMap *map)
1493 {
1494 OsmGpsMapPrivate *priv;
1495 GtkAllocation allocation;
1496
1497 g_return_if_fail (OSM_IS_GPS_MAP (map));
1498 priv = map->priv;
1499 gtk_widget_get_allocation(GTK_WIDGET(map), &allocation);
1500
1501 if(priv->map_auto_center_enabled) {
1502 int pixel_x = lon2pixel(priv->map_zoom, priv->gps->rlon);
1503 int pixel_y = lat2pixel(priv->map_zoom, priv->gps->rlat);
1504 int x = pixel_x - priv->map_x;
1505 int y = pixel_y - priv->map_y;
1506 int width = allocation.width;
1507 int height = allocation.height;
1508 if( x < (width/2 - width/8) || x > (width/2 + width/8) ||
1509 y < (height/2 - height/8) || y > (height/2 + height/8)) {
1510
1511 priv->map_x = pixel_x - allocation.width/2;
1512 priv->map_y = pixel_y - allocation.height/2;
1513 center_coord_update(map);
1514 }
1515 }
1516 }
1517
1518 static gboolean
on_window_key_press(GtkWidget * widget,GdkEventKey * event,OsmGpsMapPrivate * priv)1519 on_window_key_press(GtkWidget *widget, GdkEventKey *event, OsmGpsMapPrivate *priv)
1520 {
1521 int i;
1522 int step;
1523 gboolean handled;
1524 GtkAllocation allocation;
1525 OsmGpsMap *map = OSM_GPS_MAP(widget);
1526
1527 /* if no keybindings are set, let the app handle them... */
1528 if (!priv->keybindings_enabled)
1529 return FALSE;
1530
1531 handled = FALSE;
1532 gtk_widget_get_allocation(GTK_WIDGET(map), &allocation);
1533 step = allocation.width/OSM_GPS_MAP_SCROLL_STEP;
1534
1535 /* the map handles some keys on its own */
1536 for (i = 0; i < OSM_GPS_MAP_KEY_MAX; i++) {
1537 /* not the key we have a binding for */
1538 if (map->priv->keybindings[i] != event->keyval)
1539 continue;
1540
1541 switch(i) {
1542 case OSM_GPS_MAP_KEY_FULLSCREEN: {
1543 GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(widget));
1544 if(!priv->is_fullscreen)
1545 gtk_window_fullscreen(GTK_WINDOW(toplevel));
1546 else
1547 gtk_window_unfullscreen(GTK_WINDOW(toplevel));
1548
1549 priv->is_fullscreen = !priv->is_fullscreen;
1550 handled = TRUE;
1551 } break;
1552 case OSM_GPS_MAP_KEY_ZOOMIN:
1553 osm_gps_map_zoom_in(map);
1554 handled = TRUE;
1555 break;
1556 case OSM_GPS_MAP_KEY_ZOOMOUT:
1557 osm_gps_map_zoom_out(map);
1558 handled = TRUE;
1559 break;
1560 case OSM_GPS_MAP_KEY_UP:
1561 priv->map_y -= step;
1562 center_coord_update(map);
1563 osm_gps_map_map_redraw_idle(map);
1564 handled = TRUE;
1565 break;
1566 case OSM_GPS_MAP_KEY_DOWN:
1567 priv->map_y += step;
1568 center_coord_update(map);
1569 osm_gps_map_map_redraw_idle(map);
1570 handled = TRUE;
1571 break;
1572 case OSM_GPS_MAP_KEY_LEFT:
1573 priv->map_x -= step;
1574 center_coord_update(map);
1575 osm_gps_map_map_redraw_idle(map);
1576 handled = TRUE;
1577 break;
1578 case OSM_GPS_MAP_KEY_RIGHT:
1579 priv->map_x += step;
1580 center_coord_update(map);
1581 osm_gps_map_map_redraw_idle(map);
1582 handled = TRUE;
1583 break;
1584 default:
1585 break;
1586 }
1587 }
1588
1589 return handled;
1590 }
1591
1592 static void
on_gps_point_added(OsmGpsMapTrack * track,OsmGpsMapPoint * point,OsmGpsMap * map)1593 on_gps_point_added (OsmGpsMapTrack *track, OsmGpsMapPoint *point, OsmGpsMap *map)
1594 {
1595 osm_gps_map_map_redraw_idle (map);
1596 maybe_autocenter_map (map);
1597 }
1598
1599 static void
on_track_changed(OsmGpsMapTrack * track,GParamSpec * pspec,OsmGpsMap * map)1600 on_track_changed (OsmGpsMapTrack *track, GParamSpec *pspec, OsmGpsMap *map)
1601 {
1602 osm_gps_map_map_redraw_idle (map);
1603 }
1604
1605 static void
osm_gps_map_init(OsmGpsMap * object)1606 osm_gps_map_init (OsmGpsMap *object)
1607 {
1608 int i;
1609 OsmGpsMapPrivate *priv;
1610
1611 priv = G_TYPE_INSTANCE_GET_PRIVATE (object, OSM_TYPE_GPS_MAP, OsmGpsMapPrivate);
1612 object->priv = priv;
1613
1614 priv->pixmap = NULL;
1615
1616 priv->trip_history = NULL;
1617 priv->gps = osm_gps_map_point_new_radians(0.0, 0.0);
1618 priv->gps_track_used = FALSE;
1619 priv->gps_heading = OSM_GPS_MAP_INVALID;
1620
1621 priv->gps_track = osm_gps_map_track_new();
1622 g_signal_connect(priv->gps_track, "point-added",
1623 G_CALLBACK(on_gps_point_added), object);
1624 g_signal_connect(priv->gps_track, "notify",
1625 G_CALLBACK(on_track_changed), object);
1626
1627 priv->tracks = NULL;
1628 priv->images = NULL;
1629 priv->layers = NULL;
1630
1631 priv->drag_counter = 0;
1632 priv->drag_mouse_dx = 0;
1633 priv->drag_mouse_dy = 0;
1634 priv->drag_start_mouse_x = 0;
1635 priv->drag_start_mouse_y = 0;
1636
1637 priv->uri_format = 0;
1638 priv->is_google = FALSE;
1639
1640 priv->map_source = -1;
1641
1642 priv->keybindings_enabled = FALSE;
1643 for (i = 0; i < OSM_GPS_MAP_KEY_MAX; i++)
1644 priv->keybindings[i] = 0;
1645
1646
1647 /* set the user agent */
1648 priv->soup_session =
1649 soup_session_async_new_with_options(SOUP_SESSION_USER_AGENT,
1650 USER_AGENT, NULL);
1651
1652 /* Hash table which maps tile d/l URIs to SoupMessage requests, the hashtable
1653 must free the key, the soup session unrefs the message */
1654 priv->tile_queue = g_hash_table_new_full (g_str_hash, g_str_equal,
1655 g_free, NULL);
1656
1657 //Some mapping providers (Google) have varying degrees of tiles at multiple
1658 //zoom levels
1659 priv->missing_tiles = g_hash_table_new (g_str_hash, g_str_equal);
1660
1661 /* memory cache for most recently used tiles */
1662 priv->tile_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
1663 g_free, (GDestroyNotify)cached_tile_free);
1664 priv->max_tile_cache_size = 20;
1665
1666 gtk_widget_add_events (GTK_WIDGET (object),
1667 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1668 GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK |
1669 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
1670 #ifdef HAVE_GDK_EVENT_GET_SCROLL_DELTAS
1671 gtk_widget_add_events (GTK_WIDGET (object), GDK_SMOOTH_SCROLL_MASK)
1672 #endif
1673 gtk_widget_set_can_focus (GTK_WIDGET (object), TRUE);
1674
1675 g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MASK, my_log_handler, NULL);
1676
1677 /* setup signal handlers */
1678 g_signal_connect(object, "key_press_event",
1679 G_CALLBACK(on_window_key_press), priv);
1680 }
1681
1682 static char*
osm_gps_map_get_cache_base_dir(OsmGpsMapPrivate * priv)1683 osm_gps_map_get_cache_base_dir(OsmGpsMapPrivate *priv)
1684 {
1685 if (priv->tile_base_dir)
1686 return g_strdup(priv->tile_base_dir);
1687 return osm_gps_map_get_default_cache_directory();
1688 }
1689
1690 static void
osm_gps_map_setup(OsmGpsMap * map)1691 osm_gps_map_setup(OsmGpsMap *map)
1692 {
1693 const char *uri;
1694 OsmGpsMapPrivate *priv = map->priv;
1695
1696 /* user can specify a map source ID, or a repo URI as the map source */
1697 uri = osm_gps_map_source_get_repo_uri(OSM_GPS_MAP_SOURCE_NULL);
1698 if ( (priv->map_source == 0) || (strcmp(priv->repo_uri, uri) == 0) ) {
1699 g_debug("Using null source");
1700 priv->map_source = OSM_GPS_MAP_SOURCE_NULL;
1701
1702 priv->null_tile = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 256, 256);
1703 gdk_pixbuf_fill(priv->null_tile, 0xcccccc00);
1704 }
1705 else if (priv->map_source >= 0) {
1706 /* check if the source given is valid */
1707 uri = osm_gps_map_source_get_repo_uri(priv->map_source);
1708 if (uri) {
1709 g_debug("Setting map source from ID");
1710 g_free(priv->repo_uri);
1711
1712 priv->repo_uri = g_strdup(uri);
1713
1714 g_free(priv->image_format);
1715 priv->image_format = g_strdup(
1716 osm_gps_map_source_get_image_format(priv->map_source));
1717 priv->max_zoom = osm_gps_map_source_get_max_zoom(priv->map_source);
1718 priv->min_zoom = osm_gps_map_source_get_min_zoom(priv->map_source);
1719 }
1720 }
1721 /* parse the source uri */
1722 inspect_map_uri(priv);
1723
1724 /* setup the tile cache */
1725 if ( g_strcmp0(priv->tile_dir, OSM_GPS_MAP_CACHE_DISABLED) == 0 ) {
1726 g_free(priv->cache_dir);
1727 priv->cache_dir = NULL;
1728 } else if ( g_strcmp0(priv->tile_dir, OSM_GPS_MAP_CACHE_AUTO) == 0 ) {
1729 char *base = osm_gps_map_get_cache_base_dir(priv);
1730 char *md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, priv->repo_uri, -1);
1731 g_free(priv->cache_dir);
1732 priv->cache_dir = g_strdup_printf("%s%c%s", base, G_DIR_SEPARATOR, md5);
1733 g_free(base);
1734 g_free(md5);
1735 } else if ( g_strcmp0(priv->tile_dir, OSM_GPS_MAP_CACHE_FRIENDLY) == 0 ) {
1736 char *base = osm_gps_map_get_cache_base_dir(priv);
1737 const char *fname = osm_gps_map_source_get_friendly_name(priv->map_source);
1738 g_free(priv->cache_dir);
1739 priv->cache_dir = g_strdup_printf("%s%c%s", base, G_DIR_SEPARATOR, fname);
1740 g_free(base);
1741 } else {
1742 /* the simple case is handled in g_object_set(PROP_TILE_CACHE_DIR) */
1743 }
1744 g_debug("Cache dir: %s", priv->cache_dir);
1745
1746 /* check if we are being called for a second (or more) time in the lifetime
1747 of the object, and if so, do some extra cleanup */
1748 if ( priv->is_constructed ) {
1749 g_debug("Setup called again in map lifetime");
1750 /* flush the ram cache */
1751 g_hash_table_remove_all(priv->tile_cache);
1752
1753 /* adjust zoom if necessary */
1754 if(priv->map_zoom > priv->max_zoom)
1755 osm_gps_map_set_zoom(map, priv->max_zoom);
1756
1757 if(priv->map_zoom < priv->min_zoom)
1758 osm_gps_map_set_zoom(map, priv->min_zoom);
1759
1760 osm_gps_map_map_redraw_idle(map);
1761 }
1762 }
1763
1764 static GObject *
osm_gps_map_constructor(GType gtype,guint n_properties,GObjectConstructParam * properties)1765 osm_gps_map_constructor (GType gtype, guint n_properties, GObjectConstructParam *properties)
1766 {
1767 GObject *object;
1768 OsmGpsMap *map;
1769
1770 /* always chain up to the parent constructor */
1771 object = G_OBJECT_CLASS(osm_gps_map_parent_class)->constructor(gtype, n_properties, properties);
1772
1773 map = OSM_GPS_MAP(object);
1774
1775 osm_gps_map_setup(map);
1776 map->priv->is_constructed = TRUE;
1777
1778 return object;
1779 }
1780
1781 static void
osm_gps_map_dispose(GObject * object)1782 osm_gps_map_dispose (GObject *object)
1783 {
1784 OsmGpsMap *map = OSM_GPS_MAP(object);
1785 OsmGpsMapPrivate *priv = map->priv;
1786
1787 if (priv->is_disposed)
1788 return;
1789
1790 priv->is_disposed = TRUE;
1791
1792 soup_session_abort(priv->soup_session);
1793 g_object_unref(priv->soup_session);
1794
1795 g_object_unref(priv->gps_track);
1796
1797 g_hash_table_destroy(priv->tile_queue);
1798 g_hash_table_destroy(priv->missing_tiles);
1799 g_hash_table_destroy(priv->tile_cache);
1800
1801 /* images and layers contain GObjects which need unreffing, so free here */
1802 gslist_of_gobjects_free(&priv->images);
1803 gslist_of_gobjects_free(&priv->layers);
1804 gslist_of_gobjects_free(&priv->tracks);
1805
1806 if(priv->pixmap)
1807 cairo_surface_destroy (priv->pixmap);
1808
1809 if (priv->null_tile)
1810 g_object_unref (priv->null_tile);
1811
1812 if (priv->idle_map_redraw != 0)
1813 g_source_remove (priv->idle_map_redraw);
1814
1815 if (priv->drag_expose_source != 0)
1816 g_source_remove (priv->drag_expose_source);
1817
1818 g_free(priv->gps);
1819
1820
1821 G_OBJECT_CLASS (osm_gps_map_parent_class)->dispose (object);
1822 }
1823
1824 static void
osm_gps_map_finalize(GObject * object)1825 osm_gps_map_finalize (GObject *object)
1826 {
1827 OsmGpsMap *map = OSM_GPS_MAP(object);
1828 OsmGpsMapPrivate *priv = map->priv;
1829
1830 if (priv->tile_dir)
1831 g_free(priv->tile_dir);
1832
1833 g_free(priv->tile_base_dir);
1834
1835 if (priv->cache_dir)
1836 g_free(priv->cache_dir);
1837
1838 g_free(priv->repo_uri);
1839 g_free(priv->proxy_uri);
1840 g_free(priv->image_format);
1841
1842 /* trip and tracks contain simple non GObject types, so free them here */
1843 gslist_of_data_free(&priv->trip_history);
1844
1845 G_OBJECT_CLASS (osm_gps_map_parent_class)->finalize (object);
1846 }
1847
1848 static void
osm_gps_map_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1849 osm_gps_map_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
1850 {
1851 g_return_if_fail (OSM_IS_GPS_MAP (object));
1852 OsmGpsMap *map = OSM_GPS_MAP(object);
1853 OsmGpsMapPrivate *priv = map->priv;
1854
1855 switch (prop_id)
1856 {
1857 case PROP_AUTO_CENTER:
1858 priv->map_auto_center_enabled = g_value_get_boolean (value);
1859 break;
1860 case PROP_RECORD_TRIP_HISTORY:
1861 priv->trip_history_record_enabled = g_value_get_boolean (value);
1862 break;
1863 case PROP_SHOW_TRIP_HISTORY:
1864 priv->trip_history_show_enabled = g_value_get_boolean (value);
1865 break;
1866 case PROP_AUTO_DOWNLOAD:
1867 priv->map_auto_download_enabled = g_value_get_boolean (value);
1868 break;
1869 case PROP_REPO_URI:
1870 g_free(priv->repo_uri);
1871 priv->repo_uri = g_value_dup_string (value);
1872 break;
1873 case PROP_PROXY_URI:
1874 if ( g_value_get_string(value) ) {
1875 g_free(priv->proxy_uri);
1876 priv->proxy_uri = g_value_dup_string (value);
1877 g_debug("Setting proxy server: %s", priv->proxy_uri);
1878
1879 GValue val = {0};
1880 SoupURI* uri = soup_uri_new(priv->proxy_uri);
1881 g_value_init(&val, SOUP_TYPE_URI);
1882 g_value_take_boxed(&val, uri);
1883 g_object_set_property(G_OBJECT(priv->soup_session),SOUP_SESSION_PROXY_URI,&val);
1884
1885 } else {
1886 g_free(priv->proxy_uri);
1887 priv->proxy_uri = NULL;
1888 }
1889 break;
1890 case PROP_TILE_CACHE_DIR:
1891 if ( g_value_get_string(value) ) {
1892 g_free(priv->tile_dir);
1893 priv->tile_dir = g_value_dup_string (value);
1894 if ((g_strcmp0(priv->tile_dir, OSM_GPS_MAP_CACHE_DISABLED) == 0) ||
1895 (g_strcmp0(priv->tile_dir, OSM_GPS_MAP_CACHE_AUTO) == 0) ||
1896 (g_strcmp0(priv->tile_dir, OSM_GPS_MAP_CACHE_FRIENDLY) == 0)) {
1897 /* this case is handled by osm_gps_map_setup */
1898 } else {
1899 if (priv->cache_dir)
1900 g_free(priv->cache_dir);
1901 priv->cache_dir = g_strdup(priv->tile_dir);
1902 g_debug("Cache dir: %s", priv->cache_dir);
1903 }
1904 } else {
1905 if (priv->tile_dir)
1906 g_free(priv->tile_dir);
1907 priv->tile_dir = g_strdup(OSM_GPS_MAP_CACHE_DISABLED);
1908 }
1909 break;
1910 case PROP_TILE_CACHE_BASE_DIR:
1911 g_free(priv->tile_base_dir);
1912 priv->tile_base_dir = g_value_dup_string (value);
1913 break;
1914 case PROP_TILE_ZOOM_OFFSET:
1915 priv->tile_zoom_offset = g_value_get_int (value);
1916 break;
1917 case PROP_ZOOM:
1918 priv->map_zoom = g_value_get_int (value);
1919 break;
1920 case PROP_MAX_ZOOM:
1921 priv->max_zoom = g_value_get_int (value);
1922 break;
1923 case PROP_MIN_ZOOM:
1924 priv->min_zoom = g_value_get_int (value);
1925 break;
1926 case PROP_MAP_X:
1927 priv->map_x = g_value_get_int (value);
1928 center_coord_update(map);
1929 break;
1930 case PROP_MAP_Y:
1931 priv->map_y = g_value_get_int (value);
1932 center_coord_update(map);
1933 break;
1934 case PROP_GPS_TRACK_WIDTH:
1935 g_object_set (priv->gps_track,
1936 "line-width", g_value_get_float (value),
1937 NULL);
1938 break;
1939 case PROP_GPS_POINT_R1:
1940 priv->ui_gps_point_inner_radius = g_value_get_int (value);
1941 break;
1942 case PROP_GPS_POINT_R2:
1943 priv->ui_gps_point_outer_radius = g_value_get_int (value);
1944 break;
1945 case PROP_MAP_SOURCE: {
1946 gint old = priv->map_source;
1947 priv->map_source = g_value_get_int (value);
1948 if(old >= OSM_GPS_MAP_SOURCE_NULL &&
1949 priv->map_source != old &&
1950 priv->map_source >= OSM_GPS_MAP_SOURCE_NULL &&
1951 priv->map_source <= OSM_GPS_MAP_SOURCE_LAST) {
1952
1953 if (!priv->is_constructed)
1954 g_critical("Map source setup called twice");
1955
1956 /* we now have to switch the entire map */
1957 osm_gps_map_setup(map);
1958
1959 } } break;
1960 case PROP_IMAGE_FORMAT:
1961 g_free(priv->image_format);
1962 priv->image_format = g_value_dup_string (value);
1963 break;
1964 case PROP_DRAG_LIMIT:
1965 priv->drag_limit = g_value_get_int (value);
1966 break;
1967 case PROP_AUTO_CENTER_THRESHOLD:
1968 priv->map_auto_center_threshold = g_value_get_float (value);
1969 break;
1970 case PROP_SHOW_GPS_POINT:
1971 priv->gps_point_enabled = g_value_get_boolean (value);
1972 break;
1973 default:
1974 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1975 break;
1976 }
1977 }
1978
1979 static void
osm_gps_map_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1980 osm_gps_map_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
1981 {
1982 g_return_if_fail (OSM_IS_GPS_MAP (object));
1983 OsmGpsMap *map = OSM_GPS_MAP(object);
1984 OsmGpsMapPrivate *priv = map->priv;
1985
1986 switch (prop_id)
1987 {
1988 case PROP_AUTO_CENTER:
1989 g_value_set_boolean(value, priv->map_auto_center_enabled);
1990 break;
1991 case PROP_RECORD_TRIP_HISTORY:
1992 g_value_set_boolean(value, priv->trip_history_record_enabled);
1993 break;
1994 case PROP_SHOW_TRIP_HISTORY:
1995 g_value_set_boolean(value, priv->trip_history_show_enabled);
1996 break;
1997 case PROP_AUTO_DOWNLOAD:
1998 g_value_set_boolean(value, priv->map_auto_download_enabled);
1999 break;
2000 case PROP_REPO_URI:
2001 g_value_set_string(value, priv->repo_uri);
2002 break;
2003 case PROP_PROXY_URI:
2004 g_value_set_string(value, priv->proxy_uri);
2005 break;
2006 case PROP_TILE_CACHE_DIR:
2007 g_value_set_string(value, priv->cache_dir);
2008 break;
2009 case PROP_TILE_CACHE_BASE_DIR:
2010 g_value_set_string(value, priv->tile_base_dir);
2011 break;
2012 case PROP_TILE_ZOOM_OFFSET:
2013 g_value_set_int(value, priv->tile_zoom_offset);
2014 break;
2015 case PROP_ZOOM:
2016 g_value_set_int(value, priv->map_zoom);
2017 break;
2018 case PROP_MAX_ZOOM:
2019 g_value_set_int(value, priv->max_zoom);
2020 break;
2021 case PROP_MIN_ZOOM:
2022 g_value_set_int(value, priv->min_zoom);
2023 break;
2024 case PROP_LATITUDE:
2025 g_value_set_float(value, rad2deg(priv->center_rlat));
2026 break;
2027 case PROP_LONGITUDE:
2028 g_value_set_float(value, rad2deg(priv->center_rlon));
2029 break;
2030 case PROP_MAP_X:
2031 g_value_set_int(value, priv->map_x);
2032 break;
2033 case PROP_MAP_Y:
2034 g_value_set_int(value, priv->map_y);
2035 break;
2036 case PROP_TILES_QUEUED:
2037 g_value_set_int(value, g_hash_table_size(priv->tile_queue));
2038 break;
2039 case PROP_GPS_TRACK_WIDTH: {
2040 gfloat f;
2041 g_object_get (priv->gps_track, "line-width", &f, NULL);
2042 g_value_set_float (value, f);
2043 } break;
2044 case PROP_GPS_POINT_R1:
2045 g_value_set_int(value, priv->ui_gps_point_inner_radius);
2046 break;
2047 case PROP_GPS_POINT_R2:
2048 g_value_set_int(value, priv->ui_gps_point_outer_radius);
2049 break;
2050 case PROP_MAP_SOURCE:
2051 g_value_set_int(value, priv->map_source);
2052 break;
2053 case PROP_IMAGE_FORMAT:
2054 g_value_set_string(value, priv->image_format);
2055 break;
2056 case PROP_DRAG_LIMIT:
2057 g_value_set_int(value, priv->drag_limit);
2058 break;
2059 case PROP_AUTO_CENTER_THRESHOLD:
2060 g_value_set_float(value, priv->map_auto_center_threshold);
2061 break;
2062 case PROP_SHOW_GPS_POINT:
2063 g_value_set_boolean(value, priv->gps_point_enabled);
2064 break;
2065 default:
2066 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2067 break;
2068 }
2069 }
2070
2071 static gboolean
osm_gps_map_scroll_event(GtkWidget * widget,GdkEventScroll * event)2072 osm_gps_map_scroll_event (GtkWidget *widget, GdkEventScroll *event)
2073 {
2074 OsmGpsMap *map;
2075 OsmGpsMapPoint *pt;
2076 float lat, lon, c_lat, c_lon;
2077
2078 map = OSM_GPS_MAP(widget);
2079 pt = osm_gps_map_point_new_degrees(0.0,0.0);
2080 /* arguably we could use get_event_location here, but I'm not convinced it
2081 is forward compatible to cast between GdkEventScroll and GtkEventButton */
2082 osm_gps_map_convert_screen_to_geographic(map, event->x, event->y, pt);
2083 osm_gps_map_point_get_degrees (pt, &lat, &lon);
2084
2085 c_lat = rad2deg(map->priv->center_rlat);
2086 c_lon = rad2deg(map->priv->center_rlon);
2087
2088
2089
2090 if ((event->direction == GDK_SCROLL_UP) && (map->priv->map_zoom < map->priv->max_zoom)) {
2091 lat = c_lat + ((lat - c_lat)/2.0);
2092 lon = c_lon + ((lon - c_lon)/2.0);
2093 osm_gps_map_set_center_and_zoom(map, lat, lon, map->priv->map_zoom+1);
2094 } else if ((event->direction == GDK_SCROLL_DOWN) && (map->priv->map_zoom > map->priv->min_zoom)) {
2095 lat = c_lat + ((c_lat - lat)*1.0);
2096 lon = c_lon + ((c_lon - lon)*1.0);
2097 osm_gps_map_set_center_and_zoom(map, lat, lon, map->priv->map_zoom-1);
2098 }
2099
2100 osm_gps_map_point_free (pt);
2101
2102 return FALSE;
2103 }
2104
2105 static gboolean
osm_gps_map_button_press(GtkWidget * widget,GdkEventButton * event)2106 osm_gps_map_button_press (GtkWidget *widget, GdkEventButton *event)
2107 {
2108 OsmGpsMap *map = OSM_GPS_MAP(widget);
2109 OsmGpsMapPrivate *priv = map->priv;
2110
2111 if (priv->layers)
2112 {
2113 GSList *list;
2114 for(list = priv->layers; list != NULL; list = list->next)
2115 {
2116 OsmGpsMapLayer *layer = list->data;
2117 if (osm_gps_map_layer_button_press(layer, map, event))
2118 return FALSE;
2119 }
2120 }
2121
2122 if(event->button == 1)
2123 {
2124 GSList* tracks = priv->tracks;
2125 while(tracks)
2126 {
2127 OsmGpsMapTrack* track = tracks->data;
2128 gboolean path_editable = FALSE;
2129 g_object_get(track, "editable", &path_editable, NULL);
2130 if(path_editable)
2131 {
2132 GSList* points = osm_gps_map_track_get_points(track);
2133 int ctr = 0;
2134 int last_x = 0;
2135 int last_y = 0;
2136 while(points)
2137 {
2138 //if the mouse has gone down on a point, start dragging it
2139 int cx, cy;
2140 OsmGpsMapPoint* point = (OsmGpsMapPoint*)points->data;
2141 osm_gps_map_convert_geographic_to_screen(map, point, &cx, &cy);
2142
2143 float dist_sqrd = (event->x - cx) * (event->x-cx) + (event->y-cy) * (event->y-cy);
2144 if(dist_sqrd <= ((DOT_RADIUS + 1) * (DOT_RADIUS + 1)))
2145 {
2146 priv->is_button_down = TRUE;
2147 priv->drag_point = point;
2148 priv->drag_track = track;
2149 priv->is_dragging_point = TRUE;
2150 osm_gps_map_map_redraw(map);
2151 return FALSE;
2152 }
2153
2154 //add a new point if a 'breaker' has been clicked
2155 if(ctr != 0)
2156 {
2157 int ptx = (last_x+cx)/2.0;
2158 int pty = (last_y+cy)/2.0;
2159 dist_sqrd = (event->x - ptx) * (event->x-ptx) + (event->y-pty) * (event->y-pty);
2160 if(dist_sqrd <= ((DOT_RADIUS + 1) * (DOT_RADIUS + 1)))
2161 {
2162 OsmGpsMapPoint* newpoint = malloc(sizeof(OsmGpsMapPoint));
2163 osm_gps_map_convert_screen_to_geographic(map, ptx, pty, newpoint);
2164 osm_gps_map_track_insert_point(track, newpoint, ctr);
2165 osm_gps_map_map_redraw(map);
2166 return FALSE;
2167 }
2168 }
2169
2170 last_x = cx;
2171 last_y = cy;
2172 points = points->next;
2173 ctr++;
2174 }
2175 }
2176 tracks = tracks->next;
2177 }
2178
2179 GSList* polys = priv->polygons;
2180 while(polys)
2181 {
2182 OsmGpsMapPolygon* poly = polys->data;
2183 gboolean path_editable = FALSE;
2184 OsmGpsMapTrack* track = osm_gps_map_polygon_get_track(poly);
2185 g_object_get(poly, "editable", &path_editable, NULL);
2186 if(path_editable)
2187 {
2188 GSList* points = osm_gps_map_track_get_points(track);
2189 int ctr = 0;
2190 int last_x = 0;
2191 int last_y = 0;
2192 int first_x = 0; int first_y = 0;
2193 while(points)
2194 {
2195 //if the mouse has gone down on a point, start dragging it
2196 int cx, cy;
2197 OsmGpsMapPoint* point = (OsmGpsMapPoint*)points->data;
2198 osm_gps_map_convert_geographic_to_screen(map, point, &cx, &cy);
2199
2200 float dist_sqrd = (event->x - cx) * (event->x-cx) + (event->y-cy) * (event->y-cy);
2201 if(dist_sqrd <= ((DOT_RADIUS + 1) * (DOT_RADIUS + 1)))
2202 {
2203 priv->is_button_down = TRUE;
2204 priv->drag_point = point;
2205 priv->drag_track = track;
2206 priv->is_dragging_point = TRUE;
2207 osm_gps_map_map_redraw(map);
2208 return FALSE;
2209 }
2210
2211 //add a new point if a 'breaker' has been clicked
2212 if(ctr != 0)
2213 {
2214 int ptx = (last_x+cx)/2.0;
2215 int pty = (last_y+cy)/2.0;
2216 dist_sqrd = (event->x - ptx) * (event->x-ptx) + (event->y-pty) * (event->y-pty);
2217 if(dist_sqrd <= ((DOT_RADIUS + 1) * (DOT_RADIUS + 1)))
2218 {
2219 OsmGpsMapPoint* newpoint = malloc(sizeof(OsmGpsMapPoint));
2220 osm_gps_map_convert_screen_to_geographic(map, ptx, pty, newpoint);
2221 osm_gps_map_track_insert_point(track, newpoint, ctr);
2222 osm_gps_map_map_redraw(map);
2223 return FALSE;
2224 }
2225 }
2226 else
2227 {
2228 first_x = cx; first_y = cy;
2229 }
2230
2231 last_x = cx;
2232 last_y = cy;
2233 points = points->next;
2234 ctr++;
2235 }
2236
2237 int ptx = (last_x+first_x)/2.0;
2238 int pty = (last_y+first_y)/2.0;
2239 float dist_sqrd = (event->x - ptx) * (event->x-ptx) + (event->y-pty) * (event->y-pty);
2240 if(dist_sqrd <= ((DOT_RADIUS + 1) * (DOT_RADIUS + 1)))
2241 {
2242 OsmGpsMapPoint* newpoint = malloc(sizeof(OsmGpsMapPoint));
2243 osm_gps_map_convert_screen_to_geographic(map, ptx, pty, newpoint);
2244 osm_gps_map_track_insert_point(track, newpoint, ctr);
2245 osm_gps_map_map_redraw(map);
2246 return FALSE;
2247 }
2248 }
2249 polys = polys->next;
2250 }
2251 }
2252
2253 priv->is_button_down = TRUE;
2254 priv->drag_counter = 0;
2255 priv->drag_start_mouse_x = (int) event->x;
2256 priv->drag_start_mouse_y = (int) event->y;
2257 priv->drag_start_map_x = priv->map_x;
2258 priv->drag_start_map_y = priv->map_y;
2259
2260 return FALSE;
2261 }
2262
2263 static gboolean
osm_gps_map_button_release(GtkWidget * widget,GdkEventButton * event)2264 osm_gps_map_button_release (GtkWidget *widget, GdkEventButton *event)
2265 {
2266 OsmGpsMap *map = OSM_GPS_MAP(widget);
2267 OsmGpsMapPrivate *priv = map->priv;
2268
2269 if(!priv->is_button_down)
2270 return FALSE;
2271
2272 if (priv->is_dragging)
2273 {
2274 priv->is_dragging = FALSE;
2275
2276 priv->map_x = priv->drag_start_map_x;
2277 priv->map_y = priv->drag_start_map_y;
2278
2279 priv->map_x += (priv->drag_start_mouse_x - (int) event->x);
2280 priv->map_y += (priv->drag_start_mouse_y - (int) event->y);
2281
2282 center_coord_update(map);
2283
2284 osm_gps_map_map_redraw_idle(map);
2285 }
2286
2287 if( priv->is_dragging_point)
2288 {
2289 priv->is_dragging_point = FALSE;
2290 osm_gps_map_convert_screen_to_geographic(map, event->x, event->y, priv->drag_point);
2291 g_signal_emit_by_name(priv->drag_track, "point-changed");
2292 }
2293
2294 priv->drag_counter = -1;
2295 priv->is_button_down = FALSE;
2296
2297 return FALSE;
2298 }
2299
2300 static gboolean
osm_gps_map_idle_expose(GtkWidget * widget)2301 osm_gps_map_idle_expose (GtkWidget *widget)
2302 {
2303 OsmGpsMapPrivate *priv = OSM_GPS_MAP(widget)->priv;
2304 priv->drag_expose_source = 0;
2305 gtk_widget_queue_draw (widget);
2306 return FALSE;
2307 }
2308
2309 static gboolean
osm_gps_map_motion_notify(GtkWidget * widget,GdkEventMotion * event)2310 osm_gps_map_motion_notify (GtkWidget *widget, GdkEventMotion *event)
2311 {
2312 GdkModifierType state;
2313 OsmGpsMap *map = OSM_GPS_MAP(widget);
2314 OsmGpsMapPrivate *priv = map->priv;
2315 gint x, y;
2316
2317 GdkDeviceManager* manager = gdk_display_get_device_manager( gdk_display_get_default() );
2318 GdkDevice* pointer = gdk_device_manager_get_client_pointer( manager);
2319
2320 if(!priv->is_button_down)
2321 return FALSE;
2322
2323 if(priv->is_dragging_point)
2324 {
2325 osm_gps_map_convert_screen_to_geographic(map, event->x, event->y, priv->drag_point);
2326 osm_gps_map_map_redraw_idle(map);
2327 return FALSE;
2328 }
2329
2330 if (event->is_hint)
2331 // gdk_window_get_pointer (event->window, &x, &y, &state);
2332 gdk_window_get_device_position( event->window, pointer, &x, &y, &state);
2333
2334 else
2335 {
2336 x = event->x;
2337 y = event->y;
2338 state = event->state;
2339 }
2340
2341 // are we being dragged
2342 if (!(state & GDK_BUTTON1_MASK))
2343 return FALSE;
2344
2345 if (priv->drag_counter < 0)
2346 return FALSE;
2347
2348 /* not yet dragged far enough? */
2349 if(!priv->drag_counter &&
2350 ( (x - priv->drag_start_mouse_x) * (x - priv->drag_start_mouse_x) +
2351 (y - priv->drag_start_mouse_y) * (y - priv->drag_start_mouse_y) <
2352 priv->drag_limit*priv->drag_limit))
2353 return FALSE;
2354
2355 priv->drag_counter++;
2356
2357 priv->is_dragging = TRUE;
2358
2359 if (priv->map_auto_center_enabled)
2360 g_object_set(G_OBJECT(widget), "auto-center", FALSE, NULL);
2361
2362 priv->drag_mouse_dx = x - priv->drag_start_mouse_x;
2363 priv->drag_mouse_dy = y - priv->drag_start_mouse_y;
2364
2365 /* instead of redrawing directly just add an idle function */
2366 if (!priv->drag_expose_source)
2367 priv->drag_expose_source =
2368 g_idle_add ((GSourceFunc)osm_gps_map_idle_expose, widget);
2369
2370 return FALSE;
2371 }
2372
2373 static gboolean
osm_gps_map_configure(GtkWidget * widget,GdkEventConfigure * event)2374 osm_gps_map_configure (GtkWidget *widget, GdkEventConfigure *event)
2375 {
2376 int w,h;
2377 GdkWindow *window;
2378 OsmGpsMap *map = OSM_GPS_MAP(widget);
2379 OsmGpsMapPrivate *priv = map->priv;
2380
2381 if (priv->pixmap)
2382 cairo_surface_destroy (priv->pixmap);
2383
2384 w = gtk_widget_get_allocated_width (widget);
2385 h = gtk_widget_get_allocated_height (widget);
2386 window = gtk_widget_get_window(widget);
2387
2388 priv->pixmap = gdk_window_create_similar_surface (
2389 window,
2390 CAIRO_CONTENT_COLOR,
2391 w + EXTRA_BORDER * 2,
2392 h + EXTRA_BORDER * 2);
2393
2394 // pixel_x,y, offsets
2395 gint pixel_x = lon2pixel(priv->map_zoom, priv->center_rlon);
2396 gint pixel_y = lat2pixel(priv->map_zoom, priv->center_rlat);
2397
2398 priv->map_x = pixel_x - w/2;
2399 priv->map_y = pixel_y - h/2;
2400
2401 osm_gps_map_map_redraw(OSM_GPS_MAP(widget));
2402
2403 g_signal_emit_by_name(widget, "changed");
2404
2405 return FALSE;
2406 }
2407
2408 static gboolean
osm_gps_map_draw(GtkWidget * widget,cairo_t * cr)2409 osm_gps_map_draw (GtkWidget *widget, cairo_t *cr)
2410 {
2411 OsmGpsMap *map = OSM_GPS_MAP(widget);
2412 OsmGpsMapPrivate *priv = map->priv;
2413
2414 if (!priv->drag_mouse_dx && !priv->drag_mouse_dy) {
2415 cairo_set_source_surface (cr, priv->pixmap, 0, 0);
2416 } else {
2417 cairo_set_source_surface (cr, priv->pixmap,
2418 priv->drag_mouse_dx - EXTRA_BORDER,
2419 priv->drag_mouse_dy - EXTRA_BORDER);
2420 }
2421
2422 cairo_paint (cr);
2423
2424 if (priv->layers) {
2425 GSList *list;
2426 for(list = priv->layers; list != NULL; list = list->next) {
2427 OsmGpsMapLayer *layer = list->data;
2428 osm_gps_map_layer_draw(layer, map, cr);
2429 }
2430 }
2431
2432 return FALSE;
2433 }
2434
2435 static void
osm_gps_map_class_init(OsmGpsMapClass * klass)2436 osm_gps_map_class_init (OsmGpsMapClass *klass)
2437 {
2438 GObjectClass* object_class;
2439 GtkWidgetClass *widget_class;
2440
2441 g_type_class_add_private (klass, sizeof (OsmGpsMapPrivate));
2442
2443 object_class = G_OBJECT_CLASS (klass);
2444 object_class->dispose = osm_gps_map_dispose;
2445 object_class->finalize = osm_gps_map_finalize;
2446 object_class->constructor = osm_gps_map_constructor;
2447 object_class->set_property = osm_gps_map_set_property;
2448 object_class->get_property = osm_gps_map_get_property;
2449
2450 widget_class = GTK_WIDGET_CLASS (klass);
2451 widget_class->draw = osm_gps_map_draw;
2452 widget_class->configure_event = osm_gps_map_configure;
2453 widget_class->button_press_event = osm_gps_map_button_press;
2454 widget_class->button_release_event = osm_gps_map_button_release;
2455 widget_class->motion_notify_event = osm_gps_map_motion_notify;
2456 widget_class->scroll_event = osm_gps_map_scroll_event;
2457 //widget_class->get_preferred_width = osm_gps_map_get_preferred_width;
2458 //widget_class->get_preferred_height = osm_gps_map_get_preferred_height;
2459
2460 /* default implementation of draw_gps_point */
2461 klass->draw_gps_point = osm_gps_map_draw_gps_point;
2462
2463 g_object_class_install_property (object_class,
2464 PROP_AUTO_CENTER,
2465 g_param_spec_boolean ("auto-center",
2466 "auto center",
2467 "map auto center",
2468 TRUE,
2469 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2470
2471 g_object_class_install_property (object_class,
2472 PROP_AUTO_CENTER_THRESHOLD,
2473 g_param_spec_float ("auto-center-threshold",
2474 "auto center threshold",
2475 "the amount of the window the gps point must move before auto centering",
2476 0.0, /* minimum property value */
2477 1.0, /* maximum property value */
2478 0.25,
2479 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2480
2481 g_object_class_install_property (object_class,
2482 PROP_RECORD_TRIP_HISTORY,
2483 g_param_spec_boolean ("record-trip-history",
2484 "record trip history",
2485 "should all gps points be recorded in a trip history",
2486 TRUE,
2487 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2488
2489 g_object_class_install_property (object_class,
2490 PROP_SHOW_TRIP_HISTORY,
2491 g_param_spec_boolean ("show-trip-history",
2492 "show trip history",
2493 "should the recorded trip history be shown on the map",
2494 TRUE,
2495 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2496
2497 /**
2498 * OsmGpsMap:show-gps-point:
2499 *
2500 * Controls whether the current gps point is shown on the map. Note that
2501 * for derived classes that implement the draw_gps_point vfunc, if this
2502 * property is %FALSE
2503 **/
2504 g_object_class_install_property (object_class,
2505 PROP_SHOW_GPS_POINT,
2506 g_param_spec_boolean ("show-gps-point",
2507 "show gps point",
2508 "should the current gps point be shown on the map",
2509 TRUE,
2510 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2511
2512 g_object_class_install_property (object_class,
2513 PROP_AUTO_DOWNLOAD,
2514 g_param_spec_boolean ("auto-download",
2515 "auto download",
2516 "map auto download",
2517 TRUE,
2518 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2519
2520 /**
2521 * OsmGpsMap:repo-uri:
2522 *
2523 * A URI string which defines the location and format to fetch tiles
2524 * for the map. The string is of the format
2525 * "http://tile.openstreetmap.org/#Z/#X/#Y.png". Characters
2526 * that begin with # are treated as tokens and replaced according to
2527 * the following rules;
2528 *
2529 * <itemizedlist>
2530 * <listitem>
2531 * <para>
2532 * \#X - X-tile, slippy map format
2533 * </para>
2534 * </listitem>
2535 * <listitem>
2536 * <para>
2537 * \#Y - Y-tile, slippy map format, mercator projection
2538 * </para>
2539 * </listitem>
2540 * <listitem>
2541 * <para>
2542 * \#Z - Zoom level, where min_zoom >= zoom <= max_zoom
2543 * </para>
2544 * </listitem>
2545 * <listitem>
2546 * <para>
2547 * \#S - Zoom level, where -max_zoom >= (zoom-max_zoom) <= min_zoom
2548 * </para>
2549 * </listitem>
2550 * <listitem>
2551 * <para>
2552 * \#Q - Quad tree format, set of "qrts"
2553 * </para>
2554 * </listitem>
2555 * <listitem>
2556 * <para>
2557 * \#Q0 - Quad tree format, set of "0123"
2558 * </para>
2559 * </listitem>
2560 * <listitem>
2561 * <para>
2562 * \#YS - Not Implemented
2563 * </para>
2564 * </listitem>
2565 * <listitem>
2566 * <para>
2567 * \#R - Random integer in range [0,4]
2568 * </para>
2569 * </listitem>
2570 * </itemizedlist>
2571 *
2572 * <note>
2573 * <para>
2574 * If you do not wish to use the default map tiles (provided by OpenStreeMap)
2575 * it is recommened that you use one of the predefined map sources, and thus
2576 * you should construct the map by setting #OsmGpsMap:map-source and not
2577 * #OsmGpsMap:repo-uri. The #OsmGpsMap:repo-uri property is primarily
2578 * designed for applications that wish complete control of tile repository
2579 * management, or wish to use #OsmGpsMap with a tile repository it does not
2580 * explicitly support.
2581 * </para>
2582 * </note>
2583 **/
2584 g_object_class_install_property (object_class,
2585 PROP_REPO_URI,
2586 g_param_spec_string ("repo-uri",
2587 "repo uri",
2588 "Map source tile repository uri",
2589 OSM_REPO_URI,
2590 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2591
2592 g_object_class_install_property (object_class,
2593 PROP_PROXY_URI,
2594 g_param_spec_string ("proxy-uri",
2595 "proxy uri",
2596 "HTTP proxy uri or NULL",
2597 NULL,
2598 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2599
2600
2601 /**
2602 * OsmGpsMap:tile-cache:
2603 *
2604 * Either a full path or one of the special format URIs
2605 * #OSM_GPS_MAP_CACHE_DISABLED, #OSM_GPS_MAP_CACHE_AUTO,
2606 * #OSM_GPS_MAP_CACHE_FRIENDLY. Also see #OsmGpsMap:tile-cache-base for a
2607 * full understanding.
2608 *
2609 * #OSM_GPS_MAP_CACHE_DISABLED disables the on disk tile cache (so all tiles
2610 * are fetched from the network. #OSM_GPS_MAP_CACHE_AUTO causes the tile
2611 * cache to be /tile-cache-base/md5(repo-uri), where md5 is the md5sum
2612 * of #OsmGpsMap:repo-uri. #OSM_GPS_MAP_CACHE_FRIENDLY
2613 * causes the tile cache to be /tile-cache-base/friendlyname(repo-uri).
2614 *
2615 * Any other string is interpreted as a local path, i.e. /path/to/cache
2616 **/
2617 g_object_class_install_property (object_class,
2618 PROP_TILE_CACHE_DIR,
2619 g_param_spec_string ("tile-cache",
2620 "tile cache",
2621 "Tile cache dir",
2622 OSM_GPS_MAP_CACHE_AUTO,
2623 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2624
2625 /**
2626 * OsmGpsMap:tile-cache-base:
2627 *
2628 * The base directory of the tile cache when you have constructed
2629 * the map with #OsmGpsMap:tile-cache set to #OSM_GPS_MAP_CACHE_AUTO or
2630 * #OSM_GPS_MAP_CACHE_FRIENDLY
2631 *
2632 * The string is interpreted as a local path, i.e. /path/to/cache. If NULL
2633 * is supplied, map tiles are cached starting in the users cache directory,
2634 * (as outlined in the
2635 * <ulink url="http://www.freedesktop.org/wiki/Specifications/basedir-spec">
2636 * <citetitle>XDG Base Directory Specification</citetitle></ulink>). To get the
2637 * base directory where map tiles will be cached call
2638 * osm_gps_map_get_default_cache_directory()
2639 *
2640 **/
2641 g_object_class_install_property (object_class,
2642 PROP_TILE_CACHE_BASE_DIR,
2643 g_param_spec_string ("tile-cache-base",
2644 "tile cache-base",
2645 "Base directory to which friendly and auto paths are appended",
2646 NULL,
2647 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2648
2649 /**
2650 * OsmGpsMap:zoom:
2651 *
2652 * The map zoom level. Connect to ::notify::zoom if you want to be informed
2653 * when this changes.
2654 **/
2655 g_object_class_install_property (object_class,
2656 PROP_ZOOM,
2657 g_param_spec_int ("zoom",
2658 "zoom",
2659 "Map zoom level",
2660 MIN_ZOOM, /* minimum property value */
2661 MAX_ZOOM, /* maximum property value */
2662 3,
2663 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2664
2665 g_object_class_install_property (object_class,
2666 PROP_TILE_ZOOM_OFFSET,
2667 g_param_spec_int ("tile-zoom-offset",
2668 "tile zoom-offset",
2669 "Number of zoom-levels to upsample tiles",
2670 MIN_TILE_ZOOM_OFFSET, /* minimum propery value */
2671 MAX_TILE_ZOOM_OFFSET, /* maximum propery value */
2672 0,
2673 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2674
2675 g_object_class_install_property (object_class,
2676 PROP_MAX_ZOOM,
2677 g_param_spec_int ("max-zoom",
2678 "max zoom",
2679 "Maximum zoom level",
2680 MIN_ZOOM, /* minimum property value */
2681 MAX_ZOOM, /* maximum property value */
2682 OSM_MAX_ZOOM,
2683 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2684
2685 g_object_class_install_property (object_class,
2686 PROP_MIN_ZOOM,
2687 g_param_spec_int ("min-zoom",
2688 "min zoom",
2689 "Minimum zoom level",
2690 MIN_ZOOM, /* minimum property value */
2691 MAX_ZOOM, /* maximum property value */
2692 OSM_MIN_ZOOM,
2693 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2694
2695 g_object_class_install_property (object_class,
2696 PROP_LATITUDE,
2697 g_param_spec_float ("latitude",
2698 "latitude",
2699 "Latitude in degrees",
2700 -90.0, /* minimum property value */
2701 90.0, /* maximum property value */
2702 0,
2703 G_PARAM_READABLE));
2704
2705 g_object_class_install_property (object_class,
2706 PROP_LONGITUDE,
2707 g_param_spec_float ("longitude",
2708 "longitude",
2709 "Longitude in degrees",
2710 -180.0, /* minimum property value */
2711 180.0, /* maximum property value */
2712 0,
2713 G_PARAM_READABLE));
2714
2715 g_object_class_install_property (object_class,
2716 PROP_MAP_X,
2717 g_param_spec_int ("map-x",
2718 "map-x",
2719 "Initial map x location",
2720 G_MININT, /* minimum property value */
2721 G_MAXINT, /* maximum property value */
2722 890,
2723 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2724
2725 g_object_class_install_property (object_class,
2726 PROP_MAP_Y,
2727 g_param_spec_int ("map-y",
2728 "map-y",
2729 "Initial map y location",
2730 G_MININT, /* minimum property value */
2731 G_MAXINT, /* maximum property value */
2732 515,
2733 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2734
2735 /**
2736 * OsmGpsMap:tiles-queued:
2737 *
2738 * The number of tiles currently waiting to download. Connect to
2739 * ::notify::tiles-queued if you want to be informed when this changes
2740 **/
2741 g_object_class_install_property (object_class,
2742 PROP_TILES_QUEUED,
2743 g_param_spec_int ("tiles-queued",
2744 "tiles-queued",
2745 "The number of tiles currently waiting to download",
2746 G_MININT, /* minimum property value */
2747 G_MAXINT, /* maximum property value */
2748 0,
2749 G_PARAM_READABLE));
2750
2751 g_object_class_install_property (object_class,
2752 PROP_GPS_TRACK_WIDTH,
2753 g_param_spec_float ("gps-track-width",
2754 "gps-track-width",
2755 "The width of the lines drawn for the gps track",
2756 1.0, /* minimum property value */
2757 100.0, /* maximum property value */
2758 4.0,
2759 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2760
2761 g_object_class_install_property (object_class,
2762 PROP_GPS_POINT_R1,
2763 g_param_spec_int ("gps-track-point-radius",
2764 "gps-track-point-radius",
2765 "The radius of the gps point inner circle",
2766 0, /* minimum property value */
2767 G_MAXINT, /* maximum property value */
2768 5,
2769 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2770
2771 g_object_class_install_property (object_class,
2772 PROP_GPS_POINT_R2,
2773 g_param_spec_int ("gps-track-highlight-radius",
2774 "gps-track-highlight-radius",
2775 "The radius of the gps point highlight circle",
2776 0, /* minimum property value */
2777 G_MAXINT, /* maximum property value */
2778 20,
2779 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2780
2781 /**
2782 * OsmGpsMap:map-source:
2783 *
2784 * A #OsmGpsMapSource_t representing the tile repository to use
2785 *
2786 * <note>
2787 * <para>
2788 * If you do not wish to use the default map tiles (provided by OpenStreeMap)
2789 * it is recommened that you set this property at construction, instead
2790 * of setting #OsmGpsMap:repo-uri.
2791 * </para>
2792 * </note>
2793 **/
2794 g_object_class_install_property (object_class,
2795 PROP_MAP_SOURCE,
2796 g_param_spec_int ("map-source",
2797 "map source",
2798 "The map source ID",
2799 -1, /* minimum property value */
2800 G_MAXINT, /* maximum property value */
2801 -1,
2802 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
2803
2804 g_object_class_install_property (object_class,
2805 PROP_IMAGE_FORMAT,
2806 g_param_spec_string ("image-format",
2807 "image format",
2808 "The map source tile repository image format (jpg, png)",
2809 OSM_IMAGE_FORMAT,
2810 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2811
2812 g_object_class_install_property (object_class,
2813 PROP_DRAG_LIMIT,
2814 g_param_spec_int ("drag-limit",
2815 "drag limit",
2816 "The number of pixels the user has to move the pointer in order to start dragging",
2817 0, /* minimum property value */
2818 G_MAXINT, /* maximum property value */
2819 10,
2820 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
2821
2822 /**
2823 * OsmGpsMap::changed:
2824 *
2825 * The #OsmGpsMap::changed signal is emitted any time the map zoom or map center
2826 * is chaged (such as by dragging or zooming).
2827 *
2828 * <note>
2829 * <para>
2830 * If you are only interested in the map zoom, then you can simply connect
2831 * to ::notify::zoom
2832 * </para>
2833 * </note>
2834 **/
2835 g_signal_new ("changed", OSM_TYPE_GPS_MAP,
2836 G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
2837 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
2838 }
2839
2840 /**
2841 * osm_gps_map_download_maps:
2842 *
2843 * Downloads all tiles over the supplied zoom range in the rectangular
2844 * region specified by pt1 (north west corner) to pt2 (south east corner)
2845 *
2846 **/
2847 void
osm_gps_map_download_maps(OsmGpsMap * map,OsmGpsMapPoint * pt1,OsmGpsMapPoint * pt2,int zoom_start,int zoom_end)2848 osm_gps_map_download_maps (OsmGpsMap *map, OsmGpsMapPoint *pt1, OsmGpsMapPoint *pt2, int zoom_start, int zoom_end)
2849 {
2850 OsmGpsMapPrivate *priv = map->priv;
2851
2852 if (pt1 && pt2) {
2853 gchar *filename;
2854 int i,j,zoom;
2855 int num_tiles = 0;
2856 zoom_end = CLAMP(zoom_end, priv->min_zoom, priv->max_zoom);
2857 zoom_start = CLAMP(zoom_start, priv->min_zoom, priv->max_zoom);
2858
2859 for(zoom=zoom_start; zoom<=zoom_end; zoom++) {
2860 int x1,y1,x2,y2;
2861
2862 x1 = (int)floor((float)lon2pixel(zoom, pt1->rlon) / (float)TILESIZE);
2863 y1 = (int)floor((float)lat2pixel(zoom, pt1->rlat) / (float)TILESIZE);
2864
2865 x2 = (int)floor((float)lon2pixel(zoom, pt2->rlon) / (float)TILESIZE);
2866 y2 = (int)floor((float)lat2pixel(zoom, pt2->rlat) / (float)TILESIZE);
2867
2868 /* check for insane ranges */
2869 if ( (x2-x1) * (y2-y1) > MAX_DOWNLOAD_TILES ) {
2870 g_warning("Aborting download of zoom level %d and up, because "
2871 "number of tiles would exceed %d", zoom, MAX_DOWNLOAD_TILES);
2872 break;
2873 }
2874
2875 /* loop x1-x2 */
2876 for(i=x1; i<=x2; i++) {
2877 /* loop y1 - y2 */
2878 for(j=y1; j<=y2; j++) {
2879 /* x = i, y = j */
2880 filename = g_strdup_printf("%s%c%d%c%d%c%d.%s",
2881 priv->cache_dir, G_DIR_SEPARATOR,
2882 zoom, G_DIR_SEPARATOR,
2883 i, G_DIR_SEPARATOR,
2884 j,
2885 priv->image_format);
2886 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
2887 osm_gps_map_download_tile(map, zoom, i, j, FALSE);
2888 num_tiles++;
2889 }
2890 g_free(filename);
2891 }
2892 }
2893 g_debug("DL @Z:%d = %d tiles", zoom, num_tiles);
2894 }
2895 }
2896 }
2897
2898 static void
cancel_message(char * key,SoupMessage * value,SoupSession * user_data)2899 cancel_message (char *key, SoupMessage *value, SoupSession *user_data)
2900 {
2901 soup_session_cancel_message (user_data, value, SOUP_STATUS_CANCELLED);
2902 }
2903
2904 /**
2905 * osm_gps_map_download_cancel_all:
2906 *
2907 * Cancels all tiles currently being downloaded. Typically used if you wish to
2908 * cacel a large number of tiles queued using osm_gps_map_download_maps()
2909 *
2910 * Since: 0.7.0
2911 **/
2912 void
osm_gps_map_download_cancel_all(OsmGpsMap * map)2913 osm_gps_map_download_cancel_all (OsmGpsMap *map)
2914 {
2915 OsmGpsMapPrivate *priv = map->priv;
2916 g_hash_table_foreach (priv->tile_queue, (GHFunc)cancel_message, priv->soup_session);
2917 }
2918
2919 /**
2920 * osm_gps_map_get_bbox:
2921 * @pt1: (out): point to be filled with the top left location
2922 * @pt2: (out): point to be filled with the bottom right location
2923 *
2924 * Returns the geographic locations of the bounding box describing the contents
2925 * of the current window, i.e the top left and bottom right corners.
2926 **/
2927 void
osm_gps_map_get_bbox(OsmGpsMap * map,OsmGpsMapPoint * pt1,OsmGpsMapPoint * pt2)2928 osm_gps_map_get_bbox (OsmGpsMap *map, OsmGpsMapPoint *pt1, OsmGpsMapPoint *pt2)
2929 {
2930 GtkAllocation allocation;
2931 OsmGpsMapPrivate *priv = map->priv;
2932
2933 if (pt1 && pt2) {
2934 gtk_widget_get_allocation(GTK_WIDGET(map), &allocation);
2935 pt1->rlat = pixel2lat(priv->map_zoom, priv->map_y);
2936 pt1->rlon = pixel2lon(priv->map_zoom, priv->map_x);
2937 pt2->rlat = pixel2lat(priv->map_zoom, priv->map_y + allocation.height);
2938 pt2->rlon = pixel2lon(priv->map_zoom, priv->map_x + allocation.width);
2939 }
2940 }
2941
2942 /**
2943 * osm_gps_map_zoom_fit_bbox:
2944 * Zoom and center the map so that both points fit inside the window.
2945 **/
2946 void
osm_gps_map_zoom_fit_bbox(OsmGpsMap * map,float latitude1,float latitude2,float longitude1,float longitude2)2947 osm_gps_map_zoom_fit_bbox (OsmGpsMap *map, float latitude1, float latitude2, float longitude1, float longitude2)
2948 {
2949 GtkAllocation allocation;
2950 int zoom;
2951 gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
2952 zoom = latlon2zoom (allocation.height, allocation.width, deg2rad(latitude1), deg2rad(latitude2), deg2rad(longitude1), deg2rad(longitude2));
2953 osm_gps_map_set_center (map, (latitude1 + latitude2) / 2, (longitude1 + longitude2) / 2);
2954 osm_gps_map_set_zoom (map, zoom);
2955 }
2956
2957 /**
2958 * osm_gps_map_set_center_and_zoom:
2959 *
2960 * Since: 0.7.0
2961 **/
osm_gps_map_set_center_and_zoom(OsmGpsMap * map,float latitude,float longitude,int zoom)2962 void osm_gps_map_set_center_and_zoom (OsmGpsMap *map, float latitude, float longitude, int zoom)
2963 {
2964 osm_gps_map_set_center (map, latitude, longitude);
2965 osm_gps_map_set_zoom (map, zoom);
2966 }
2967
2968 /**
2969 * osm_gps_map_set_center:
2970 *
2971 **/
2972 void
osm_gps_map_set_center(OsmGpsMap * map,float latitude,float longitude)2973 osm_gps_map_set_center (OsmGpsMap *map, float latitude, float longitude)
2974 {
2975 int pixel_x, pixel_y;
2976 OsmGpsMapPrivate *priv;
2977 GtkAllocation allocation;
2978
2979 g_return_if_fail (OSM_IS_GPS_MAP (map));
2980
2981 priv = map->priv;
2982 gtk_widget_get_allocation(GTK_WIDGET(map), &allocation);
2983 g_object_set(G_OBJECT(map), "auto-center", FALSE, NULL);
2984
2985 priv->center_rlat = deg2rad(latitude);
2986 priv->center_rlon = deg2rad(longitude);
2987
2988 pixel_x = lon2pixel(priv->map_zoom, priv->center_rlon);
2989 pixel_y = lat2pixel(priv->map_zoom, priv->center_rlat);
2990
2991 priv->map_x = pixel_x - allocation.width/2;
2992 priv->map_y = pixel_y - allocation.height/2;
2993
2994 osm_gps_map_map_redraw_idle(map);
2995
2996 g_signal_emit_by_name(map, "changed");
2997 }
2998
2999 /**
3000 * osm_gps_map_set_zoom_offset:
3001 *
3002 **/
3003 void
osm_gps_map_set_zoom_offset(OsmGpsMap * map,int zoom_offset)3004 osm_gps_map_set_zoom_offset (OsmGpsMap *map, int zoom_offset)
3005 {
3006 OsmGpsMapPrivate *priv;
3007
3008 g_return_if_fail (OSM_GPS_MAP (map));
3009 priv = map->priv;
3010
3011 if (zoom_offset != priv->tile_zoom_offset)
3012 {
3013 priv->tile_zoom_offset = zoom_offset;
3014 osm_gps_map_map_redraw_idle (map);
3015 }
3016 }
3017
3018 /**
3019 * osm_gps_map_set_zoom:
3020 *
3021 **/
3022 int
osm_gps_map_set_zoom(OsmGpsMap * map,int zoom)3023 osm_gps_map_set_zoom (OsmGpsMap *map, int zoom)
3024 {
3025 int width_center, height_center;
3026 OsmGpsMapPrivate *priv;
3027 GtkAllocation allocation;
3028
3029 g_return_val_if_fail (OSM_IS_GPS_MAP (map), 0);
3030 priv = map->priv;
3031
3032 if (zoom != priv->map_zoom)
3033 {
3034 gtk_widget_get_allocation(GTK_WIDGET(map), &allocation);
3035 width_center = allocation.width / 2;
3036 height_center = allocation.height / 2;
3037
3038 /* update zoom but constrain [min_zoom..max_zoom] */
3039 priv->map_zoom = CLAMP(zoom, priv->min_zoom, priv->max_zoom);
3040 priv->map_x = lon2pixel(priv->map_zoom, priv->center_rlon) - width_center;
3041 priv->map_y = lat2pixel(priv->map_zoom, priv->center_rlat) - height_center;
3042
3043 osm_gps_map_map_redraw_idle(map);
3044
3045 g_signal_emit_by_name(map, "changed");
3046 g_object_notify(G_OBJECT(map), "zoom");
3047 }
3048 return priv->map_zoom;
3049 }
3050
3051 /**
3052 * osm_gps_map_zoom_in:
3053 *
3054 **/
3055 int
osm_gps_map_zoom_in(OsmGpsMap * map)3056 osm_gps_map_zoom_in (OsmGpsMap *map)
3057 {
3058 g_return_val_if_fail (OSM_IS_GPS_MAP (map), 0);
3059 return osm_gps_map_set_zoom(map, map->priv->map_zoom+1);
3060 }
3061
3062 /**
3063 * osm_gps_map_zoom_out:
3064 *
3065 **/
3066 int
osm_gps_map_zoom_out(OsmGpsMap * map)3067 osm_gps_map_zoom_out (OsmGpsMap *map)
3068 {
3069 g_return_val_if_fail (OSM_IS_GPS_MAP (map), 0);
3070 return osm_gps_map_set_zoom(map, map->priv->map_zoom-1);
3071 }
3072
3073 /**
3074 * osm_gps_map_new:
3075 *
3076 * Returns a new #OsmGpsMap object, defaults to showing data from
3077 * <ulink url="http://www.openstreetmap.org"><citetitle>OpenStreetMap</citetitle></ulink>
3078 *
3079 * See the properties description for more information about construction
3080 * parameters than could be passed to g_object_new()
3081 *
3082 * Returns: a newly created #OsmGpsMap object.
3083 **/
3084 GtkWidget *
osm_gps_map_new(void)3085 osm_gps_map_new (void)
3086 {
3087 return g_object_new (OSM_TYPE_GPS_MAP, NULL);
3088 }
3089
3090 /**
3091 * osm_gps_map_scroll:
3092 * @map:
3093 * @dx:
3094 * @dy:
3095 *
3096 * Scrolls the map by @dx, @dy pixels (positive north, east)
3097 *
3098 **/
3099 void
osm_gps_map_scroll(OsmGpsMap * map,gint dx,gint dy)3100 osm_gps_map_scroll (OsmGpsMap *map, gint dx, gint dy)
3101 {
3102 OsmGpsMapPrivate *priv;
3103
3104 g_return_if_fail (OSM_IS_GPS_MAP (map));
3105 priv = map->priv;
3106
3107 priv->map_x += dx;
3108 priv->map_y += dy;
3109 center_coord_update(map);
3110
3111 osm_gps_map_map_redraw_idle (map);
3112 }
3113
3114 /**
3115 * osm_gps_map_get_scale:
3116 * @map:
3117 *
3118 * Returns: the scale of the map at the center, in meters/pixel.
3119 *
3120 **/
3121 float
osm_gps_map_get_scale(OsmGpsMap * map)3122 osm_gps_map_get_scale (OsmGpsMap *map)
3123 {
3124 OsmGpsMapPrivate *priv;
3125
3126 g_return_val_if_fail (OSM_IS_GPS_MAP (map), OSM_GPS_MAP_INVALID);
3127 priv = map->priv;
3128
3129 return osm_gps_map_get_scale_at_point(priv->map_zoom, priv->center_rlat, priv->center_rlon);
3130 }
3131
3132 /**
3133 * osm_gps_map_get_default_cache_directory:
3134 *
3135 * Returns: the default cache directory for the library, that is the base
3136 * directory to which the full cache path is appended. If
3137 * #OsmGpsMap:tile-cache-base is omitted from the constructor then this value
3138 * is used.
3139 *
3140 **/
3141 gchar *
osm_gps_map_get_default_cache_directory(void)3142 osm_gps_map_get_default_cache_directory (void)
3143 {
3144 return g_build_filename(
3145 g_get_user_cache_dir(),
3146 "osmgpsmap",
3147 NULL);
3148 }
3149
3150 /**
3151 * osm_gps_map_set_keyboard_shortcut:
3152 * @key: a #OsmGpsMapKey_t
3153 * @keyval:
3154 *
3155 * Associates a keyboard shortcut with the supplied @keyval
3156 * (as returned by #gdk_keyval_from_name or simiar). The action given in @key
3157 * will be triggered when the corresponding @keyval is pressed. By default
3158 * no keyboard shortcuts are associated.
3159 *
3160 **/
3161 void
osm_gps_map_set_keyboard_shortcut(OsmGpsMap * map,OsmGpsMapKey_t key,guint keyval)3162 osm_gps_map_set_keyboard_shortcut (OsmGpsMap *map, OsmGpsMapKey_t key, guint keyval)
3163 {
3164 g_return_if_fail (OSM_IS_GPS_MAP (map));
3165 g_return_if_fail(key < OSM_GPS_MAP_KEY_MAX);
3166
3167 map->priv->keybindings[key] = keyval;
3168 map->priv->keybindings_enabled = TRUE;
3169 }
3170
3171 /**
3172 * osm_gps_map_track_add:
3173 *
3174 * Since: 0.7.0
3175 **/
3176 void
osm_gps_map_track_add(OsmGpsMap * map,OsmGpsMapTrack * track)3177 osm_gps_map_track_add (OsmGpsMap *map, OsmGpsMapTrack *track)
3178 {
3179 OsmGpsMapPrivate *priv;
3180
3181 g_return_if_fail (OSM_IS_GPS_MAP (map));
3182 priv = map->priv;
3183
3184 g_object_ref(track);
3185 g_signal_connect(track, "point-added",
3186 G_CALLBACK(on_gps_point_added), map);
3187 g_signal_connect(track, "notify",
3188 G_CALLBACK(on_track_changed), map);
3189
3190 priv->tracks = g_slist_append(priv->tracks, track);
3191 osm_gps_map_map_redraw_idle(map);
3192 }
3193
3194 /**
3195 * osm_gps_map_track_remove_all:
3196 *
3197 * Since: 0.7.0
3198 **/
3199 void
osm_gps_map_track_remove_all(OsmGpsMap * map)3200 osm_gps_map_track_remove_all (OsmGpsMap *map)
3201 {
3202 g_return_if_fail (OSM_IS_GPS_MAP (map));
3203
3204 gslist_of_gobjects_free(&map->priv->tracks);
3205 osm_gps_map_map_redraw_idle(map);
3206 }
3207
3208 /**
3209 * osm_gps_map_track_remove:
3210 *
3211 * Since: 0.7.0
3212 **/
3213 gboolean
osm_gps_map_track_remove(OsmGpsMap * map,OsmGpsMapTrack * track)3214 osm_gps_map_track_remove (OsmGpsMap *map, OsmGpsMapTrack *track)
3215 {
3216 GSList *data;
3217
3218 g_return_val_if_fail (OSM_IS_GPS_MAP (map), FALSE);
3219 g_return_val_if_fail (track != NULL, FALSE);
3220
3221 data = gslist_remove_one_gobject (&map->priv->tracks, G_OBJECT(track));
3222 osm_gps_map_map_redraw_idle(map);
3223 return data != NULL;
3224 }
3225
3226 void
osm_gps_map_polygon_add(OsmGpsMap * map,OsmGpsMapPolygon * poly)3227 osm_gps_map_polygon_add (OsmGpsMap *map, OsmGpsMapPolygon *poly)
3228 {
3229 OsmGpsMapPrivate *priv;
3230
3231 g_return_if_fail (OSM_IS_GPS_MAP (map));
3232 priv = map->priv;
3233
3234 g_object_ref(poly);
3235
3236 OsmGpsMapTrack* track = osm_gps_map_polygon_get_track(poly);
3237 g_signal_connect(track, "point-added",
3238 G_CALLBACK(on_gps_point_added), map);
3239 g_signal_connect(track, "notify",
3240 G_CALLBACK(on_track_changed), map);
3241
3242 priv->polygons = g_slist_append(priv->polygons, poly);
3243 osm_gps_map_map_redraw_idle(map);
3244 }
3245
3246 void
osm_gps_map_polygon_remove_all(OsmGpsMap * map)3247 osm_gps_map_polygon_remove_all(OsmGpsMap *map)
3248 {
3249 g_return_if_fail (OSM_IS_GPS_MAP (map));
3250
3251 gslist_of_gobjects_free(&map->priv->polygons);
3252 osm_gps_map_map_redraw_idle(map);
3253 }
3254
3255 gboolean
osm_gps_map_polygon_remove(OsmGpsMap * map,OsmGpsMapPolygon * poly)3256 osm_gps_map_polygon_remove(OsmGpsMap *map, OsmGpsMapPolygon *poly)
3257 {
3258 GSList *data;
3259
3260 g_return_val_if_fail (OSM_IS_GPS_MAP (map), FALSE);
3261 g_return_val_if_fail (poly != NULL, FALSE);
3262
3263 data = gslist_remove_one_gobject (&map->priv->polygons, G_OBJECT(poly));
3264 osm_gps_map_map_redraw_idle(map);
3265 return data != NULL;
3266 }
3267
3268
3269 /**
3270 * osm_gps_map_gps_clear:
3271 *
3272 * Since: 0.7.0
3273 **/
3274 void
osm_gps_map_gps_clear(OsmGpsMap * map)3275 osm_gps_map_gps_clear (OsmGpsMap *map)
3276 {
3277 OsmGpsMapPrivate *priv;
3278
3279 g_return_if_fail (OSM_IS_GPS_MAP (map));
3280 priv = map->priv;
3281
3282 g_object_unref(priv->gps_track);
3283 priv->gps_track = osm_gps_map_track_new();
3284 g_signal_connect(priv->gps_track, "point-added",
3285 G_CALLBACK(on_gps_point_added), map);
3286 g_signal_connect(priv->gps_track, "notify",
3287 G_CALLBACK(on_track_changed), map);
3288 osm_gps_map_map_redraw_idle(map);
3289 }
3290
3291 /**
3292 * osm_gps_map_gps_get_track:
3293 *
3294 * Returns: (transfer none): The #OsmGpsMapTrack of the internal GPS track,
3295 * i.e. that which is modified when calling osm_gps_map_gps_add(). You must
3296 * not free this.
3297 * Since: 0.7.0
3298 **/
3299 OsmGpsMapTrack *
osm_gps_map_gps_get_track(OsmGpsMap * map)3300 osm_gps_map_gps_get_track (OsmGpsMap *map)
3301 {
3302 g_return_val_if_fail (OSM_IS_GPS_MAP (map), NULL);
3303 return map->priv->gps_track;
3304 }
3305
3306 /**
3307 * osm_gps_map_gps_add:
3308 * @latitude: degrees
3309 * @longitude: degrees
3310 * @heading: degrees or #OSM_GPS_MAP_INVALID to disable showing heading
3311 *
3312 * Since: 0.7.0
3313 **/
3314 void
osm_gps_map_gps_add(OsmGpsMap * map,float latitude,float longitude,float heading)3315 osm_gps_map_gps_add (OsmGpsMap *map, float latitude, float longitude, float heading)
3316 {
3317 OsmGpsMapPrivate *priv;
3318
3319 g_return_if_fail (OSM_IS_GPS_MAP (map));
3320 priv = map->priv;
3321
3322 /* update the current point */
3323 priv->gps->rlat = deg2rad(latitude);
3324 priv->gps->rlon = deg2rad(longitude);
3325 priv->gps_track_used = TRUE;
3326 priv->gps_heading = deg2rad(heading);
3327
3328 /* If trip marker add to list of gps points */
3329 if (priv->trip_history_record_enabled) {
3330 OsmGpsMapPoint point;
3331 osm_gps_map_point_set_degrees (&point, latitude, longitude);
3332 /* this will cause a redraw to be scheduled */
3333 osm_gps_map_track_add_point (priv->gps_track, &point);
3334 } else {
3335 osm_gps_map_map_redraw_idle (map);
3336 maybe_autocenter_map (map);
3337 }
3338 }
3339
3340 /**
3341 * osm_gps_map_image_add:
3342 *
3343 * Returns: (transfer full): A #OsmGpsMapImage representing the added pixbuf
3344 * Since: 0.7.0
3345 **/
3346 OsmGpsMapImage *
osm_gps_map_image_add(OsmGpsMap * map,float latitude,float longitude,GdkPixbuf * image)3347 osm_gps_map_image_add (OsmGpsMap *map, float latitude, float longitude, GdkPixbuf *image)
3348 {
3349 return osm_gps_map_image_add_with_alignment_z (map, latitude, longitude, image, 0.5, 0.5, 0);
3350 }
3351
3352 /**
3353 * osm_gps_map_image_add_z:
3354 *
3355 * Returns: (transfer full): A #OsmGpsMapImage representing the added pixbuf
3356 * Since: 0.7.4
3357 **/
3358 OsmGpsMapImage *
osm_gps_map_image_add_z(OsmGpsMap * map,float latitude,float longitude,GdkPixbuf * image,gint zorder)3359 osm_gps_map_image_add_z (OsmGpsMap *map, float latitude, float longitude, GdkPixbuf *image, gint zorder)
3360 {
3361 return osm_gps_map_image_add_with_alignment_z (map, latitude, longitude, image, 0.5, 0.5, zorder);
3362 }
3363
3364 static void
on_image_changed(OsmGpsMapImage * image,GParamSpec * pspec,OsmGpsMap * map)3365 on_image_changed (OsmGpsMapImage *image, GParamSpec *pspec, OsmGpsMap *map)
3366 {
3367 osm_gps_map_map_redraw_idle (map);
3368 }
3369
3370 /**
3371 * osm_gps_map_image_add_with_alignment:
3372 *
3373 * Returns: (transfer full): A #OsmGpsMapImage representing the added pixbuf
3374 * Since: 0.7.0
3375 **/
3376 OsmGpsMapImage *
osm_gps_map_image_add_with_alignment(OsmGpsMap * map,float latitude,float longitude,GdkPixbuf * image,float xalign,float yalign)3377 osm_gps_map_image_add_with_alignment (OsmGpsMap *map, float latitude, float longitude, GdkPixbuf *image, float xalign, float yalign)
3378 {
3379 return osm_gps_map_image_add_with_alignment_z (map, latitude, longitude, image, xalign, yalign, 0);
3380 }
3381
3382 static gint
osm_gps_map_image_z_compare(gconstpointer item1,gconstpointer item2)3383 osm_gps_map_image_z_compare(gconstpointer item1, gconstpointer item2)
3384 {
3385 gint z1 = osm_gps_map_image_get_zorder(OSM_GPS_MAP_IMAGE(item1));
3386 gint z2 = osm_gps_map_image_get_zorder(OSM_GPS_MAP_IMAGE(item2));
3387
3388 return(z1 - z2 + 1);
3389 }
3390
3391 /**
3392 * osm_gps_map_image_add_with_alignment_z:
3393 *
3394 * Returns: (transfer full): A #OsmGpsMapImage representing the added pixbuf
3395 * Since: 0.7.4
3396 **/
3397 OsmGpsMapImage *
osm_gps_map_image_add_with_alignment_z(OsmGpsMap * map,float latitude,float longitude,GdkPixbuf * image,float xalign,float yalign,gint zorder)3398 osm_gps_map_image_add_with_alignment_z (OsmGpsMap *map, float latitude, float longitude, GdkPixbuf *image, float xalign, float yalign, gint zorder)
3399 {
3400 OsmGpsMapImage *im;
3401 OsmGpsMapPoint pt;
3402
3403 g_return_val_if_fail (OSM_IS_GPS_MAP (map), NULL);
3404 pt.rlat = deg2rad(latitude);
3405 pt.rlon = deg2rad(longitude);
3406
3407 im = g_object_new (OSM_TYPE_GPS_MAP_IMAGE, "pixbuf", image, "x-align", xalign, "y-align", yalign, "point", &pt, "z-order", zorder, NULL);
3408 g_signal_connect(im, "notify",
3409 G_CALLBACK(on_image_changed), map);
3410
3411 map->priv->images = g_slist_insert_sorted(map->priv->images, im,
3412 (GCompareFunc) osm_gps_map_image_z_compare);
3413 osm_gps_map_map_redraw_idle(map);
3414
3415 g_object_ref(im);
3416 return im;
3417 }
3418
3419 /**
3420 * osm_gps_map_image_remove:
3421 *
3422 * Since: 0.7.0
3423 **/
3424 gboolean
osm_gps_map_image_remove(OsmGpsMap * map,OsmGpsMapImage * image)3425 osm_gps_map_image_remove (OsmGpsMap *map, OsmGpsMapImage *image)
3426 {
3427 GSList *data;
3428
3429 g_return_val_if_fail (OSM_IS_GPS_MAP (map), FALSE);
3430 g_return_val_if_fail (image != NULL, FALSE);
3431
3432 data = gslist_remove_one_gobject (&map->priv->images, G_OBJECT(image));
3433 osm_gps_map_map_redraw_idle(map);
3434 return data != NULL;
3435 }
3436
3437 /**
3438 * osm_gps_map_image_remove_all:
3439 *
3440 * Since: 0.7.0
3441 **/
3442 void
osm_gps_map_image_remove_all(OsmGpsMap * map)3443 osm_gps_map_image_remove_all (OsmGpsMap *map)
3444 {
3445 g_return_if_fail (OSM_IS_GPS_MAP (map));
3446
3447 gslist_of_gobjects_free(&map->priv->images);
3448 osm_gps_map_map_redraw_idle(map);
3449 }
3450
3451 /**
3452 * osm_gps_map_layer_add:
3453 * @layer: a #OsmGpsMapLayer object
3454 *
3455 * Since: 0.7.0
3456 **/
3457 void
osm_gps_map_layer_add(OsmGpsMap * map,OsmGpsMapLayer * layer)3458 osm_gps_map_layer_add (OsmGpsMap *map, OsmGpsMapLayer *layer)
3459 {
3460 g_return_if_fail (OSM_IS_GPS_MAP (map));
3461 g_return_if_fail (OSM_GPS_MAP_IS_LAYER (layer));
3462
3463 g_object_ref(G_OBJECT(layer));
3464 map->priv->layers = g_slist_append(map->priv->layers, layer);
3465 }
3466
3467 /**
3468 * osm_gps_map_layer_remove:
3469 * @layer: a #OsmGpsMapLayer object
3470 *
3471 * Since: 0.7.0
3472 **/
3473 gboolean
osm_gps_map_layer_remove(OsmGpsMap * map,OsmGpsMapLayer * layer)3474 osm_gps_map_layer_remove (OsmGpsMap *map, OsmGpsMapLayer *layer)
3475 {
3476 GSList *data;
3477
3478 g_return_val_if_fail (OSM_IS_GPS_MAP (map), FALSE);
3479 g_return_val_if_fail (layer != NULL, FALSE);
3480
3481 data = gslist_remove_one_gobject (&map->priv->layers, G_OBJECT(layer));
3482 osm_gps_map_map_redraw_idle(map);
3483 return data != NULL;
3484 }
3485
3486 /**
3487 * osm_gps_map_layer_remove_all:
3488 *
3489 * Since: 0.7.0
3490 **/
3491 void
osm_gps_map_layer_remove_all(OsmGpsMap * map)3492 osm_gps_map_layer_remove_all (OsmGpsMap *map)
3493 {
3494 g_return_if_fail (OSM_IS_GPS_MAP (map));
3495
3496 gslist_of_gobjects_free(&map->priv->layers);
3497 osm_gps_map_map_redraw_idle(map);
3498 }
3499
3500 /**
3501 * osm_gps_map_convert_screen_to_geographic:
3502 * @map:
3503 * @pixel_x: pixel location on map, x axis
3504 * @pixel_y: pixel location on map, y axis
3505 * @pt: (out): location
3506 *
3507 * Convert the given pixel location on the map into corresponding
3508 * location on the globe
3509 *
3510 * Since: 0.7.0
3511 **/
3512 void
osm_gps_map_convert_screen_to_geographic(OsmGpsMap * map,gint pixel_x,gint pixel_y,OsmGpsMapPoint * pt)3513 osm_gps_map_convert_screen_to_geographic(OsmGpsMap *map, gint pixel_x, gint pixel_y, OsmGpsMapPoint *pt)
3514 {
3515 OsmGpsMapPrivate *priv;
3516 int map_x0, map_y0;
3517
3518 g_return_if_fail (OSM_IS_GPS_MAP (map));
3519 g_return_if_fail (pt);
3520
3521 priv = map->priv;
3522 map_x0 = priv->map_x - EXTRA_BORDER;
3523 map_y0 = priv->map_y - EXTRA_BORDER;
3524
3525 pt->rlat = pixel2lat(priv->map_zoom, map_y0 + pixel_y);
3526 pt->rlon = pixel2lon(priv->map_zoom, map_x0 + pixel_x);
3527 }
3528
3529 /**
3530 * osm_gps_map_convert_geographic_to_screen:
3531 * @map:
3532 * @pt: location
3533 * @pixel_x: (out): pixel location on map, x axis
3534 * @pixel_y: (out): pixel location on map, y axis
3535 *
3536 * Convert the given location on the globe to the corresponding
3537 * pixel locations on the map.
3538 *
3539 * Since: 0.7.0
3540 **/
3541 void
osm_gps_map_convert_geographic_to_screen(OsmGpsMap * map,OsmGpsMapPoint * pt,gint * pixel_x,gint * pixel_y)3542 osm_gps_map_convert_geographic_to_screen(OsmGpsMap *map, OsmGpsMapPoint *pt, gint *pixel_x, gint *pixel_y)
3543 {
3544 OsmGpsMapPrivate *priv;
3545 int map_x0, map_y0;
3546
3547 g_return_if_fail (OSM_IS_GPS_MAP (map));
3548 g_return_if_fail (pt);
3549
3550 priv = map->priv;
3551 map_x0 = priv->map_x - EXTRA_BORDER;
3552 map_y0 = priv->map_y - EXTRA_BORDER;
3553
3554 if (pixel_x)
3555 *pixel_x = lon2pixel(priv->map_zoom, pt->rlon) - map_x0 + priv->drag_mouse_dx;
3556 if (pixel_y)
3557 *pixel_y = lat2pixel(priv->map_zoom, pt->rlat) - map_y0 + priv->drag_mouse_dy;
3558 }
3559
3560 /**
3561 * osm_gps_map_get_event_location:
3562 * @map:
3563 * @event: A #GtkEventButton that occured on the map
3564 *
3565 * A convenience function for getting the geographic location of events,
3566 * such as mouse clicks, on the map
3567 *
3568 * Returns: (transfer full): The point on the globe corresponding to the click
3569 * Since: 0.7.0
3570 **/
3571 OsmGpsMapPoint *
osm_gps_map_get_event_location(OsmGpsMap * map,GdkEventButton * event)3572 osm_gps_map_get_event_location (OsmGpsMap *map, GdkEventButton *event)
3573 {
3574 OsmGpsMapPoint *p = osm_gps_map_point_new_degrees(0.0,0.0);
3575 osm_gps_map_convert_screen_to_geographic(map, event->x, event->y, p);
3576 return p;
3577 }
3578
3579