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/&num;Z/&num;X/&num;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/&num;Z/&num;X/&num;Y.png". Characters
2526      * that begin with &num; 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 &gt;= zoom &lt;= max_zoom
2543      * </para>
2544      * </listitem>
2545      * <listitem>
2546      * <para>
2547      * \#S - Zoom level, where -max_zoom &gt;= (zoom-max_zoom) &lt;= 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