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