Contents of /trunk/src/map-tool.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 89 - (show annotations)
Tue Sep 1 11:16:30 2009 UTC (14 years, 7 months ago) by harbaum
File MIME type: text/plain
File size: 17623 byte(s)
OSD source selection working
1 /*
2 * Copyright (C) 2008-2009 Till Harbaum <till@harbaum.org>.
3 *
4 * This file is part of GPXView.
5 *
6 * GPXView is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * GPXView is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with GPXView. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "gpxview.h"
21 #include "converter.h"
22 #include <math.h> // for isnan
23
24 #ifdef ENABLE_OSM_GPS_MAP
25 #include "osm-gps-map.h"
26 #include "osm-gps-map-osd-classic.h"
27 #endif
28
29 #if defined(USE_MAEMO) && (MAEMO_VERSION_MAJOR == 5)
30 #include <gdk/gdkx.h>
31 #include <X11/Xatom.h>
32 #endif
33
34 // #define MAP_SOURCE OSM_GPS_MAP_SOURCE_OPENSTREETMAP
35 #define MAP_SOURCE OSM_GPS_MAP_SOURCE_OPENCYCLEMAP
36 // #define MAP_SOURCE OSM_GPS_MAP_SOURCE_GOOGLE_STREET
37 #define GPS_DEFAULT_ZOOM 13
38
39 #define PROXY_KEY "/system/http_proxy/"
40
41 static const char *get_proxy_uri(appdata_t *appdata) {
42 static char proxy_buffer[64] = "";
43
44 /* use environment settings if preset */
45 const char *proxy = g_getenv("http_proxy");
46 if(proxy) {
47 printf("http_proxy: %s\n", proxy);
48 return proxy;
49 }
50
51 /* ------------- get proxy settings -------------------- */
52 if(gconf_client_get_bool(appdata->gconf_client,
53 PROXY_KEY "use_http_proxy", NULL)) {
54
55 /* we can savely ignore things like "ignore_hosts" since we */
56 /* are pretty sure not inside the net of one of our map renderers */
57 /* (unless the user works at google :-) */
58
59 /* get basic settings */
60 char *host =
61 gconf_client_get_string(appdata->gconf_client, PROXY_KEY "host", NULL);
62 if(host) {
63 int port =
64 gconf_client_get_int(appdata->gconf_client, PROXY_KEY "port", NULL);
65
66 snprintf(proxy_buffer, sizeof(proxy_buffer),
67 "http://%s:%u", host, port);
68
69 g_free(host);
70 }
71 return proxy_buffer;
72 }
73
74 return NULL;
75 }
76
77 static void
78 cb_map_gps(osd_button_t but, map_context_t *context) {
79 if(but == OSD_GPS) {
80 pos_t *refpos = get_pos(context->appdata);
81 if(refpos && !isnan(refpos->lat) && !isnan(refpos->lon)) {
82 gint zoom;
83 g_object_get(OSM_GPS_MAP(context->widget), "zoom", &zoom, NULL);
84 if(zoom < 10)
85 osm_gps_map_set_mapcenter(OSM_GPS_MAP(context->widget),
86 refpos->lat, refpos->lon, GPS_DEFAULT_ZOOM);
87 else
88 osm_gps_map_set_center(OSM_GPS_MAP(context->widget),
89 refpos->lat, refpos->lon);
90
91 /* re-enable centering */
92 g_object_set(context->widget, "auto-center", TRUE, NULL);
93 } else {
94 /* no coordinates given: display the entire world */
95 osm_gps_map_set_mapcenter(OSM_GPS_MAP(context->widget),
96 0.0, 0.0, 1);
97 }
98 }
99 }
100
101 static int dist2pixel(map_context_t *context, float km, float lat) {
102 return 1000.0*km/osm_gps_map_get_scale(OSM_GPS_MAP(context->widget));
103 }
104
105 static gboolean map_gps_update(gpointer data) {
106 map_context_t *context = (map_context_t*)data;
107
108 /* get reference position ... */
109 pos_t *refpos = get_pos(context->appdata);
110 gboolean ok = (refpos!= NULL) && !isnan(refpos->lat) && !isnan(refpos->lon);
111
112 /* ... and enable "goto" button if it's valid */
113 osm_gps_map_osd_enable_gps (OSM_GPS_MAP(context->widget),
114 OSM_GPS_MAP_OSD_CALLBACK(ok?cb_map_gps:NULL), context);
115
116 if(ok) {
117 float heading = NAN;
118 int radius = 0;
119
120 if(context->appdata->use_gps) {
121 heading = gps_get_heading(context->appdata);
122
123 /* get error */
124 float eph = gps_get_eph(context->appdata);
125 if(!isnan(eph))
126 radius = dist2pixel(context, eph/1000, refpos->lat);
127 }
128
129 g_object_set(context->widget, "gps-track-highlight-radius", radius, NULL);
130 osm_gps_map_draw_gps(OSM_GPS_MAP(context->widget),
131 refpos->lat, refpos->lon, heading);
132 } else
133 osm_gps_map_clear_gps(OSM_GPS_MAP(context->widget));
134
135 return TRUE;
136 }
137
138 static gboolean on_map_configure(GtkWidget *widget,
139 GdkEventConfigure *event,
140 map_context_t *context) {
141
142 if(!context->map_complete) {
143
144 /* set default values if they are invalid */
145 if(!context->appdata->map.zoom ||
146 isnan(context->appdata->map.pos.lat) ||
147 isnan(context->appdata->map.pos.lon)) {
148 printf("no valid map position found\n");
149
150 pos_t *refpos = get_pos(context->appdata);
151 if(refpos && !isnan(refpos->lat) && !isnan(refpos->lon)) {
152 /* use gps position if present */
153 context->appdata->map.pos = *refpos;
154 context->appdata->map.zoom = GPS_DEFAULT_ZOOM;
155 } else {
156 /* use world map otherwise */
157 context->appdata->map.pos.lat = 0.0;
158 context->appdata->map.pos.lon = 0.0;
159 context->appdata->map.zoom = 1;
160 }
161 }
162
163 /* jump to initial position */
164 osm_gps_map_set_mapcenter(OSM_GPS_MAP(context->widget),
165 context->appdata->map.pos.lat,
166 context->appdata->map.pos.lon,
167 context->appdata->map.zoom);
168 context->map_complete = TRUE;
169 }
170
171 return FALSE;
172 }
173
174 static void map_draw_cachelist(GtkWidget *map, cache_t *cache) {
175 while(cache) {
176 GdkPixbuf *icon = icon_get(ICON_CACHE_TYPE, cache->type);
177
178 osm_gps_map_add_image(OSM_GPS_MAP(map),
179 cache->pos.lat, cache->pos.lon, icon);
180
181 cache = cache->next;
182 }
183 }
184
185 static void
186 map_cachelist_nearest(cache_t *cache, pos_t *pos,
187 cache_t **result, float *distance) {
188 while(cache) {
189 float dist =
190 pow(cache->pos.lat - pos->lat, 2) +
191 pow(cache->pos.lon - pos->lon, 2);
192
193 if(!(dist > *distance)) {
194 *result = cache;
195 *distance = dist;
196 }
197
198 cache = cache->next;
199 }
200 }
201
202 static cache_t *map_closest(map_context_t *context, pos_t *pos) {
203 cache_t *result = NULL;
204 float distance = NAN;
205
206 #ifdef USE_MAEMO
207 if(!context->appdata->cur_gpx) {
208 #endif
209 /* search all geocaches */
210 gpx_t *gpx = context->appdata->gpx;
211 while(gpx) {
212 map_cachelist_nearest(gpx->cache, pos, &result, &distance);
213 gpx = gpx->next;
214 }
215 #ifdef USE_MAEMO
216 } else {
217 map_cachelist_nearest(context->appdata->cur_gpx->cache,
218 pos, &result, &distance);
219 }
220 #endif
221
222 return result;
223 }
224
225 /* translate between osm-gps-map positions and gpxview ones */
226 pos_t coord2pos(coord_t coo) {
227 pos_t pos;
228 pos.lat = rad2deg(coo.rlat);
229 pos.lon = rad2deg(coo.rlon);
230 return pos;
231 }
232
233 #define CLICK_FUZZ (24)
234
235 static gboolean
236 on_map_button_press_event(GtkWidget *widget,
237 GdkEventButton *event, map_context_t *context) {
238 OsmGpsMap *map = OSM_GPS_MAP(context->widget);
239
240 /* check if we actually clicked parts of the OSD */
241 if(osm_gps_map_osd_check(map, event->x, event->y) != OSD_NONE)
242 return FALSE;
243
244 /* got a press event without release event? eat it! */
245 if(context->press_on != NULL) {
246 printf("PRESS: already\n");
247 return FALSE;
248 }
249
250 pos_t pos =
251 coord2pos(osm_gps_map_get_co_ordinates(map, event->x, event->y));
252
253 cache_t *nearest = map_closest(context, &pos);
254 if(nearest) {
255 float dist = gpx_pos_get_distance(pos, nearest->pos, FALSE);
256 if(dist2pixel(context, dist, nearest->pos.lat) < CLICK_FUZZ)
257 context->press_on = nearest;
258 }
259
260 return FALSE;
261 }
262
263 static void
264 cairo_draw_pixbuf(cairo_t *cr, GdkPixbuf *buf, gint x, gint y) {
265 /* convert the pixbuf into something cairo can handle */
266
267 // Create a new ImageSurface
268 cairo_surface_t *image_surface =
269 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
270 gdk_pixbuf_get_width(buf),
271 gdk_pixbuf_get_height(buf));
272
273 // Create the new Context for the ImageSurface
274 cairo_t *context = cairo_create(image_surface);
275
276 // Draw the image on the new Context
277 gdk_cairo_set_source_pixbuf(context, buf, 0.0, 0.0);
278 cairo_paint(context);
279
280 // now draw this onto the original context
281 cairo_set_source_surface(cr, image_surface, x, y);
282
283 cairo_paint(cr);
284 }
285
286 #ifndef BIG_BALLOONS
287 #define LINE_SKIP 7
288 #else
289 #define LINE_SKIP 12
290 #endif
291
292 static void
293 balloon_draw_cb(cairo_t *cr, OsmGpsMapRect_t *rect, gpointer data) {
294 cache_t *cache = (cache_t*)data;
295
296 #if 0
297 /* draw pink background to check clipping */
298 cairo_rectangle (cr, rect->x-20, rect->y-20, rect->w+40, rect->h+40);
299 cairo_set_source_rgba (cr, 1, 0, 0, 0.3);
300 cairo_fill_preserve (cr);
301 cairo_set_line_width (cr, 0);
302 cairo_stroke (cr);
303 #endif
304
305 /* leave a little border top and left */
306 gint x = rect->x, y = rect->y;
307
308 /* draw the cache type icon ... */
309 GdkPixbuf *icon = icon_get(ICON_CACHE_TYPE, cache->type);
310 cairo_draw_pixbuf(cr, icon, x, y);
311
312 /* ... and right of it the waypoint id */
313 cairo_text_extents_t extents;
314
315 if(cache->id) {
316 cairo_select_font_face (cr, "Sans",
317 CAIRO_FONT_SLANT_NORMAL,
318 CAIRO_FONT_WEIGHT_BOLD);
319
320 #ifndef BIG_BALLOONS
321 cairo_set_font_size (cr, 20.0);
322 #else
323 cairo_set_font_size (cr, 36.0);
324 #endif
325
326 cairo_text_extents (cr, cache->id, &extents);
327
328 /* display id right of icon vertically centered */
329 x += gdk_pixbuf_get_width(icon) + 5;
330 y += (gdk_pixbuf_get_height(icon) + extents.height)/2;
331 cairo_move_to (cr, x, y);
332 cairo_set_source_rgba (cr, 0, 0, 0, 1);
333 cairo_show_text (cr, cache->id);
334 cairo_stroke (cr);
335
336 y += (gdk_pixbuf_get_height(icon) - extents.height)/2 + LINE_SKIP;
337 } else
338 y += gdk_pixbuf_get_height(icon);
339
340 /* return to the left border and below icon/text */
341 x = rect->x;
342
343 /* everything from here uses the same font */
344 cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL,
345 CAIRO_FONT_WEIGHT_NORMAL);
346 #ifndef BIG_BALLOONS
347 cairo_set_font_size (cr, 14.0);
348 #else
349 cairo_set_font_size (cr, 22.0);
350 #endif
351
352 if(cache->name) {
353 /* draw cache name */
354 cairo_text_extents (cr, cache->name, &extents);
355 y += extents.height;
356 cairo_move_to (cr, x, y);
357 cairo_set_source_rgba (cr, 0, 0, 0, 1);
358 cairo_show_text (cr, cache->name);
359 cairo_stroke (cr);
360
361 /* return to the left border and below text */
362 y += LINE_SKIP;
363 x = rect->x;
364 }
365
366 if(cache->terrain) {
367 /* draw cache rating */
368 const char *terrain = "Terrain:";
369 icon = icon_get(ICON_STARS, (int)(cache->terrain*2-2));
370 cairo_text_extents (cr, _(terrain), &extents);
371 y += (gdk_pixbuf_get_height(icon) + extents.height)/2;
372
373 /* draw "Terrain:" string */
374 cairo_move_to (cr, x, y);
375 cairo_set_source_rgba (cr, 0, 0, 0, 1);
376 cairo_show_text (cr, _(terrain));
377 cairo_stroke (cr);
378 x += extents.width + 2;
379
380 /* draw terrain stars */
381 cairo_draw_pixbuf(cr, icon, x, y -
382 (gdk_pixbuf_get_height(icon) + extents.height)/2);
383
384 x += gdk_pixbuf_get_width(icon) + LINE_SKIP;
385 y -= (gdk_pixbuf_get_height(icon) + extents.height)/2;
386 }
387
388 if(cache->difficulty) {
389 const char *difficulty = "Difficulty:";
390 cairo_text_extents (cr, _(difficulty), &extents);
391 y += (gdk_pixbuf_get_height(icon) + extents.height)/2;
392
393 /* draw "Difficulty:" string */
394 cairo_move_to (cr, x, y);
395 cairo_set_source_rgba (cr, 0, 0, 0, 1);
396 cairo_show_text (cr, _(difficulty));
397 cairo_stroke (cr);
398 x += extents.width + 2;
399
400 icon = icon_get(ICON_STARS, (int)(cache->difficulty*2-2));
401 cairo_draw_pixbuf(cr, icon, x, y -
402 (gdk_pixbuf_get_height(icon) + extents.height)/2);
403 }
404 }
405
406 static gboolean
407 on_map_button_release_event(GtkWidget *widget,
408 GdkEventButton *event, map_context_t *context) {
409 OsmGpsMap *map = OSM_GPS_MAP(context->widget);
410
411 if(context->press_on) {
412 coord_t coo;
413 coo = osm_gps_map_get_co_ordinates(map, event->x, event->y);
414
415 pos_t pos =
416 coord2pos(osm_gps_map_get_co_ordinates(map, event->x, event->y));
417
418 cache_t *nearest = map_closest(context, &pos);
419 if(nearest && nearest == context->press_on) {
420 float dist = gpx_pos_get_distance(pos, nearest->pos, FALSE);
421 if(dist2pixel(context, dist, nearest->pos.lat) < CLICK_FUZZ) {
422
423 osm_gps_map_draw_balloon(map, nearest->pos.lat, nearest->pos.lon,
424 balloon_draw_cb, nearest);
425 }
426 }
427 context->press_on = NULL;
428 } else {
429 /* save new map position */
430 gfloat lat, lon;
431 g_object_get(map, "latitude", &lat, "longitude", &lon, NULL);
432 context->appdata->map.pos.lat = lat;
433 context->appdata->map.pos.lon = lon;
434 }
435
436 return FALSE;
437 }
438
439 static void on_window_destroy(GtkWidget *widget, map_context_t *context) {
440 appdata_t *appdata = context->appdata;
441
442 printf("destroy map window\n");
443
444 /* save map parameters */
445 OsmGpsMap *map = OSM_GPS_MAP(context->widget);
446 gint zoom;
447 g_object_get(map, "zoom", &zoom, NULL);
448 context->appdata->map.zoom = zoom;
449
450 gfloat lat, lon;
451 g_object_get(map, "latitude", &lat, "longitude", &lon, NULL);
452 context->appdata->map.pos.lat = lat;
453 context->appdata->map.pos.lon = lon;
454
455 gint source;
456 g_object_get(map, "map-source", &source, NULL);
457 context->appdata->map.source = source;
458
459 #if MAEMO_VERSION_MAJOR == 5
460 /* restore cur_view */
461 context->appdata->cur_view = context->old_view;
462 #endif
463
464 gtk_timeout_remove(context->handler_id);
465
466 g_free(context);
467 appdata->map.context = NULL;
468 }
469
470 #if (MAEMO_VERSION_MAJOR == 5) && !defined(__i386__)
471 /* get access to zoom buttons */
472 static void
473 on_window_realize(GtkWidget *widget, gpointer data) {
474 if (widget->window) {
475 unsigned char value = 1;
476 Atom hildon_zoom_key_atom =
477 gdk_x11_get_xatom_by_name("_HILDON_ZOOM_KEY_ATOM"),
478 integer_atom = gdk_x11_get_xatom_by_name("INTEGER");
479 Display *dpy =
480 GDK_DISPLAY_XDISPLAY(gdk_drawable_get_display(widget->window));
481 Window w = GDK_WINDOW_XID(widget->window);
482
483 XChangeProperty(dpy, w, hildon_zoom_key_atom,
484 integer_atom, 8, PropModeReplace, &value, 1);
485 }
486 }
487 #endif
488
489 void map(appdata_t *appdata) {
490 map_context_t *context = NULL;
491
492 /* if the map window already exists, just raise it */
493 if(appdata->map.context) {
494 gtk_window_present(GTK_WINDOW(appdata->map.context->window));
495 return;
496 }
497
498 context = appdata->map.context = g_new0(map_context_t, 1);
499 context->appdata = appdata;
500 context->map_complete = FALSE;
501
502 /* cleanup old (pre 0.8.7) path if it exists */
503 char *old_path = g_strdup_printf("%s/map/", appdata->image_path);
504 if(g_file_test(old_path, G_FILE_TEST_IS_DIR)) {
505 printf("old file path %s exists\n", old_path);
506 rmdir_recursive(old_path);
507 }
508
509 /* It is recommanded that all applications share these same */
510 /* map path, so data is only cached once. The path should be: */
511 /* ~/.osm-gps-map on standard PC (users home) */
512 /* /home/user/.osm-gps-map on Maemo5 (ext3 on internal card) */
513 /* /media/mmc2/osm-gps-map on Maemo4 (vfat on internal card) */
514 #if !defined(USE_MAEMO)
515 char *p = getenv("HOME");
516 if(!p) p = "/tmp";
517 char *path = g_strdup_printf("%s/.osm-gps-map", p);
518 #else
519 #if MAEMO_VERSION_MAJOR == 5
520 char *path = g_strdup("/home/user/.osm-gps-map");
521 #else
522 char *path = g_strdup("/media/mmc2/osm-gps-map");
523 #endif
524 #endif
525
526 const char *proxy = get_proxy_uri(appdata);
527
528 gint source = context->appdata->map.source;
529 if(!source) source = MAP_SOURCE;
530
531 context->widget = g_object_new(OSM_TYPE_GPS_MAP,
532 "map-source", source,
533 "tile-cache", path,
534 "auto-center", FALSE,
535 "record-trip-history", FALSE,
536 "show-trip-history", FALSE,
537 proxy?"proxy-uri":NULL, proxy,
538 NULL);
539
540 g_free(path);
541
542 osm_gps_map_osd_classic_init(OSM_GPS_MAP(context->widget));
543
544 char *name = NULL;
545 #ifdef USE_MAEMO
546 if(!appdata->cur_gpx) {
547 #endif
548 /* draw all geocaches */
549 gpx_t *gpx = appdata->gpx;
550 while(gpx) {
551 map_draw_cachelist(context->widget, gpx->cache);
552 gpx = gpx->next;
553 }
554 name = g_strdup(_("all geocaches"));
555 #ifdef USE_MAEMO
556 } else {
557 map_draw_cachelist(context->widget, appdata->cur_gpx->cache);
558 name = g_strdup(appdata->cur_gpx->name);
559 }
560 #endif
561
562 char *title = g_strdup_printf(_("Map - %s"), name);
563 g_free(name);
564
565 #ifdef USE_MAEMO
566 #ifdef USE_STACKABLE_WINDOW
567 context->window = hildon_stackable_window_new();
568 /* try to enable the zoom buttons. don't do this on x86 as it breaks */
569 /* at runtime with cygwin x */
570 #ifndef __i386__
571 g_signal_connect(G_OBJECT(context->window), "realize",
572 G_CALLBACK(on_window_realize), NULL);
573 #endif // MAEMO_VERSION
574 #else
575 context->window = hildon_window_new();
576 #endif
577 #else
578 context->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
579 #endif
580
581 gtk_window_set_title(GTK_WINDOW(context->window), title);
582
583 #ifndef USE_MAEMO
584 gtk_window_set_default_size(GTK_WINDOW(context->window), 640, 480);
585 #endif
586
587 g_free(title);
588
589 g_signal_connect(G_OBJECT(context->widget), "configure-event",
590 G_CALLBACK(on_map_configure), context);
591
592 g_signal_connect(G_OBJECT(context->widget), "button-press-event",
593 G_CALLBACK(on_map_button_press_event), context);
594
595 g_signal_connect(G_OBJECT(context->widget), "button-release-event",
596 G_CALLBACK(on_map_button_release_event), context);
597
598 /* install handler for timed updates of the gps button */
599 context->handler_id = gtk_timeout_add(1000, map_gps_update, context);
600
601 #if MAEMO_VERSION_MAJOR == 5
602 /* prevent some of the main screen things */
603 context->old_view = appdata->cur_view;
604 appdata->cur_view = NULL;
605 #endif
606
607 g_signal_connect(G_OBJECT(context->window), "destroy",
608 G_CALLBACK(on_window_destroy), context);
609
610 gtk_container_add(GTK_CONTAINER(context->window), context->widget);
611 gtk_widget_show_all(GTK_WIDGET(context->window));
612 }