]> git.street.me.uk Git - andy/viking.git/blob - src/vikutils.c
SF Bugs#133: Remove the auto added map when opening the first .vik file from the...
[andy/viking.git] / src / vikutils.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4  *
5  * Copyright (C) 2013, Rob Norris <rw_norris@hotmail.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  */
22 /*
23  * Dependencies in this file can be on anything.
24  * For functions with simple system dependencies put it in util.c
25  */
26 #include <math.h>
27 #include <glib/gstdio.h>
28 #include <glib/gi18n.h>
29 #include <gtk/gtk.h>
30
31 #include "viking.h"
32 #include "vikutils.h"
33 #include "globals.h"
34 #include "download.h"
35 #include "preferences.h"
36 #include "vikmapslayer.h"
37 #include "settings.h"
38 #include "ui_util.h"
39 #include "dir.h"
40 #include "misc/kdtree.h"
41
42 #define FMT_MAX_NUMBER_CODES 9
43
44 /**
45  * vu_trackpoint_formatted_message:
46  * @format_code:  String describing the message to generate
47  * @trkpt:        The trackpoint for which the message is generated about
48  * @trkpt_prev:   A trackpoint (presumed previous) for interpolating values with the other trackpoint (such as speed)
49  * @trk:          The track in which the trackpoints reside
50  * @climb:        Vertical speed (Out of band (i.e. not in a trackpoint) value for display currently only for GPSD usage)
51  *
52  *  TODO: One day replace this cryptic format code with some kind of tokenizer parsing
53  *    thus would make it more user friendly and maybe even GUI controlable.
54  * However for now at least there is some semblance of user control
55  */
56 gchar* vu_trackpoint_formatted_message ( gchar *format_code, VikTrackpoint *trkpt, VikTrackpoint *trkpt_prev, VikTrack *trk, gdouble climb )
57 {
58         if ( !trkpt )
59                 return NULL;
60
61         gint len = 0;
62         if ( format_code )
63                 len = strlen ( format_code );
64         if ( len > FMT_MAX_NUMBER_CODES )
65                 len = FMT_MAX_NUMBER_CODES;
66
67         gchar* values[FMT_MAX_NUMBER_CODES];
68         int i;
69         for ( i = 0; i < FMT_MAX_NUMBER_CODES; i++ ) {
70                 values[i] = '\0';
71         }
72
73         gchar *speed_units_str = NULL;
74         vik_units_speed_t speed_units = a_vik_get_units_speed ();
75         switch (speed_units) {
76         case VIK_UNITS_SPEED_MILES_PER_HOUR:
77                 speed_units_str = g_strdup ( _("mph") );
78                 break;
79         case VIK_UNITS_SPEED_METRES_PER_SECOND:
80                 speed_units_str = g_strdup ( _("m/s") );
81                 break;
82         case VIK_UNITS_SPEED_KNOTS:
83                 speed_units_str = g_strdup ( _("knots") );
84                 break;
85         default:
86                 // VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
87                 speed_units_str = g_strdup ( _("km/h") );
88                 break;
89         }
90
91         gchar *separator = g_strdup ( " | " );
92
93         for ( i = 0; i < len; i++ ) {
94                 switch ( g_ascii_toupper ( format_code[i] ) ) {
95                 case 'G': values[i] = g_strdup ( _("GPSD") ); break; // GPS Preamble
96                 case 'K': values[i] = g_strdup ( _("Trkpt") ); break; // Trkpt Preamble
97
98                 case 'S': {
99                         gdouble speed = 0.0;
100                         gchar *speedtype = NULL;
101                         if ( isnan(trkpt->speed) && trkpt_prev ) {
102                                 if ( trkpt->has_timestamp && trkpt_prev->has_timestamp ) {
103                                         if ( trkpt->timestamp != trkpt_prev->timestamp ) {
104
105                                                 // Work out from previous trackpoint location and time difference
106                                                 speed = vik_coord_diff(&(trkpt->coord), &(trkpt_prev->coord)) / ABS(trkpt->timestamp - trkpt_prev->timestamp);
107                                                 speedtype = g_strdup ( "*" ); // Interpolated
108                                         }
109                                         else
110                                                 speedtype = g_strdup ( "**" );
111                                 }
112                                 else
113                                         speedtype = g_strdup ( "**" );
114                         }
115                         else {
116                                 speed = trkpt->speed;
117                                 speedtype = g_strdup ( "" );
118                         }
119                         switch (speed_units) {
120                         case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
121                                 speed = VIK_MPS_TO_KPH(speed);
122                                 break;
123                         case VIK_UNITS_SPEED_MILES_PER_HOUR:
124                                 speed = VIK_MPS_TO_MPH(speed);
125                                 break;
126                         case VIK_UNITS_SPEED_KNOTS:
127                                 speed = VIK_MPS_TO_KNOTS(speed);
128                                 break;
129                         default:
130                                 // VIK_UNITS_SPEED_METRES_PER_SECOND:
131                                 // Already in m/s so nothing to do
132                                 break;
133                         }
134
135                         values[i] = g_strdup_printf ( _("%sSpeed%s %.1f%s"), separator, speedtype, speed, speed_units_str );
136                         g_free ( speedtype );
137                         break;
138                 }
139
140                 case 'B': {
141                         gdouble speed = 0.0;
142                         gchar *speedtype = NULL;
143                         if ( isnan(climb) && trkpt_prev ) {
144                                 if ( trkpt->has_timestamp && trkpt_prev->has_timestamp ) {
145                                         if ( trkpt->timestamp != trkpt_prev->timestamp ) {
146                                                 // Work out from previous trackpoint altitudes and time difference
147                                                 // 'speed' can be negative if going downhill
148                                                 speed = (trkpt->altitude - trkpt_prev->altitude) / ABS(trkpt->timestamp - trkpt_prev->timestamp);
149                                                 speedtype = g_strdup ( "*" ); // Interpolated
150                                         }
151                                         else
152                                                 speedtype = g_strdup ( "**" ); // Unavailable
153                                 }
154                                 else
155                                         speedtype = g_strdup ( "**" );
156                         }
157                         else {
158                                 speed = climb;
159                                 speedtype = g_strdup ( "" );
160                         }
161                         switch (speed_units) {
162                         case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
163                                 speed = VIK_MPS_TO_KPH(speed);
164                                 break;
165                         case VIK_UNITS_SPEED_MILES_PER_HOUR:
166                                 speed = VIK_MPS_TO_MPH(speed);
167                                 break;
168                         case VIK_UNITS_SPEED_KNOTS:
169                                 speed = VIK_MPS_TO_KNOTS(speed);
170                                 break;
171                         default:
172                                 // VIK_UNITS_SPEED_METRES_PER_SECOND:
173                                 // Already in m/s so nothing to do
174                                 break;
175                         }
176                         // Go for 2dp as expect low values for vertical speeds
177                         values[i] = g_strdup_printf ( _("%sClimb%s %.2f%s"), separator, speedtype, speed, speed_units_str );
178                         g_free ( speedtype );
179                         break;
180                 }
181
182                 case 'A': {
183                         vik_units_height_t height_units = a_vik_get_units_height ();
184                         switch (height_units) {
185                         case VIK_UNITS_HEIGHT_FEET:
186                                 values[i] = g_strdup_printf ( _("%sAlt %dfeet"), separator, (int)round(VIK_METERS_TO_FEET(trkpt->altitude)) );
187                                 break;
188                         default:
189                                 //VIK_UNITS_HEIGHT_METRES:
190                                 values[i] = g_strdup_printf ( _("%sAlt %dm"), separator, (int)round(trkpt->altitude) );
191                                 break;
192                         }
193                         break;
194                 }
195
196                 case 'C': {
197                         gint heading = isnan(trkpt->course) ? 0 : (gint)round(trkpt->course);
198                         values[i] = g_strdup_printf ( _("%sCourse %03d\302\260" ), separator, heading );
199                         break;
200                 }
201
202                 case 'P': {
203                         if ( trkpt_prev ) {
204                                 gint diff = (gint) round ( vik_coord_diff ( &(trkpt->coord), &(trkpt_prev->coord) ) );
205
206                                 gchar *dist_units_str = NULL;
207                                 vik_units_distance_t dist_units = a_vik_get_units_distance ();
208                                 // expect the difference between track points to be small hence use metres or yards
209                                 switch (dist_units) {
210                                 case VIK_UNITS_DISTANCE_MILES:
211                                 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
212                                         dist_units_str = g_strdup ( _("yards") );
213                                         break;
214                                 default:
215                                         // VIK_UNITS_DISTANCE_KILOMETRES:
216                                         dist_units_str = g_strdup ( _("m") );
217                                         break;
218                                 }
219
220                                 values[i] = g_strdup_printf ( _("%sDistance diff %d%s"), separator, diff, dist_units_str );
221
222                                 g_free ( dist_units_str );
223                         }
224                         break;
225                 }
226
227                 case 'T': {
228                         gchar *msg;
229                         if ( trkpt->has_timestamp ) {
230                                 // Compact date time format
231                                 msg = vu_get_time_string ( &(trkpt->timestamp), "%x %X", &(trkpt->coord), NULL );
232                         }
233                         else
234                                 msg = g_strdup ("--");
235                         values[i] = g_strdup_printf ( _("%sTime %s"), separator, msg );
236                         g_free ( msg );
237                         break;
238                 }
239
240                 case 'M': {
241                         if ( trkpt_prev ) {
242                                 if ( trkpt->has_timestamp && trkpt_prev->has_timestamp ) {
243                                         time_t t_diff = trkpt->timestamp - trkpt_prev->timestamp;
244                                         values[i] = g_strdup_printf ( _("%sTime diff %lds"), separator, t_diff );
245                                 }
246                         }
247                         break;
248                 }
249
250                 case 'X': values[i] = g_strdup_printf ( _("%sNo. of Sats %d"), separator, trkpt->nsats ); break;
251
252                 case 'F': {
253                         if ( trk ) {
254                                 // Distance to the end 'Finish' (along the track)
255                                 gdouble distd = vik_track_get_length_to_trackpoint (trk, trkpt);
256                                 gdouble diste = vik_track_get_length_including_gaps ( trk );
257                                 gdouble dist = diste - distd;
258                                 gchar *dist_units_str = NULL;
259                                 vik_units_distance_t dist_units = a_vik_get_units_distance ();
260                                 switch (dist_units) {
261                                 case VIK_UNITS_DISTANCE_MILES:
262                                         dist_units_str = g_strdup ( _("miles") );
263                                         dist = VIK_METERS_TO_MILES(dist);
264                                         break;
265                                 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
266                                         dist_units_str = g_strdup ( _("NM") );
267                                         dist = VIK_METERS_TO_NAUTICAL_MILES(dist);
268                                         break;
269                                 default:
270                                         // VIK_UNITS_DISTANCE_KILOMETRES:
271                                         dist_units_str = g_strdup ( _("km") );
272                                         dist = dist / 1000.0;
273                                         break;
274                                 }
275                                 values[i] = g_strdup_printf ( _("%sTo End %.2f%s"), separator, dist, dist_units_str );
276                                 g_free ( dist_units_str );
277                         }
278                         break;
279                 }
280
281                 case 'D': {
282                         if ( trk ) {
283                                 // Distance from start (along the track)
284                                 gdouble distd = vik_track_get_length_to_trackpoint (trk, trkpt);
285                                 gchar *dist_units_str = NULL;
286                                 vik_units_distance_t dist_units = a_vik_get_units_distance ();
287                                 switch (dist_units) {
288                                 case VIK_UNITS_DISTANCE_MILES:
289                                         dist_units_str = g_strdup ( _("miles") );
290                                         distd = VIK_METERS_TO_MILES(distd);
291                                         break;
292                                 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
293                                         dist_units_str = g_strdup ( _("NM") );
294                                         distd = VIK_METERS_TO_NAUTICAL_MILES(distd);
295                                         break;
296                                 default:
297                                         // VIK_UNITS_DISTANCE_KILOMETRES:
298                                         dist_units_str = g_strdup ( _("km") );
299                                         distd = distd / 1000.0;
300                                         break;
301                                 }
302                                 values[i] = g_strdup_printf ( _("%sDistance along %.2f%s"), separator, distd, dist_units_str );
303                                 g_free ( dist_units_str );
304                         }
305                         break;
306                 }
307
308                 case 'L': {
309                         // Location (Lat/Long)
310                         gchar *lat = NULL, *lon = NULL;
311                         struct LatLon ll;
312                         vik_coord_to_latlon (&(trkpt->coord), &ll);
313                         a_coords_latlon_to_string ( &ll, &lat, &lon );
314                         values[i] = g_strdup_printf ( "%s%s %s", separator, lat, lon );
315                         g_free ( lat );
316                         g_free ( lon );
317                         break;
318                 }
319
320                 case 'N': // Name of track
321                         if ( trk )
322                                 values[i] = g_strdup_printf ( _("%sTrack: %s"), separator, trk->name );
323                         break;
324
325                 case 'E': // Name of trackpoint if available
326                         if ( trkpt->name )
327                                 values[i] = g_strdup_printf ( "%s%s", separator, trkpt->name );
328                         else
329                                 values[i] = g_strdup ( "" );
330                         break;
331
332                 default:
333                         break;
334                 }
335         }
336
337         g_free ( separator );
338         g_free ( speed_units_str );
339
340         gchar *msg = g_strconcat ( values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], values[8], NULL );
341
342         for ( i = 0; i < FMT_MAX_NUMBER_CODES; i++ ) {
343                 if ( values[i] != '\0' )
344                         g_free ( values[i] );
345         }
346         
347         return msg;
348 }
349
350 typedef struct {
351         GtkWindow *window; // Layer needed for redrawing
352         gchar *version;    // Image list
353 } new_version_thread_data;
354
355 static gboolean new_version_available_message ( new_version_thread_data *nvtd )
356 {
357         // Only a simple goto website option is offered
358         // Trying to do an installation update is platform specific
359         if ( a_dialog_yes_or_no ( nvtd->window,
360                                 _("There is a newer version of Viking available: %s\n\nDo you wish to go to Viking's website now?"), nvtd->version ) )
361                 // NB 'VIKING_URL' redirects to the Wiki, here we want to go the main site.
362                 open_url ( nvtd->window, "http://sourceforge.net/projects/viking/" );
363
364         g_free ( nvtd->version );
365         g_free ( nvtd );
366         return FALSE;
367 }
368
369 #define VIK_SETTINGS_VERSION_CHECKED_DATE "version_checked_date"
370
371 static void latest_version_thread ( GtkWindow *window )
372 {
373         // Need to allow a few redirects, as SF file is often served from different server
374         DownloadFileOptions options = { FALSE, FALSE, NULL, 5, NULL, NULL, NULL };
375         gchar *filename = a_download_uri_to_tmp_file ( "http://sourceforge.net/projects/viking/files/VERSION", &options );
376         //gchar *filename = g_strdup ( "VERSION" );
377         if ( !filename ) {
378                 return;
379         }
380
381         GMappedFile *mf = g_mapped_file_new ( filename, FALSE, NULL );
382         if ( !mf )
383                 return;
384
385         gchar *text = g_mapped_file_get_contents ( mf );
386
387         gint latest_version = viking_version_to_number ( text );
388         gint my_version = viking_version_to_number ( VIKING_VERSION );
389
390         g_debug ( "The lastest version is: %s", text );
391
392         if ( my_version < latest_version ) {
393                 new_version_thread_data *nvtd = g_malloc ( sizeof(new_version_thread_data) );
394                 nvtd->window = window;
395                 nvtd->version = g_strdup ( text );
396                 gdk_threads_add_idle ( (GSourceFunc) new_version_available_message, nvtd );
397         }
398         else
399                 g_debug ( "Running the lastest version: %s", VIKING_VERSION );
400
401         g_mapped_file_unref ( mf );
402         if ( filename ) {
403                 g_remove ( filename );
404                 g_free ( filename );
405         }
406
407         // Update last checked time
408         GTimeVal time;
409         g_get_current_time ( &time );
410         a_settings_set_string ( VIK_SETTINGS_VERSION_CHECKED_DATE, g_time_val_to_iso8601(&time) );
411 }
412
413 #define VIK_SETTINGS_VERSION_CHECK_PERIOD "version_check_period_days"
414
415 /**
416  * vu_check_latest_version:
417  * @window: Somewhere where we may need use the display to inform the user about the version status
418  *
419  * Periodically checks the released latest VERSION file on the website to compare with the running version
420  *
421  */
422 void vu_check_latest_version ( GtkWindow *window )
423 {
424         if ( ! a_vik_get_check_version () )
425                 return;
426
427         gboolean do_check = FALSE;
428
429         gint check_period;
430         if ( ! a_settings_get_integer ( VIK_SETTINGS_VERSION_CHECK_PERIOD, &check_period ) ) {
431                 check_period = 14;
432         }
433
434         // Get last checked date...
435         GDate *gdate_last = g_date_new();
436         GDate *gdate_now = g_date_new();
437         GTimeVal time_last;
438         gchar *last_checked_date = NULL;
439
440         // When no previous date available - set to do the version check
441         if ( a_settings_get_string ( VIK_SETTINGS_VERSION_CHECKED_DATE, &last_checked_date) ) {
442                 if ( g_time_val_from_iso8601 ( last_checked_date, &time_last ) ) {
443                         g_date_set_time_val ( gdate_last, &time_last );
444                 }
445                 else
446                         do_check = TRUE;
447         }
448         else
449                 do_check = TRUE;
450
451         GTimeVal time_now;
452         g_get_current_time ( &time_now );
453         g_date_set_time_val ( gdate_now, &time_now );
454
455         if ( ! do_check ) {
456                 // Dates available so do the comparison
457                 g_date_add_days ( gdate_last, check_period );
458                 if ( g_date_compare ( gdate_last, gdate_now ) < 0 )
459                         do_check = TRUE;
460         }
461
462         g_date_free ( gdate_last );
463         g_date_free ( gdate_now );
464
465         if ( do_check ) {
466 #if GLIB_CHECK_VERSION (2, 32, 0)
467                 g_thread_try_new ( "latest_version_thread", (GThreadFunc)latest_version_thread, window, NULL );
468 #else
469                 g_thread_create ( (GThreadFunc)latest_version_thread, window, FALSE, NULL );
470 #endif
471         }
472 }
473
474 /**
475  * vu_set_auto_features_on_first_run:
476  *
477  *  Ask the user's opinion to set some of Viking's default behaviour
478  */
479 void vu_set_auto_features_on_first_run ( void )
480 {
481         gboolean auto_features = FALSE;
482         gboolean set_defaults = FALSE;
483
484         if ( a_vik_very_first_run () ) {
485
486                 GtkWidget *win = gtk_window_new ( GTK_WINDOW_TOPLEVEL );
487
488                 if ( a_dialog_yes_or_no ( GTK_WINDOW(win),
489                                           _("This appears to be Viking's very first run.\n\nDo you wish to enable automatic internet features?\n\nIndividual settings can be controlled in the Preferences."), NULL ) )
490                         auto_features = TRUE;
491
492                 // Default to more standard cache layout for new users (well new installs at least)
493                 maps_layer_set_cache_default ( VIK_MAPS_CACHE_LAYOUT_OSM );
494                 set_defaults = TRUE;
495         }
496
497         if ( auto_features ) {
498                 // Set Maps to autodownload
499                 // Ensure the default is true
500                 maps_layer_set_autodownload_default ( TRUE );
501                 set_defaults = TRUE;
502
503                 // Simplistic repeat of preference settings
504                 //  Only the name & type are important for setting a preference via this 'external' way
505
506                 // Enable auto add map +
507                 // Enable IP lookup
508                 VikLayerParam pref_add_map[] = { { VIK_LAYER_NUM_TYPES, VIKING_PREFERENCES_STARTUP_NAMESPACE "add_default_map_layer", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_GROUP_NONE, NULL, VIK_LAYER_WIDGET_CHECKBUTTON, NULL, NULL, NULL, NULL, NULL, NULL, }, };
509                 VikLayerParam pref_startup_method[] = { { VIK_LAYER_NUM_TYPES, VIKING_PREFERENCES_STARTUP_NAMESPACE "startup_method", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, NULL, VIK_LAYER_WIDGET_COMBOBOX, NULL, NULL, NULL, NULL, NULL, NULL}, };
510
511                 VikLayerParamData vlp_data;
512                 vlp_data.b = TRUE;
513                 a_preferences_run_setparam ( vlp_data, pref_add_map );
514
515                 vlp_data.u = VIK_STARTUP_METHOD_AUTO_LOCATION;
516                 a_preferences_run_setparam ( vlp_data, pref_startup_method );
517
518                 // Only on Windows make checking for the latest version on by default
519                 // For other systems it's expected a Package manager or similar controls the installation, so leave it off
520 #ifdef WINDOWS
521                 VikLayerParam pref_startup_version_check[] = { { VIK_LAYER_NUM_TYPES, VIKING_PREFERENCES_STARTUP_NAMESPACE "check_version", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_GROUP_NONE, NULL, VIK_LAYER_WIDGET_CHECKBUTTON, NULL, NULL, NULL, NULL, }, };
522                 vlp_data.b = TRUE;
523                 a_preferences_run_setparam ( vlp_data, pref_startup_version_check );
524 #endif
525
526                 // Ensure settings are saved for next time
527                 a_preferences_save_to_file ();
528         }
529
530         // Ensure defaults are saved if changed
531         if ( set_defaults )
532                 a_layer_defaults_save ();
533 }
534
535 /**
536  * vu_get_canonical_filename:
537  *
538  * Returns: Canonical absolute filename
539  *
540  * Any time a path may contain a relative component, so need to prepend that directory it is relative to
541  * Then resolve the full path to get the normal canonical filename
542  */
543 gchar *vu_get_canonical_filename ( VikLayer *vl, const gchar *filename )
544 {
545   gchar *canonical = NULL;
546   if ( !filename )
547     return NULL;
548
549   if ( g_path_is_absolute ( filename ) )
550     canonical = g_strdup ( filename );
551   else {
552     const gchar *vw_filename = vik_window_get_filename ( VIK_WINDOW_FROM_WIDGET (vl->vvp) );
553     gchar *dirpath = NULL;
554     if ( vw_filename )
555       dirpath = g_path_get_dirname ( vw_filename );
556     else
557       dirpath = g_get_current_dir(); // Fallback - if here then probably can't create the correct path
558
559     gchar *full = NULL;
560     if ( g_path_is_absolute ( dirpath ) )
561       full = g_strconcat ( dirpath, G_DIR_SEPARATOR_S, filename, NULL );
562     else
563       full = g_strconcat ( g_get_current_dir(), G_DIR_SEPARATOR_S, dirpath, G_DIR_SEPARATOR_S, filename, NULL );
564
565     canonical = file_realpath_dup ( full ); // resolved
566     g_free ( full );
567     g_free ( dirpath );
568   }
569
570   return canonical;
571 }
572
573 static struct kdtree *kd = NULL;
574
575 /**
576  * load_ll_tz_dir
577  * @dir: The directory from which to load the latlontz.txt file
578  *
579  * Returns: The number of elements within the latlontz.txt loaded
580  */
581 static gint load_ll_tz_dir ( const gchar *dir )
582 {
583         gint inserted = 0;
584         gchar *lltz = g_build_filename ( dir, "latlontz.txt", NULL );
585         if ( g_access(lltz, R_OK) == 0 ) {
586                 gchar buffer[4096];
587                 long line_num = 0;
588                 FILE *ff = g_fopen ( lltz, "r" );
589                 if ( ff ) {
590                         while ( fgets ( buffer, 4096, ff ) ) {
591                                 line_num++;
592                                 gchar **components = g_strsplit (buffer, " ", 3);
593                                 guint nn = g_strv_length ( components );
594                                 if ( nn == 3 ) {
595                                         double pt[2] = { g_ascii_strtod (components[0], NULL), g_ascii_strtod (components[1], NULL) };
596                                         gchar *timezone = g_strchomp ( components[2] );
597                                         if ( kd_insert ( kd, pt, timezone ) )
598                                                 g_critical ( "Insertion problem of %s for line %ld of latlontz.txt", timezone, line_num );
599                                         else
600                                                 inserted++;
601                                         // NB Don't free timezone as it's part of the kdtree data now
602                                         g_free ( components[0] );
603                                         g_free ( components[1] );
604                                 } else {
605                                         g_warning ( "Line %ld of latlontz.txt does not have 3 parts", line_num );
606                                 }
607                                 g_free ( components );
608                         }
609                         fclose ( ff );
610                 }
611                 else {
612                         g_warning ( "%s: Could not open %s", __FUNCTION__, lltz);
613                 }
614         }
615         g_free ( lltz );
616
617         return inserted;
618 }
619
620 /**
621  * vu_setup_lat_lon_tz_lookup:
622  *
623  * Can be called multiple times but only initializes the lookup once
624  */
625 void vu_setup_lat_lon_tz_lookup ()
626 {
627         // Only setup once
628         if ( kd )
629                 return;
630
631         kd = kd_create(2);
632
633         // Look in the directories of data path
634         gchar **data_dirs = a_get_viking_data_path();
635         guint loaded = 0;
636         // Process directories in reverse order for priority
637         guint n_data_dirs = g_strv_length ( data_dirs );
638         for (; n_data_dirs > 0; n_data_dirs--) {
639                 loaded += load_ll_tz_dir(data_dirs[n_data_dirs-1]);
640         }
641         g_strfreev ( data_dirs );
642
643         g_debug ( "%s: Loaded %d elements", __FUNCTION__, loaded );
644         if ( loaded == 0 )
645                 g_critical ( "%s: No lat/lon/timezones loaded", __FUNCTION__ );
646 }
647
648 /**
649  * vu_finalize_lat_lon_tz_lookup:
650  *
651  * Clear memory used by the lookup.
652  *  only call on program exit
653  */
654 void vu_finalize_lat_lon_tz_lookup ()
655 {
656         if ( kd ) {
657                 kd_data_destructor ( kd, g_free );
658                 kd_free ( kd );
659         }
660 }
661
662 static double dist_sq( double *a1, double *a2, int dims ) {
663   double dist_sq = 0, diff;
664   while( --dims >= 0 ) {
665     diff = (a1[dims] - a2[dims]);
666     dist_sq += diff*diff;
667   }
668   return dist_sq;
669 }
670
671 static gchar* time_string_adjusted ( time_t *time, gint offset_s )
672 {
673         time_t *mytime = time;
674         *mytime = *mytime + offset_s;
675         gchar *str = g_malloc ( 64 );
676         // Append asterisks to indicate use of simplistic model (i.e. no TZ)
677         strftime ( str, 64, "%a %X %x **", gmtime(mytime) );
678         return str;
679 }
680
681 static gchar* time_string_tz ( time_t *time, const gchar *format, GTimeZone *tz )
682 {
683         GDateTime *utc = g_date_time_new_from_unix_utc (*time);
684         if ( !utc ) {
685                 g_warning ( "%s: result from g_date_time_new_from_unix_utc() is NULL", __FUNCTION__ );
686                 return NULL;
687         }
688         GDateTime *local = g_date_time_to_timezone ( utc, tz );
689         if ( !local ) {
690                 g_date_time_unref ( utc );
691                 g_warning ( "%s: result from g_date_time_to_timezone() is NULL", __FUNCTION__ );
692                 return NULL;
693         }
694         gchar *str = g_date_time_format ( local, format );
695
696         g_date_time_unref ( local );
697         g_date_time_unref ( utc );
698         return str;
699 }
700
701 #define VIK_SETTINGS_NEAREST_TZ_FACTOR "utils_nearest_tz_factor"
702 /**
703  * vu_get_tz_at_location:
704  *
705  * @vc:     Position for which the time zone is desired
706  *
707  * Returns: TimeZone string of the nearest known location. String may be NULL.
708  *
709  * Use the k-d tree method (http://en.wikipedia.org/wiki/Kd-tree) to quickly retreive
710  *  the nearest location to the given position.
711  */
712 gchar* vu_get_tz_at_location ( const VikCoord* vc )
713 {
714         gchar *tz = NULL;
715         if ( !vc || !kd )
716                 return tz;
717
718         struct LatLon ll;
719         vik_coord_to_latlon ( vc, &ll );
720         double pt[2] = { ll.lat, ll.lon };
721
722         gdouble nearest;
723         if ( !a_settings_get_double(VIK_SETTINGS_NEAREST_TZ_FACTOR, &nearest) )
724                 nearest = 1.0;
725
726         struct kdres *presults = kd_nearest_range ( kd, pt, nearest );
727         while( !kd_res_end( presults ) ) {
728                 double pos[2];
729                 gchar *ans = (gchar*)kd_res_item ( presults, pos );
730                 // compute the distance of the current result from the pt
731                 double dist = sqrt( dist_sq( pt, pos, 2 ) );
732                 if ( dist < nearest ) {
733                         //printf( "NEARER node at (%.3f, %.3f, %.3f) is %.3f away is %s\n", pos[0], pos[1], pos[2], dist, ans );
734                         nearest = dist;
735                         tz = ans;
736                 }
737                 kd_res_next ( presults );
738         }
739         g_debug ( "TZ lookup found %d results - picked %s", kd_res_size(presults), tz );
740         kd_res_free ( presults );
741
742         return tz;
743 }
744
745 /**
746  * vu_get_time_string:
747  *
748  * @time_t: The time of which the string is wanted
749  * @format  The format of the time string - such as "%c"
750  * @vc:     Position of object for the time output - maybe NULL
751  *          (only applicable for VIK_TIME_REF_WORLD)
752  * @tz:     TimeZone string - maybe NULL.
753  *          (only applicable for VIK_TIME_REF_WORLD)
754  *          Useful to pass in the cached value from vu_get_tz_at_location() to save looking it up again for the same position
755  *
756  * Returns: A string of the time according to the time display property
757  */
758 gchar* vu_get_time_string ( time_t *time, const gchar *format, const VikCoord* vc, const gchar *tz )
759 {
760         if ( !format ) return NULL;
761         gchar *str = NULL;
762         switch ( a_vik_get_time_ref_frame() ) {
763                 case VIK_TIME_REF_UTC:
764                         str = g_malloc ( 64 );
765                         strftime ( str, 64, format, gmtime(time) ); // Always 'GMT'
766                         break;
767                 case VIK_TIME_REF_WORLD:
768                         if ( vc && !tz ) {
769                                 // No timezone specified so work it out
770                                 gchar *mytz = vu_get_tz_at_location ( vc );
771                                 if ( mytz ) {
772                                         GTimeZone *gtz = g_time_zone_new ( mytz );
773                                         str = time_string_tz ( time, format, gtz );
774                                         g_time_zone_unref ( gtz );
775                                 }
776                                 else {
777                                         // No results (e.g. could be in the middle of a sea)
778                                         // Fallback to simplistic method that doesn't take into account Timezones of countries.
779                                         struct LatLon ll;
780                                         vik_coord_to_latlon ( vc, &ll );
781                                         str = time_string_adjusted ( time, round ( ll.lon / 15.0 ) * 3600 );
782                                 }
783                         }
784                         else {
785                                 // Use specified timezone
786                                 GTimeZone *gtz = g_time_zone_new ( tz );
787                                 str = time_string_tz ( time, format, gtz );
788                                 g_time_zone_unref ( gtz );
789                         }
790                         break;
791                 default: // VIK_TIME_REF_LOCALE
792                         str = g_malloc ( 64 );
793                         strftime ( str, 64, format, localtime(time) );
794                         break;
795         }
796         return str;
797 }
798
799 /**
800  * vu_command_line:
801  *
802  * Apply any startup values that have been specified from the command line
803  * Values are defaulted in such a manner not to be applied when they haven't been specified
804  *
805  */
806 void vu_command_line ( VikWindow *vw, gdouble latitude, gdouble longitude, gint zoom_osm_level, gint map_id )
807 {
808         if ( !vw )
809                 return;
810
811         VikViewport *vvp = vik_window_viewport(vw);
812
813         if ( latitude != 0.0 || longitude != 0.0 ) {
814                 struct LatLon ll;
815                 ll.lat = latitude;
816                 ll.lon = longitude;
817                 vik_viewport_set_center_latlon ( vvp, &ll, TRUE );
818         }
819
820         if ( zoom_osm_level >= 0 ) {
821                 // Convert OSM zoom level into Viking zoom level
822                 gdouble mpp = exp ( (17-zoom_osm_level) * log(2) );
823                 if ( mpp > 1.0 )
824                         mpp = round (mpp);
825                 vik_viewport_set_zoom ( vvp, mpp );
826         }
827
828         if ( map_id >= 0 ) {
829                 guint my_map_id = map_id;
830                 if ( my_map_id == 0 )
831                         my_map_id = vik_maps_layer_get_default_map_type ();
832
833                 // Don't add map layer if one already exists
834                 GList *vmls = vik_layers_panel_get_all_layers_of_type(vik_window_layers_panel(vw), VIK_LAYER_MAPS, TRUE);
835                 int num_maps = g_list_length(vmls);
836                 gboolean add_map = TRUE;
837
838                 for (int i = 0; i < num_maps; i++) {
839                         VikMapsLayer *vml = (VikMapsLayer*)(vmls->data);
840                         gint id = vik_maps_layer_get_map_type(vml);
841                         if ( my_map_id == id ) {
842                                 add_map = FALSE;
843                                 break;
844                         }
845                         vmls = vmls->next;
846                 }
847
848                 if ( add_map ) {
849                         VikMapsLayer *vml = VIK_MAPS_LAYER ( vik_layer_create(VIK_LAYER_MAPS, vvp, FALSE) );
850                         vik_maps_layer_set_map_type ( vml, my_map_id );
851                         vik_layer_rename ( VIK_LAYER(vml), _("Map") );
852                         vik_aggregate_layer_add_layer ( vik_layers_panel_get_top_layer(vik_window_layers_panel(vw)), VIK_LAYER(vml), TRUE );
853                         vik_layer_emit_update ( VIK_LAYER(vml) );
854                 }
855         }
856 }
857
858 /**
859  * Copy the displayed text of a widget (should be a GtkButton ATM)
860  */
861 static void vu_copy_label ( GtkWidget *widget )
862 {
863         a_clipboard_copy (VIK_CLIPBOARD_DATA_TEXT, 0, 0, 0, gtk_button_get_label(GTK_BUTTON(widget)), NULL );
864 }
865
866 /**
867  * Generate a single entry menu to allow copying the displayed text of a widget (should be a GtkButton ATM)
868  */
869 void vu_copy_label_menu ( GtkWidget *widget, guint button )
870 {
871         GtkWidget *menu = gtk_menu_new();
872         GtkWidget *item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_COPY, NULL );
873         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(vu_copy_label), widget );
874         gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
875         gtk_widget_show ( item );
876         gtk_menu_popup ( GTK_MENU(menu), NULL, NULL, NULL, NULL, button, gtk_get_current_event_time() );
877 }
878
879 /**
880  * Work out the best zoom level for the LatLon area and set the viewport to that zoom level
881  */
882 void vu_zoom_to_show_latlons ( VikCoordMode mode, VikViewport *vvp, struct LatLon maxmin[2] )
883 {
884         /* First set the center [in case previously viewing from elsewhere] */
885         /* Then loop through zoom levels until provided positions are in view */
886         /* This method is not particularly fast - but should work well enough */
887         struct LatLon average = { (maxmin[0].lat+maxmin[1].lat)/2, (maxmin[0].lon+maxmin[1].lon)/2 };
888         VikCoord coord;
889         vik_coord_load_from_latlon ( &coord, mode, &average );
890         vik_viewport_set_center_coord ( vvp, &coord, TRUE );
891
892         /* Convert into definite 'smallest' and 'largest' positions */
893         struct LatLon minmin;
894         if ( maxmin[0].lat < maxmin[1].lat )
895                 minmin.lat = maxmin[0].lat;
896         else
897                 minmin.lat = maxmin[1].lat;
898
899         struct LatLon maxmax;
900         if ( maxmin[0].lon > maxmin[1].lon )
901                 maxmax.lon = maxmin[0].lon;
902         else
903                 maxmax.lon = maxmin[1].lon;
904
905         /* Never zoom in too far - generally not that useful, as too close ! */
906         /* Always recalculate the 'best' zoom level */
907         gdouble zoom = 1.0;
908         vik_viewport_set_zoom ( vvp, zoom );
909
910         gdouble min_lat, max_lat, min_lon, max_lon;
911         /* Should only be a maximum of about 18 iterations from min to max zoom levels */
912         while ( zoom <= VIK_VIEWPORT_MAX_ZOOM ) {
913                 vik_viewport_get_min_max_lat_lon ( vvp, &min_lat, &max_lat, &min_lon, &max_lon );
914                 /* NB I think the logic used in this test to determine if the bounds is within view
915                    fails if track goes across 180 degrees longitude.
916                    Hopefully that situation is not too common...
917                    Mind you viking doesn't really do edge locations to well anyway */
918                 if ( min_lat < minmin.lat &&
919                      max_lat > minmin.lat &&
920                      min_lon < maxmax.lon &&
921                      max_lon > maxmax.lon )
922                         /* Found within zoom level */
923                         break;
924
925                 /* Try next */
926                 zoom = zoom * 2;
927                 vik_viewport_set_zoom ( vvp, zoom );
928         }
929 }