]> git.street.me.uk Git - andy/viking.git/blame - src/vikutils.c
Enable activating waypoint search when pressing return in the entry field.
[andy/viking.git] / src / vikutils.c
CommitLineData
f93e0210
RN
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 */
5ab2942c
RN
22/*
23 * Dependencies in this file can be on anything.
24 * For functions with simple system dependencies put it in util.c
25 */
f93e0210 26#include <math.h>
5ab2942c 27#include <glib/gstdio.h>
f93e0210 28#include <glib/gi18n.h>
5ab2942c 29#include <gtk/gtk.h>
f93e0210 30
3f31bec4 31#include "viking.h"
f93e0210 32#include "vikutils.h"
5ab2942c
RN
33#include "globals.h"
34#include "download.h"
35#include "preferences.h"
36#include "vikmapslayer.h"
37#include "settings.h"
bc34c059 38#include "ui_util.h"
74562734
RN
39#include "dir.h"
40#include "misc/kdtree.h"
f93e0210
RN
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
fee4e142 50 * @climb: Vertical speed (Out of band (i.e. not in a trackpoint) value for display currently only for GPSD usage)
f93e0210
RN
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 */
fee4e142 56gchar* vu_trackpoint_formatted_message ( gchar *format_code, VikTrackpoint *trkpt, VikTrackpoint *trkpt_prev, VikTrack *trk, gdouble climb )
f93e0210
RN
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;
b312b935 101 if ( isnan(trkpt->speed) && trkpt_prev ) {
f93e0210 102 if ( trkpt->has_timestamp && trkpt_prev->has_timestamp ) {
2a2a49c2 103 if ( trkpt->timestamp != trkpt_prev->timestamp ) {
f93e0210
RN
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);
f93e0210
RN
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 }
2a2a49c2
RN
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 }
f93e0210
RN
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
fee4e142
RN
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
f93e0210
RN
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:
b22233bd 211 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
f93e0210
RN
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': {
74562734 228 gchar *msg;
f93e0210
RN
229 if ( trkpt->has_timestamp ) {
230 // Compact date time format
74562734 231 msg = vu_get_time_string ( &(trkpt->timestamp), "%x %X", &(trkpt->coord), NULL );
f93e0210
RN
232 }
233 else
74562734
RN
234 msg = g_strdup ("--");
235 values[i] = g_strdup_printf ( _("%sTime %s"), separator, msg );
236 g_free ( msg );
f93e0210
RN
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
62525c73
RN
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
f93e0210
RN
281 case 'D': {
282 if ( trk ) {
bb77b487 283 // Distance from start (along the track)
f93e0210
RN
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 ();
f93e0210
RN
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;
b22233bd
RN
292 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
293 dist_units_str = g_strdup ( _("NM") );
294 distd = VIK_METERS_TO_NAUTICAL_MILES(distd);
295 break;
f93e0210
RN
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
0e1b2664
RN
321 if ( trk )
322 values[i] = g_strdup_printf ( _("%sTrack: %s"), separator, trk->name );
f93e0210
RN
323 break;
324
b45865b4
RN
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
f93e0210
RN
332 default:
333 break;
334 }
335 }
336
337 g_free ( separator );
338 g_free ( speed_units_str );
339
bb77b487 340 gchar *msg = g_strconcat ( values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], values[8], NULL );
f93e0210
RN
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}
5ab2942c
RN
349
350typedef struct {
351 GtkWindow *window; // Layer needed for redrawing
352 gchar *version; // Image list
353} new_version_thread_data;
354
355static 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
371static void latest_version_thread ( GtkWindow *window )
372{
373 // Need to allow a few redirects, as SF file is often served from different server
686baff0 374 DownloadFileOptions options = { FALSE, FALSE, NULL, 5, NULL, NULL, NULL };
5ab2942c
RN
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 */
422void 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 */
479void vu_set_auto_features_on_first_run ( void )
480{
481 gboolean auto_features = FALSE;
89a7d25f
RN
482 gboolean set_defaults = FALSE;
483
5ab2942c
RN
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;
89a7d25f
RN
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;
5ab2942c
RN
495 }
496
497 if ( auto_features ) {
498 // Set Maps to autodownload
499 // Ensure the default is true
500 maps_layer_set_autodownload_default ( TRUE );
89a7d25f 501 set_defaults = TRUE;
5ab2942c
RN
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 }
89a7d25f
RN
529
530 // Ensure defaults are saved if changed
531 if ( set_defaults )
532 a_layer_defaults_save ();
5ab2942c 533}
1b14d0d2
RN
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 */
543gchar *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}
8ada46de 572
74562734
RN
573static struct kdtree *kd = NULL;
574
25528e25
RN
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 */
581static gint load_ll_tz_dir ( const gchar *dir )
74562734 582{
25528e25 583 gint inserted = 0;
74562734
RN
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" );
f5bbe114
RN
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 );
25528e25
RN
599 else
600 inserted++;
f5bbe114
RN
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 );
74562734 608 }
f5bbe114 609 fclose ( ff );
74562734 610 }
f5bbe114 611 else {
25528e25 612 g_warning ( "%s: Could not open %s", __FUNCTION__, lltz);
f5bbe114
RN
613 }
614 }
74562734 615 g_free ( lltz );
25528e25
RN
616
617 return inserted;
74562734
RN
618}
619
620/**
621 * vu_setup_lat_lon_tz_lookup:
622 *
623 * Can be called multiple times but only initializes the lookup once
624 */
625void 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();
25528e25 635 guint loaded = 0;
74562734
RN
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--) {
25528e25 639 loaded += load_ll_tz_dir(data_dirs[n_data_dirs-1]);
74562734
RN
640 }
641 g_strfreev ( data_dirs );
25528e25
RN
642
643 g_debug ( "%s: Loaded %d elements", __FUNCTION__, loaded );
644 if ( loaded == 0 )
645 g_critical ( "%s: No lat/lon/timezones loaded", __FUNCTION__ );
74562734
RN
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 */
654void vu_finalize_lat_lon_tz_lookup ()
655{
656 if ( kd ) {
657 kd_data_destructor ( kd, g_free );
658 kd_free ( kd );
659 }
660}
661
662static 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
671static 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
681static gchar* time_string_tz ( time_t *time, const gchar *format, GTimeZone *tz )
682{
683 GDateTime *utc = g_date_time_new_from_unix_utc (*time);
5eb031a0
RN
684 if ( !utc ) {
685 g_warning ( "%s: result from g_date_time_new_from_unix_utc() is NULL", __FUNCTION__ );
686 return NULL;
687 }
74562734
RN
688 GDateTime *local = g_date_time_to_timezone ( utc, tz );
689 if ( !local ) {
690 g_date_time_unref ( utc );
5eb031a0 691 g_warning ( "%s: result from g_date_time_to_timezone() is NULL", __FUNCTION__ );
74562734
RN
692 return NULL;
693 }
694 gchar *str = g_date_time_format ( local, format );
695
696 g_date_time_unref ( local );
697 g_date_time_unref ( utc );
698 return str;
699}
700
701#define VIK_SETTINGS_NEAREST_TZ_FACTOR "utils_nearest_tz_factor"
702/**
703 * vu_get_tz_at_location:
704 *
705 * @vc: Position for which the time zone is desired
706 *
707 * Returns: TimeZone string of the nearest known location. String may be NULL.
708 *
709 * Use the k-d tree method (http://en.wikipedia.org/wiki/Kd-tree) to quickly retreive
710 * the nearest location to the given position.
711 */
712gchar* vu_get_tz_at_location ( const VikCoord* vc )
713{
714 gchar *tz = NULL;
715 if ( !vc || !kd )
716 return tz;
717
718 struct LatLon ll;
719 vik_coord_to_latlon ( vc, &ll );
720 double pt[2] = { ll.lat, ll.lon };
721
722 gdouble nearest;
723 if ( !a_settings_get_double(VIK_SETTINGS_NEAREST_TZ_FACTOR, &nearest) )
724 nearest = 1.0;
725
726 struct kdres *presults = kd_nearest_range ( kd, pt, nearest );
727 while( !kd_res_end( presults ) ) {
728 double pos[2];
729 gchar *ans = (gchar*)kd_res_item ( presults, pos );
730 // compute the distance of the current result from the pt
731 double dist = sqrt( dist_sq( pt, pos, 2 ) );
732 if ( dist < nearest ) {
733 //printf( "NEARER node at (%.3f, %.3f, %.3f) is %.3f away is %s\n", pos[0], pos[1], pos[2], dist, ans );
734 nearest = dist;
735 tz = ans;
736 }
737 kd_res_next ( presults );
738 }
739 g_debug ( "TZ lookup found %d results - picked %s", kd_res_size(presults), tz );
740 kd_res_free ( presults );
741
742 return tz;
743}
744
8ada46de
RN
745/**
746 * vu_get_time_string:
747 *
748 * @time_t: The time of which the string is wanted
749 * @format The format of the time string - such as "%c"
750 * @vc: Position of object for the time output - maybe NULL
751 * (only applicable for VIK_TIME_REF_WORLD)
752 * @tz: TimeZone string - maybe NULL.
753 * (only applicable for VIK_TIME_REF_WORLD)
74562734 754 * Useful to pass in the cached value from vu_get_tz_at_location() to save looking it up again for the same position
8ada46de
RN
755 *
756 * Returns: A string of the time according to the time display property
757 */
758gchar* vu_get_time_string ( time_t *time, const gchar *format, const VikCoord* vc, const gchar *tz )
759{
760 if ( !format ) return NULL;
761 gchar *str = NULL;
762 switch ( a_vik_get_time_ref_frame() ) {
763 case VIK_TIME_REF_UTC:
764 str = g_malloc ( 64 );
765 strftime ( str, 64, format, gmtime(time) ); // Always 'GMT'
766 break;
767 case VIK_TIME_REF_WORLD:
74562734
RN
768 if ( vc && !tz ) {
769 // No timezone specified so work it out
770 gchar *mytz = vu_get_tz_at_location ( vc );
771 if ( mytz ) {
772 GTimeZone *gtz = g_time_zone_new ( mytz );
773 str = time_string_tz ( time, format, gtz );
774 g_time_zone_unref ( gtz );
775 }
776 else {
777 // No results (e.g. could be in the middle of a sea)
778 // Fallback to simplistic method that doesn't take into account Timezones of countries.
779 struct LatLon ll;
780 vik_coord_to_latlon ( vc, &ll );
781 str = time_string_adjusted ( time, round ( ll.lon / 15.0 ) * 3600 );
782 }
783 }
784 else {
785 // Use specified timezone
786 GTimeZone *gtz = g_time_zone_new ( tz );
787 str = time_string_tz ( time, format, gtz );
788 g_time_zone_unref ( gtz );
8ada46de
RN
789 }
790 break;
791 default: // VIK_TIME_REF_LOCALE
792 str = g_malloc ( 64 );
793 strftime ( str, 64, format, localtime(time) );
794 break;
795 }
796 return str;
797}
c12d4347
RN
798
799/**
800 * vu_command_line:
801 *
802 * Apply any startup values that have been specified from the command line
803 * Values are defaulted in such a manner not to be applied when they haven't been specified
804 *
805 */
806void vu_command_line ( VikWindow *vw, gdouble latitude, gdouble longitude, gint zoom_osm_level, gint map_id )
807{
808 if ( !vw )
809 return;
810
811 VikViewport *vvp = vik_window_viewport(vw);
812
813 if ( latitude != 0.0 || longitude != 0.0 ) {
814 struct LatLon ll;
815 ll.lat = latitude;
816 ll.lon = longitude;
817 vik_viewport_set_center_latlon ( vvp, &ll, TRUE );
818 }
819
820 if ( zoom_osm_level >= 0 ) {
821 // Convert OSM zoom level into Viking zoom level
822 gdouble mpp = exp ( (17-zoom_osm_level) * log(2) );
823 if ( mpp > 1.0 )
824 mpp = round (mpp);
825 vik_viewport_set_zoom ( vvp, mpp );
826 }
827
828 if ( map_id >= 0 ) {
829 guint my_map_id = map_id;
830 if ( my_map_id == 0 )
831 my_map_id = vik_maps_layer_get_default_map_type ();
832
833 // Don't add map layer if one already exists
834 GList *vmls = vik_layers_panel_get_all_layers_of_type(vik_window_layers_panel(vw), VIK_LAYER_MAPS, TRUE);
835 int num_maps = g_list_length(vmls);
836 gboolean add_map = TRUE;
837
838 for (int i = 0; i < num_maps; i++) {
839 VikMapsLayer *vml = (VikMapsLayer*)(vmls->data);
840 gint id = vik_maps_layer_get_map_type(vml);
841 if ( my_map_id == id ) {
842 add_map = FALSE;
843 break;
844 }
845 vmls = vmls->next;
846 }
847
848 if ( add_map ) {
849 VikMapsLayer *vml = VIK_MAPS_LAYER ( vik_layer_create(VIK_LAYER_MAPS, vvp, FALSE) );
850 vik_maps_layer_set_map_type ( vml, my_map_id );
851 vik_layer_rename ( VIK_LAYER(vml), _("Map") );
852 vik_aggregate_layer_add_layer ( vik_layers_panel_get_top_layer(vik_window_layers_panel(vw)), VIK_LAYER(vml), TRUE );
853 vik_layer_emit_update ( VIK_LAYER(vml) );
854 }
855 }
856}
bc996077
RN
857
858/**
859 * Copy the displayed text of a widget (should be a GtkButton ATM)
860 */
861static void vu_copy_label ( GtkWidget *widget )
862{
863 a_clipboard_copy (VIK_CLIPBOARD_DATA_TEXT, 0, 0, 0, gtk_button_get_label(GTK_BUTTON(widget)), NULL );
864}
865
866/**
867 * Generate a single entry menu to allow copying the displayed text of a widget (should be a GtkButton ATM)
868 */
869void vu_copy_label_menu ( GtkWidget *widget, guint button )
870{
871 GtkWidget *menu = gtk_menu_new();
872 GtkWidget *item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_COPY, NULL );
873 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(vu_copy_label), widget );
874 gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
875 gtk_widget_show ( item );
876 gtk_menu_popup ( GTK_MENU(menu), NULL, NULL, NULL, NULL, button, gtk_get_current_event_time() );
877}
1c3ad8c9
RN
878
879/**
880 * Work out the best zoom level for the LatLon area and set the viewport to that zoom level
881 */
882void vu_zoom_to_show_latlons ( VikCoordMode mode, VikViewport *vvp, struct LatLon maxmin[2] )
883{
884 /* First set the center [in case previously viewing from elsewhere] */
885 /* Then loop through zoom levels until provided positions are in view */
886 /* This method is not particularly fast - but should work well enough */
887 struct LatLon average = { (maxmin[0].lat+maxmin[1].lat)/2, (maxmin[0].lon+maxmin[1].lon)/2 };
888 VikCoord coord;
889 vik_coord_load_from_latlon ( &coord, mode, &average );
890 vik_viewport_set_center_coord ( vvp, &coord, TRUE );
891
892 /* Convert into definite 'smallest' and 'largest' positions */
893 struct LatLon minmin;
894 if ( maxmin[0].lat < maxmin[1].lat )
895 minmin.lat = maxmin[0].lat;
896 else
897 minmin.lat = maxmin[1].lat;
898
899 struct LatLon maxmax;
900 if ( maxmin[0].lon > maxmin[1].lon )
901 maxmax.lon = maxmin[0].lon;
902 else
903 maxmax.lon = maxmin[1].lon;
904
905 /* Never zoom in too far - generally not that useful, as too close ! */
906 /* Always recalculate the 'best' zoom level */
907 gdouble zoom = 1.0;
908 vik_viewport_set_zoom ( vvp, zoom );
909
910 gdouble min_lat, max_lat, min_lon, max_lon;
911 /* Should only be a maximum of about 18 iterations from min to max zoom levels */
912 while ( zoom <= VIK_VIEWPORT_MAX_ZOOM ) {
913 vik_viewport_get_min_max_lat_lon ( vvp, &min_lat, &max_lat, &min_lon, &max_lon );
914 /* NB I think the logic used in this test to determine if the bounds is within view
915 fails if track goes across 180 degrees longitude.
916 Hopefully that situation is not too common...
917 Mind you viking doesn't really do edge locations to well anyway */
918 if ( min_lat < minmin.lat &&
919 max_lat > minmin.lat &&
920 min_lon < maxmax.lon &&
921 max_lon > maxmax.lon )
922 /* Found within zoom level */
923 break;
924
925 /* Try next */
926 zoom = zoom * 2;
927 vik_viewport_set_zoom ( vvp, zoom );
928 }
929}