]> git.street.me.uk Git - andy/viking.git/blob - src/vikutils.c
Merge branch 'KMZ-Maps'
[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         DownloadMapOptions 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         GDateTime *local = g_date_time_to_timezone ( utc, tz );
685         if ( !local ) {
686                 g_date_time_unref ( utc );
687                 return NULL;
688         }
689         gchar *str = g_date_time_format ( local, format );
690
691         g_date_time_unref ( local );
692         g_date_time_unref ( utc );
693         return str;
694 }
695
696 #define VIK_SETTINGS_NEAREST_TZ_FACTOR "utils_nearest_tz_factor"
697 /**
698  * vu_get_tz_at_location:
699  *
700  * @vc:     Position for which the time zone is desired
701  *
702  * Returns: TimeZone string of the nearest known location. String may be NULL.
703  *
704  * Use the k-d tree method (http://en.wikipedia.org/wiki/Kd-tree) to quickly retreive
705  *  the nearest location to the given position.
706  */
707 gchar* vu_get_tz_at_location ( const VikCoord* vc )
708 {
709         gchar *tz = NULL;
710         if ( !vc || !kd )
711                 return tz;
712
713         struct LatLon ll;
714         vik_coord_to_latlon ( vc, &ll );
715         double pt[2] = { ll.lat, ll.lon };
716
717         gdouble nearest;
718         if ( !a_settings_get_double(VIK_SETTINGS_NEAREST_TZ_FACTOR, &nearest) )
719                 nearest = 1.0;
720
721         struct kdres *presults = kd_nearest_range ( kd, pt, nearest );
722         while( !kd_res_end( presults ) ) {
723                 double pos[2];
724                 gchar *ans = (gchar*)kd_res_item ( presults, pos );
725                 // compute the distance of the current result from the pt
726                 double dist = sqrt( dist_sq( pt, pos, 2 ) );
727                 if ( dist < nearest ) {
728                         //printf( "NEARER node at (%.3f, %.3f, %.3f) is %.3f away is %s\n", pos[0], pos[1], pos[2], dist, ans );
729                         nearest = dist;
730                         tz = ans;
731                 }
732                 kd_res_next ( presults );
733         }
734         g_debug ( "TZ lookup found %d results - picked %s", kd_res_size(presults), tz );
735         kd_res_free ( presults );
736
737         return tz;
738 }
739
740 /**
741  * vu_get_time_string:
742  *
743  * @time_t: The time of which the string is wanted
744  * @format  The format of the time string - such as "%c"
745  * @vc:     Position of object for the time output - maybe NULL
746  *          (only applicable for VIK_TIME_REF_WORLD)
747  * @tz:     TimeZone string - maybe NULL.
748  *          (only applicable for VIK_TIME_REF_WORLD)
749  *          Useful to pass in the cached value from vu_get_tz_at_location() to save looking it up again for the same position
750  *
751  * Returns: A string of the time according to the time display property
752  */
753 gchar* vu_get_time_string ( time_t *time, const gchar *format, const VikCoord* vc, const gchar *tz )
754 {
755         if ( !format ) return NULL;
756         gchar *str = NULL;
757         switch ( a_vik_get_time_ref_frame() ) {
758                 case VIK_TIME_REF_UTC:
759                         str = g_malloc ( 64 );
760                         strftime ( str, 64, format, gmtime(time) ); // Always 'GMT'
761                         break;
762                 case VIK_TIME_REF_WORLD:
763                         if ( vc && !tz ) {
764                                 // No timezone specified so work it out
765                                 gchar *mytz = vu_get_tz_at_location ( vc );
766                                 if ( mytz ) {
767                                         GTimeZone *gtz = g_time_zone_new ( mytz );
768                                         str = time_string_tz ( time, format, gtz );
769                                         g_time_zone_unref ( gtz );
770                                 }
771                                 else {
772                                         // No results (e.g. could be in the middle of a sea)
773                                         // Fallback to simplistic method that doesn't take into account Timezones of countries.
774                                         struct LatLon ll;
775                                         vik_coord_to_latlon ( vc, &ll );
776                                         str = time_string_adjusted ( time, round ( ll.lon / 15.0 ) * 3600 );
777                                 }
778                         }
779                         else {
780                                 // Use specified timezone
781                                 GTimeZone *gtz = g_time_zone_new ( tz );
782                                 str = time_string_tz ( time, format, gtz );
783                                 g_time_zone_unref ( gtz );
784                         }
785                         break;
786                 default: // VIK_TIME_REF_LOCALE
787                         str = g_malloc ( 64 );
788                         strftime ( str, 64, format, localtime(time) );
789                         break;
790         }
791         return str;
792 }
793
794 /**
795  * vu_command_line:
796  *
797  * Apply any startup values that have been specified from the command line
798  * Values are defaulted in such a manner not to be applied when they haven't been specified
799  *
800  */
801 void vu_command_line ( VikWindow *vw, gdouble latitude, gdouble longitude, gint zoom_osm_level, gint map_id )
802 {
803         if ( !vw )
804                 return;
805
806         VikViewport *vvp = vik_window_viewport(vw);
807
808         if ( latitude != 0.0 || longitude != 0.0 ) {
809                 struct LatLon ll;
810                 ll.lat = latitude;
811                 ll.lon = longitude;
812                 vik_viewport_set_center_latlon ( vvp, &ll, TRUE );
813         }
814
815         if ( zoom_osm_level >= 0 ) {
816                 // Convert OSM zoom level into Viking zoom level
817                 gdouble mpp = exp ( (17-zoom_osm_level) * log(2) );
818                 if ( mpp > 1.0 )
819                         mpp = round (mpp);
820                 vik_viewport_set_zoom ( vvp, mpp );
821         }
822
823         if ( map_id >= 0 ) {
824                 guint my_map_id = map_id;
825                 if ( my_map_id == 0 )
826                         my_map_id = vik_maps_layer_get_default_map_type ();
827
828                 // Don't add map layer if one already exists
829                 GList *vmls = vik_layers_panel_get_all_layers_of_type(vik_window_layers_panel(vw), VIK_LAYER_MAPS, TRUE);
830                 int num_maps = g_list_length(vmls);
831                 gboolean add_map = TRUE;
832
833                 for (int i = 0; i < num_maps; i++) {
834                         VikMapsLayer *vml = (VikMapsLayer*)(vmls->data);
835                         gint id = vik_maps_layer_get_map_type(vml);
836                         if ( my_map_id == id ) {
837                                 add_map = FALSE;
838                                 break;
839                         }
840                         vmls = vmls->next;
841                 }
842
843                 if ( add_map ) {
844                         VikMapsLayer *vml = VIK_MAPS_LAYER ( vik_layer_create(VIK_LAYER_MAPS, vvp, FALSE) );
845                         vik_maps_layer_set_map_type ( vml, my_map_id );
846                         vik_layer_rename ( VIK_LAYER(vml), _("Map") );
847                         vik_aggregate_layer_add_layer ( vik_layers_panel_get_top_layer(vik_window_layers_panel(vw)), VIK_LAYER(vml), TRUE );
848                         vik_layer_emit_update ( VIK_LAYER(vml) );
849                 }
850         }
851 }
852
853 /**
854  * Copy the displayed text of a widget (should be a GtkButton ATM)
855  */
856 static void vu_copy_label ( GtkWidget *widget )
857 {
858         a_clipboard_copy (VIK_CLIPBOARD_DATA_TEXT, 0, 0, 0, gtk_button_get_label(GTK_BUTTON(widget)), NULL );
859 }
860
861 /**
862  * Generate a single entry menu to allow copying the displayed text of a widget (should be a GtkButton ATM)
863  */
864 void vu_copy_label_menu ( GtkWidget *widget, guint button )
865 {
866         GtkWidget *menu = gtk_menu_new();
867         GtkWidget *item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_COPY, NULL );
868         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(vu_copy_label), widget );
869         gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
870         gtk_widget_show ( item );
871         gtk_menu_popup ( GTK_MENU(menu), NULL, NULL, NULL, NULL, button, gtk_get_current_event_time() );
872 }
873
874 /**
875  * Work out the best zoom level for the LatLon area and set the viewport to that zoom level
876  */
877 void vu_zoom_to_show_latlons ( VikCoordMode mode, VikViewport *vvp, struct LatLon maxmin[2] )
878 {
879         /* First set the center [in case previously viewing from elsewhere] */
880         /* Then loop through zoom levels until provided positions are in view */
881         /* This method is not particularly fast - but should work well enough */
882         struct LatLon average = { (maxmin[0].lat+maxmin[1].lat)/2, (maxmin[0].lon+maxmin[1].lon)/2 };
883         VikCoord coord;
884         vik_coord_load_from_latlon ( &coord, mode, &average );
885         vik_viewport_set_center_coord ( vvp, &coord, TRUE );
886
887         /* Convert into definite 'smallest' and 'largest' positions */
888         struct LatLon minmin;
889         if ( maxmin[0].lat < maxmin[1].lat )
890                 minmin.lat = maxmin[0].lat;
891         else
892                 minmin.lat = maxmin[1].lat;
893
894         struct LatLon maxmax;
895         if ( maxmin[0].lon > maxmin[1].lon )
896                 maxmax.lon = maxmin[0].lon;
897         else
898                 maxmax.lon = maxmin[1].lon;
899
900         /* Never zoom in too far - generally not that useful, as too close ! */
901         /* Always recalculate the 'best' zoom level */
902         gdouble zoom = 1.0;
903         vik_viewport_set_zoom ( vvp, zoom );
904
905         gdouble min_lat, max_lat, min_lon, max_lon;
906         /* Should only be a maximum of about 18 iterations from min to max zoom levels */
907         while ( zoom <= VIK_VIEWPORT_MAX_ZOOM ) {
908                 vik_viewport_get_min_max_lat_lon ( vvp, &min_lat, &max_lat, &min_lon, &max_lon );
909                 /* NB I think the logic used in this test to determine if the bounds is within view
910                    fails if track goes across 180 degrees longitude.
911                    Hopefully that situation is not too common...
912                    Mind you viking doesn't really do edge locations to well anyway */
913                 if ( min_lat < minmin.lat &&
914                      max_lat > minmin.lat &&
915                      min_lon < maxmax.lon &&
916                      max_lon > maxmax.lon )
917                         /* Found within zoom level */
918                         break;
919
920                 /* Try next */
921                 zoom = zoom * 2;
922                 vik_viewport_set_zoom ( vvp, zoom );
923         }
924 }