]> git.street.me.uk Git - andy/viking.git/blame - src/vikutils.c
Remove definition of a non existant function
[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 *
8a439f31 5 * Copyright (C) 2013-2017, Rob Norris <rw_norris@hotmail.com>
f93e0210
RN
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"
8a439f31 41#include "misc/gtkhtml-private.h"
f93e0210
RN
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
fee4e142 51 * @climb: Vertical speed (Out of band (i.e. not in a trackpoint) value for display currently only for GPSD usage)
f93e0210
RN
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 */
fee4e142 57gchar* vu_trackpoint_formatted_message ( gchar *format_code, VikTrackpoint *trkpt, VikTrackpoint *trkpt_prev, VikTrack *trk, gdouble climb )
f93e0210
RN
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;
b312b935 102 if ( isnan(trkpt->speed) && trkpt_prev ) {
f93e0210 103 if ( trkpt->has_timestamp && trkpt_prev->has_timestamp ) {
2a2a49c2 104 if ( trkpt->timestamp != trkpt_prev->timestamp ) {
f93e0210
RN
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);
f93e0210
RN
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 }
2a2a49c2
RN
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 }
f93e0210
RN
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
fee4e142
RN
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
f93e0210
RN
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:
b22233bd 212 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
f93e0210
RN
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': {
74562734 229 gchar *msg;
f93e0210
RN
230 if ( trkpt->has_timestamp ) {
231 // Compact date time format
74562734 232 msg = vu_get_time_string ( &(trkpt->timestamp), "%x %X", &(trkpt->coord), NULL );
f93e0210
RN
233 }
234 else
74562734
RN
235 msg = g_strdup ("--");
236 values[i] = g_strdup_printf ( _("%sTime %s"), separator, msg );
237 g_free ( msg );
f93e0210
RN
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
62525c73
RN
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
f93e0210
RN
282 case 'D': {
283 if ( trk ) {
bb77b487 284 // Distance from start (along the track)
f93e0210
RN
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 ();
f93e0210
RN
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;
b22233bd
RN
293 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
294 dist_units_str = g_strdup ( _("NM") );
295 distd = VIK_METERS_TO_NAUTICAL_MILES(distd);
296 break;
f93e0210
RN
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
0e1b2664
RN
322 if ( trk )
323 values[i] = g_strdup_printf ( _("%sTrack: %s"), separator, trk->name );
f93e0210
RN
324 break;
325
b45865b4
RN
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
f93e0210
RN
333 default:
334 break;
335 }
336 }
337
338 g_free ( separator );
339 g_free ( speed_units_str );
340
bb77b487 341 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
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}
5ab2942c
RN
350
351typedef struct {
352 GtkWindow *window; // Layer needed for redrawing
353 gchar *version; // Image list
354} new_version_thread_data;
355
356static 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
372static void latest_version_thread ( GtkWindow *window )
373{
374 // Need to allow a few redirects, as SF file is often served from different server
686baff0 375 DownloadFileOptions options = { FALSE, FALSE, NULL, 5, NULL, NULL, NULL };
5ab2942c
RN
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 */
423void 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 */
480void vu_set_auto_features_on_first_run ( void )
481{
482 gboolean auto_features = FALSE;
89a7d25f
RN
483 gboolean set_defaults = FALSE;
484
5ab2942c
RN
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;
89a7d25f
RN
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;
5ab2942c
RN
496 }
497
498 if ( auto_features ) {
499 // Set Maps to autodownload
500 // Ensure the default is true
501 maps_layer_set_autodownload_default ( TRUE );
89a7d25f 502 set_defaults = TRUE;
5ab2942c
RN
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 }
89a7d25f
RN
530
531 // Ensure defaults are saved if changed
532 if ( set_defaults )
533 a_layer_defaults_save ();
5ab2942c 534}
1b14d0d2
RN
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 */
544gchar *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}
8ada46de 573
74562734
RN
574static struct kdtree *kd = NULL;
575
25528e25
RN
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 */
582static gint load_ll_tz_dir ( const gchar *dir )
74562734 583{
25528e25 584 gint inserted = 0;
74562734
RN
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" );
f5bbe114
RN
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 );
25528e25
RN
600 else
601 inserted++;
f5bbe114
RN
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 );
74562734 609 }
f5bbe114 610 fclose ( ff );
74562734 611 }
f5bbe114 612 else {
25528e25 613 g_warning ( "%s: Could not open %s", __FUNCTION__, lltz);
f5bbe114
RN
614 }
615 }
74562734 616 g_free ( lltz );
25528e25
RN
617
618 return inserted;
74562734
RN
619}
620
621/**
622 * vu_setup_lat_lon_tz_lookup:
623 *
624 * Can be called multiple times but only initializes the lookup once
625 */
626void 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();
25528e25 636 guint loaded = 0;
74562734
RN
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--) {
25528e25 640 loaded += load_ll_tz_dir(data_dirs[n_data_dirs-1]);
74562734
RN
641 }
642 g_strfreev ( data_dirs );
25528e25
RN
643
644 g_debug ( "%s: Loaded %d elements", __FUNCTION__, loaded );
645 if ( loaded == 0 )
646 g_critical ( "%s: No lat/lon/timezones loaded", __FUNCTION__ );
74562734
RN
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 */
655void vu_finalize_lat_lon_tz_lookup ()
656{
657 if ( kd ) {
658 kd_data_destructor ( kd, g_free );
659 kd_free ( kd );
660 }
661}
662
663static 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
672static 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
682static gchar* time_string_tz ( time_t *time, const gchar *format, GTimeZone *tz )
683{
684 GDateTime *utc = g_date_time_new_from_unix_utc (*time);
5eb031a0
RN
685 if ( !utc ) {
686 g_warning ( "%s: result from g_date_time_new_from_unix_utc() is NULL", __FUNCTION__ );
687 return NULL;
688 }
74562734
RN
689 GDateTime *local = g_date_time_to_timezone ( utc, tz );
690 if ( !local ) {
691 g_date_time_unref ( utc );
5eb031a0 692 g_warning ( "%s: result from g_date_time_to_timezone() is NULL", __FUNCTION__ );
74562734
RN
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 */
713gchar* 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
8ada46de
RN
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)
74562734 755 * 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
756 *
757 * Returns: A string of the time according to the time display property
758 */
759gchar* 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:
74562734
RN
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 );
8ada46de
RN
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}
c12d4347
RN
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 */
807void 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}
bc996077
RN
858
859/**
860 * Copy the displayed text of a widget (should be a GtkButton ATM)
861 */
862static 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 */
870void 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}
1c3ad8c9
RN
879
880/**
881 * Work out the best zoom level for the LatLon area and set the viewport to that zoom level
882 */
883void 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}
8a439f31
RN
931
932/**
933 * Set the waypoint image given a URI
934 */
935void 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}