1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
3 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
5 * Copyright (C) 2013-2017, Rob Norris <rw_norris@hotmail.com>
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.
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.
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
23 * Dependencies in this file can be on anything.
24 * For functions with simple system dependencies put it in util.c
27 #include <glib/gstdio.h>
28 #include <glib/gi18n.h>
35 #include "preferences.h"
36 #include "vikmapslayer.h"
40 #include "misc/kdtree.h"
41 #include "misc/gtkhtml-private.h"
43 #define FMT_MAX_NUMBER_CODES 9
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)
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
57 gchar* vu_trackpoint_formatted_message ( gchar *format_code, VikTrackpoint *trkpt, VikTrackpoint *trkpt_prev, VikTrack *trk, gdouble climb )
64 len = strlen ( format_code );
65 if ( len > FMT_MAX_NUMBER_CODES )
66 len = FMT_MAX_NUMBER_CODES;
68 gchar* values[FMT_MAX_NUMBER_CODES];
70 for ( i = 0; i < FMT_MAX_NUMBER_CODES; i++ ) {
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") );
80 case VIK_UNITS_SPEED_METRES_PER_SECOND:
81 speed_units_str = g_strdup ( _("m/s") );
83 case VIK_UNITS_SPEED_KNOTS:
84 speed_units_str = g_strdup ( _("knots") );
87 // VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
88 speed_units_str = g_strdup ( _("km/h") );
92 gchar *separator = g_strdup ( " | " );
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
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 ) {
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
111 speedtype = g_strdup ( "**" );
114 speedtype = g_strdup ( "**" );
117 speed = trkpt->speed;
118 speedtype = g_strdup ( "" );
120 switch (speed_units) {
121 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
122 speed = VIK_MPS_TO_KPH(speed);
124 case VIK_UNITS_SPEED_MILES_PER_HOUR:
125 speed = VIK_MPS_TO_MPH(speed);
127 case VIK_UNITS_SPEED_KNOTS:
128 speed = VIK_MPS_TO_KNOTS(speed);
131 // VIK_UNITS_SPEED_METRES_PER_SECOND:
132 // Already in m/s so nothing to do
136 values[i] = g_strdup_printf ( _("%sSpeed%s %.1f%s"), separator, speedtype, speed, speed_units_str );
137 g_free ( speedtype );
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
153 speedtype = g_strdup ( "**" ); // Unavailable
156 speedtype = g_strdup ( "**" );
160 speedtype = g_strdup ( "" );
162 switch (speed_units) {
163 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
164 speed = VIK_MPS_TO_KPH(speed);
166 case VIK_UNITS_SPEED_MILES_PER_HOUR:
167 speed = VIK_MPS_TO_MPH(speed);
169 case VIK_UNITS_SPEED_KNOTS:
170 speed = VIK_MPS_TO_KNOTS(speed);
173 // VIK_UNITS_SPEED_METRES_PER_SECOND:
174 // Already in m/s so nothing to do
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 );
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)) );
190 //VIK_UNITS_HEIGHT_METRES:
191 values[i] = g_strdup_printf ( _("%sAlt %dm"), separator, (int)round(trkpt->altitude) );
198 gint heading = isnan(trkpt->course) ? 0 : (gint)round(trkpt->course);
199 values[i] = g_strdup_printf ( _("%sCourse %03d\302\260" ), separator, heading );
205 gint diff = (gint) round ( vik_coord_diff ( &(trkpt->coord), &(trkpt_prev->coord) ) );
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") );
216 // VIK_UNITS_DISTANCE_KILOMETRES:
217 dist_units_str = g_strdup ( _("m") );
221 values[i] = g_strdup_printf ( _("%sDistance diff %d%s"), separator, diff, dist_units_str );
223 g_free ( dist_units_str );
230 if ( trkpt->has_timestamp ) {
231 // Compact date time format
232 msg = vu_get_time_string ( &(trkpt->timestamp), "%x %X", &(trkpt->coord), NULL );
235 msg = g_strdup ("--");
236 values[i] = g_strdup_printf ( _("%sTime %s"), separator, msg );
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 );
251 case 'X': values[i] = g_strdup_printf ( _("%sNo. of Sats %d"), separator, trkpt->nsats ); break;
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);
266 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
267 dist_units_str = g_strdup ( _("NM") );
268 dist = VIK_METERS_TO_NAUTICAL_MILES(dist);
271 // VIK_UNITS_DISTANCE_KILOMETRES:
272 dist_units_str = g_strdup ( _("km") );
273 dist = dist / 1000.0;
276 values[i] = g_strdup_printf ( _("%sTo End %.2f%s"), separator, dist, dist_units_str );
277 g_free ( dist_units_str );
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);
293 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
294 dist_units_str = g_strdup ( _("NM") );
295 distd = VIK_METERS_TO_NAUTICAL_MILES(distd);
298 // VIK_UNITS_DISTANCE_KILOMETRES:
299 dist_units_str = g_strdup ( _("km") );
300 distd = distd / 1000.0;
303 values[i] = g_strdup_printf ( _("%sDistance along %.2f%s"), separator, distd, dist_units_str );
304 g_free ( dist_units_str );
310 // Location (Lat/Long)
311 gchar *lat = NULL, *lon = NULL;
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 );
321 case 'N': // Name of track
323 values[i] = g_strdup_printf ( _("%sTrack: %s"), separator, trk->name );
326 case 'E': // Name of trackpoint if available
328 values[i] = g_strdup_printf ( "%s%s", separator, trkpt->name );
330 values[i] = g_strdup ( "" );
338 g_free ( separator );
339 g_free ( speed_units_str );
341 gchar *msg = g_strconcat ( values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], values[8], NULL );
343 for ( i = 0; i < FMT_MAX_NUMBER_CODES; i++ ) {
344 if ( values[i] != '\0' )
345 g_free ( values[i] );
352 GtkWindow *window; // Layer needed for redrawing
353 gchar *version; // Image list
354 } new_version_thread_data;
356 static gboolean new_version_available_message ( new_version_thread_data *nvtd )
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/" );
365 g_free ( nvtd->version );
370 #define VIK_SETTINGS_VERSION_CHECKED_DATE "version_checked_date"
372 static void latest_version_thread ( GtkWindow *window )
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" );
382 GMappedFile *mf = g_mapped_file_new ( filename, FALSE, NULL );
386 gchar *text = g_mapped_file_get_contents ( mf );
388 gint latest_version = viking_version_to_number ( text );
389 gint my_version = viking_version_to_number ( VIKING_VERSION );
391 g_debug ( "The lastest version is: %s", text );
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 );
400 g_debug ( "Running the lastest version: %s", VIKING_VERSION );
402 g_mapped_file_unref ( mf );
404 g_remove ( filename );
408 // Update last checked time
410 g_get_current_time ( &time );
411 a_settings_set_string ( VIK_SETTINGS_VERSION_CHECKED_DATE, g_time_val_to_iso8601(&time) );
414 #define VIK_SETTINGS_VERSION_CHECK_PERIOD "version_check_period_days"
417 * vu_check_latest_version:
418 * @window: Somewhere where we may need use the display to inform the user about the version status
420 * Periodically checks the released latest VERSION file on the website to compare with the running version
423 void vu_check_latest_version ( GtkWindow *window )
425 if ( ! a_vik_get_check_version () )
428 gboolean do_check = FALSE;
431 if ( ! a_settings_get_integer ( VIK_SETTINGS_VERSION_CHECK_PERIOD, &check_period ) ) {
435 // Get last checked date...
436 GDate *gdate_last = g_date_new();
437 GDate *gdate_now = g_date_new();
439 gchar *last_checked_date = NULL;
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 );
453 g_get_current_time ( &time_now );
454 g_date_set_time_val ( gdate_now, &time_now );
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 )
463 g_date_free ( gdate_last );
464 g_date_free ( gdate_now );
467 #if GLIB_CHECK_VERSION (2, 32, 0)
468 g_thread_try_new ( "latest_version_thread", (GThreadFunc)latest_version_thread, window, NULL );
470 g_thread_create ( (GThreadFunc)latest_version_thread, window, FALSE, NULL );
476 * vu_set_auto_features_on_first_run:
478 * Ask the user's opinion to set some of Viking's default behaviour
480 void vu_set_auto_features_on_first_run ( void )
482 gboolean auto_features = FALSE;
483 gboolean set_defaults = FALSE;
485 if ( a_vik_very_first_run () ) {
487 GtkWidget *win = gtk_window_new ( GTK_WINDOW_TOPLEVEL );
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;
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 );
498 if ( auto_features ) {
499 // Set Maps to autodownload
500 // Ensure the default is true
501 maps_layer_set_autodownload_default ( TRUE );
504 // Simplistic repeat of preference settings
505 // Only the name & type are important for setting a preference via this 'external' way
507 // Enable auto add map +
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}, };
512 VikLayerParamData vlp_data;
514 a_preferences_run_setparam ( vlp_data, pref_add_map );
516 vlp_data.u = VIK_STARTUP_METHOD_AUTO_LOCATION;
517 a_preferences_run_setparam ( vlp_data, pref_startup_method );
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
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, }, };
524 a_preferences_run_setparam ( vlp_data, pref_startup_version_check );
527 // Ensure settings are saved for next time
528 a_preferences_save_to_file ();
531 // Ensure defaults are saved if changed
533 a_layer_defaults_save ();
537 * vu_get_canonical_filename:
539 * Returns: Canonical absolute filename
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
544 gchar *vu_get_canonical_filename ( VikLayer *vl, const gchar *filename )
546 gchar *canonical = NULL;
550 if ( g_path_is_absolute ( filename ) )
551 canonical = g_strdup ( filename );
553 const gchar *vw_filename = vik_window_get_filename ( VIK_WINDOW_FROM_WIDGET (vl->vvp) );
554 gchar *dirpath = NULL;
556 dirpath = g_path_get_dirname ( vw_filename );
558 dirpath = g_get_current_dir(); // Fallback - if here then probably can't create the correct path
561 if ( g_path_is_absolute ( dirpath ) )
562 full = g_strconcat ( dirpath, G_DIR_SEPARATOR_S, filename, NULL );
564 full = g_strconcat ( g_get_current_dir(), G_DIR_SEPARATOR_S, dirpath, G_DIR_SEPARATOR_S, filename, NULL );
566 canonical = file_realpath_dup ( full ); // resolved
574 static struct kdtree *kd = NULL;
578 * @dir: The directory from which to load the latlontz.txt file
580 * Returns: The number of elements within the latlontz.txt loaded
582 static gint load_ll_tz_dir ( const gchar *dir )
585 gchar *lltz = g_build_filename ( dir, "latlontz.txt", NULL );
586 if ( g_access(lltz, R_OK) == 0 ) {
589 FILE *ff = g_fopen ( lltz, "r" );
591 while ( fgets ( buffer, 4096, ff ) ) {
593 gchar **components = g_strsplit (buffer, " ", 3);
594 guint nn = g_strv_length ( components );
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 );
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] );
606 g_warning ( "Line %ld of latlontz.txt does not have 3 parts", line_num );
608 g_free ( components );
613 g_warning ( "%s: Could not open %s", __FUNCTION__, lltz);
622 * vu_setup_lat_lon_tz_lookup:
624 * Can be called multiple times but only initializes the lookup once
626 void vu_setup_lat_lon_tz_lookup ()
634 // Look in the directories of data path
635 gchar **data_dirs = a_get_viking_data_path();
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]);
642 g_strfreev ( data_dirs );
644 g_debug ( "%s: Loaded %d elements", __FUNCTION__, loaded );
646 g_critical ( "%s: No lat/lon/timezones loaded", __FUNCTION__ );
650 * vu_finalize_lat_lon_tz_lookup:
652 * Clear memory used by the lookup.
653 * only call on program exit
655 void vu_finalize_lat_lon_tz_lookup ()
658 kd_data_destructor ( kd, g_free );
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;
672 static gchar* time_string_adjusted ( time_t *time, gint offset_s )
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) );
682 static gchar* time_string_tz ( time_t *time, const gchar *format, GTimeZone *tz )
684 GDateTime *utc = g_date_time_new_from_unix_utc (*time);
686 g_warning ( "%s: result from g_date_time_new_from_unix_utc() is NULL", __FUNCTION__ );
689 GDateTime *local = g_date_time_to_timezone ( utc, tz );
691 g_date_time_unref ( utc );
692 g_warning ( "%s: result from g_date_time_to_timezone() is NULL", __FUNCTION__ );
695 gchar *str = g_date_time_format ( local, format );
697 g_date_time_unref ( local );
698 g_date_time_unref ( utc );
702 #define VIK_SETTINGS_NEAREST_TZ_FACTOR "utils_nearest_tz_factor"
704 * vu_get_tz_at_location:
706 * @vc: Position for which the time zone is desired
708 * Returns: TimeZone string of the nearest known location. String may be NULL.
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.
713 gchar* vu_get_tz_at_location ( const VikCoord* vc )
720 vik_coord_to_latlon ( vc, &ll );
721 double pt[2] = { ll.lat, ll.lon };
724 if ( !a_settings_get_double(VIK_SETTINGS_NEAREST_TZ_FACTOR, &nearest) )
727 struct kdres *presults = kd_nearest_range ( kd, pt, nearest );
728 while( !kd_res_end( presults ) ) {
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 );
738 kd_res_next ( presults );
740 g_debug ( "TZ lookup found %d results - picked %s", kd_res_size(presults), tz );
741 kd_res_free ( presults );
747 * vu_get_time_string:
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
757 * Returns: A string of the time according to the time display property
759 gchar* vu_get_time_string ( time_t *time, const gchar *format, const VikCoord* vc, const gchar *tz )
761 if ( !format ) return 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'
768 case VIK_TIME_REF_WORLD:
770 // No timezone specified so work it out
771 gchar *mytz = vu_get_tz_at_location ( vc );
773 GTimeZone *gtz = g_time_zone_new ( mytz );
774 str = time_string_tz ( time, format, gtz );
775 g_time_zone_unref ( gtz );
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.
781 vik_coord_to_latlon ( vc, &ll );
782 str = time_string_adjusted ( time, round ( ll.lon / 15.0 ) * 3600 );
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 );
792 default: // VIK_TIME_REF_LOCALE
793 str = g_malloc ( 64 );
794 strftime ( str, 64, format, localtime(time) );
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
807 void vu_command_line ( VikWindow *vw, gdouble latitude, gdouble longitude, gint zoom_osm_level, gint map_id )
812 VikViewport *vvp = vik_window_viewport(vw);
814 if ( latitude != 0.0 || longitude != 0.0 ) {
818 vik_viewport_set_center_latlon ( vvp, &ll, TRUE );
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) );
826 vik_viewport_set_zoom ( vvp, mpp );
830 guint my_map_id = map_id;
831 if ( my_map_id == 0 )
832 my_map_id = vik_maps_layer_get_default_map_type ();
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;
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 ) {
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) );
860 * Copy the displayed text of a widget (should be a GtkButton ATM)
862 static void vu_copy_label ( GtkWidget *widget )
864 a_clipboard_copy (VIK_CLIPBOARD_DATA_TEXT, 0, 0, 0, gtk_button_get_label(GTK_BUTTON(widget)), NULL );
868 * Generate a single entry menu to allow copying the displayed text of a widget (should be a GtkButton ATM)
870 void vu_copy_label_menu ( GtkWidget *widget, guint button )
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() );
881 * Work out the best zoom level for the LatLon area and set the viewport to that zoom level
883 void vu_zoom_to_show_latlons ( VikCoordMode mode, VikViewport *vvp, struct LatLon maxmin[2] )
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 };
890 vik_coord_load_from_latlon ( &coord, mode, &average );
891 vik_viewport_set_center_coord ( vvp, &coord, TRUE );
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;
898 minmin.lat = maxmin[1].lat;
900 struct LatLon maxmax;
901 if ( maxmin[0].lon > maxmin[1].lon )
902 maxmax.lon = maxmin[0].lon;
904 maxmax.lon = maxmin[1].lon;
906 /* Never zoom in too far - generally not that useful, as too close ! */
907 /* Always recalculate the 'best' zoom level */
909 vik_viewport_set_zoom ( vvp, zoom );
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 */
928 vik_viewport_set_zoom ( vvp, zoom );
933 * Set the waypoint image given a URI
935 void vu_waypoint_set_image_uri ( VikWaypoint *wp, const gchar *uri, const gchar *dirpath )
937 gchar *filename = gtk_html_filename_from_uri ( uri );
938 if ( g_path_is_absolute ( filename ) ) {
939 vik_waypoint_set_image ( wp, filename );
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 );