~foxtrotgps-team/foxtrotgps/trunk

208.1.4 by Dr. Tilmann Bubeck
Add route-planning functionality
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
#include <time.h>
5
#include <errno.h>
6
#include <math.h>
7
8
#include <gtk/gtk.h>
9
#include <glib.h>
10
#include <glib/gstdio.h>
11
12
#include <sys/types.h>
13
#include <sys/stat.h>
14
#include <fcntl.h>
15
16
#include <libxml/parser.h>
17
#include <libxml/tree.h>
18
#include <libxml/encoding.h>
19
#include <libxml/xmlwriter.h>
20
21
#include "globals.h"
22
#include "tile_management.h"
23
#include "route.h"
24
#include "wp.h"
25
#include "interface.h"
26
#include "support.h"
27
#include "converter.h"
28
#include "map_management.h"
29
#include "util.h"
30
#include "tracks.h"
31
32
#define GPX_ENCODING "utf-8"
33
34
/**
35
 * This is a GSList containing the waypoints of the route
36
 * which we are planing. It is the central data structure for route.c
37
 */
38
static GSList *route = NULL;
39
40
static GdkPixbuf	*wp_icon = NULL;
41
static int              wp_icon_width = 0;
42
static int              wp_icon_height = 0;
43
44
/**
45
 * Clear the route.
46
 */
47
void
48
reset_route ()
49
{
50
	route = NULL;
51
}
52
53
/**
54
 * Append a new waypoint at the end of the route.
55
 */
56
void
57
append_waypoint_to_route (double lat, double lon)
58
{
59
	waypoint_t *tp = g_new0 (waypoint_t,1);
60
61
	tp->lat = lat;
62
	tp->lon = lon;
63
64
	route = g_slist_append (route, tp);
65
}
66
67
/**
68
 * Change the position of the given waypoint to the new position.
69
 */
70
void
71
change_waypoint_of_route (waypoint_t *tp, double lat, double lon)
72
{
73
	tp->lat = lat;
74
	tp->lon = lon;
75
}
76
77
/**
78
 * Delete the given waypoint from the route.
79
 */
80
void
81
delete_waypoint_of_route (waypoint_t *wp)
82
{
83
	route = g_slist_remove (route, wp);
84
	g_free (wp);
85
}
86
87
/**
88
 * Insert a new waypoint before the given waypoint. This waypoint lies
89
 * between the given waypoint and the waypoint before this waypoint.
90
 */
91
void
92
insert_waypoint_before_of_route (waypoint_t *wp)
93
{
94
	waypoint_t *previous_wp;
95
	int position;
96
97
	position = g_slist_index (route, wp);
98
	if (position > 0) {
99
		previous_wp = g_slist_nth_data (route, position - 1);
100
101
		waypoint_t *new_wp = g_new0 (waypoint_t,1);
102
103
		new_wp->lat = previous_wp->lat - (previous_wp->lat - wp->lat) / 2;
104
		new_wp->lon = previous_wp->lon - (previous_wp->lon - wp->lon) / 2;
105
106
		route = g_slist_insert (route, new_wp, position + 0);
107
	}
108
}
109
110
/**
111
 * Find the waypoint which wp_icon is at the given mouse position.
112
 * Return that waypoint or NULL if none was found.
113
 */
114
waypoint_t *
115
find_routepoint (int mouse_x, int mouse_y)
116
{
117
	GSList *list;
118
	double lat;
119
	double lon;
120
	int pixel_x, pixel_y, x,y;
121
122
	for (list = route; list != NULL; list = list->next)
123
	{
124
		waypoint_t *tp = list->data;
125
126
		lat = tp->lat;
127
		lon = tp->lon;
128
129
		pixel_x = lon2pixel (global_zoom, lon);
130
		pixel_y = lat2pixel (global_zoom, lat);
131
132
		x = pixel_x - global_x + wp_icon_width / 2;
133
		y = pixel_y - global_y - wp_icon_height / 2;
134
135
		if (abs (x - mouse_x) < wp_icon_width / 2 &&
136
		    abs (y - mouse_y) < wp_icon_height / 2)
137
		{
138
			return tp;
139
		}
140
	}
141
142
	return NULL;
143
}
144
145
static
146
void
147
draw_arrow (GdkGC *gc, int start_x, int start_y, int end_x, int end_y)
148
{
149
	int x1,y1,x2,y2;
150
	double angle = atan2 (end_y - start_y, end_x - start_x) + M_PI;
151
	double arrow_length = 20;
152
	double arrow_degrees = M_PI / 10;
153
307 by Paul Wise
Update links
154
	/* https://kapo-cpp.blogspot.com/2008/10/drawing-arrows-with-cairo.html */
208.1.4 by Dr. Tilmann Bubeck
Add route-planning functionality
155
156
	x1 = end_x + arrow_length * cos (angle - arrow_degrees);
157
	y1 = end_y + arrow_length * sin (angle - arrow_degrees);
158
	x2 = end_x + arrow_length * cos (angle + arrow_degrees);
159
	y2 = end_y + arrow_length * sin (angle + arrow_degrees);
160
161
	gdk_draw_line (pixmap, gc, start_x, start_y, end_x, end_y);
162
	gdk_draw_line (pixmap, gc, end_x, end_y, x1, y1);
163
	gdk_draw_line (pixmap, gc, x1, y1, x2, y2);
164
	gdk_draw_line (pixmap, gc, x2, y2, end_x, end_y);
165
}
166
167
static
168
void
169
draw_line_of_route (GdkGC *gc, int x1, int y1, int x2, int y2)
170
{
171
	if (abs (x1-x2) > 30 || abs (y1-y2) > 30) {
172
		/* Line is long enough. Draw arrow */
173
		draw_arrow (gc, x1, y1, x2, y2);
174
	} else {
175
		/* short line. Omit arrow. */
176
		gdk_draw_line (pixmap, gc, x1, y1, x2, y2);
177
	}
178
}
179
180
/**
181
 * This function draws the current route on the screen.
182
 */
183
void
184
paint_route ()
185
{
186
	GSList *list;
187
	int pixel_x, pixel_y, x,y, last_x = 0, last_y = 0;
188
	float lat, lon;
189
	GdkColor color;
190
	GdkGC *gc;
191
	gboolean is_line = FALSE;
192
193
	/* Load icon if not already loaded: */
194
	if (!wp_icon) {
195
		wp_icon        = load_wp_icon ();
196
		wp_icon_width  = gdk_pixbuf_get_width (wp_icon);
197
		wp_icon_height = gdk_pixbuf_get_height (wp_icon);
198
	}
199
200
	/* Create GC for drawing the route line */
201
	gc = gdk_gc_new (pixmap);
202
	color.green = 0;
203
	color.blue = 0;
204
	color.red = 50000;
205
	gdk_gc_set_rgb_fg_color (gc, &color);
206
	gdk_gc_set_line_attributes (gc, 5, GDK_LINE_SOLID,
207
	                            GDK_CAP_ROUND, GDK_JOIN_ROUND);
208
209
	/* [1] paint line first */
210
	for (list = route; list != NULL; list = list->next)
211
	{
212
		waypoint_t *tp = list->data;
213
214
		lat = tp->lat;
215
		lon = tp->lon;
216
217
		pixel_x = lon2pixel (global_zoom, lon);
218
		pixel_y = lat2pixel (global_zoom, lat);
219
220
		x = pixel_x - global_x;
221
		y = pixel_y - global_y;
222
223
		if (is_line)
224
		{
225
			draw_line_of_route (gc, last_x, last_y, x, y);
226
			gtk_widget_queue_draw_area (map_drawable,
227
			                            x-4, y-4, 8, 8);
228
		}
229
230
		last_x = x;
231
		last_y = y;
232
233
		is_line = TRUE;
234
	}
235
236
	/* [2] paint flags after line */
237
	for (list = route; list != NULL; list = list->next)
238
	{
239
		waypoint_t *tp = list->data;
240
241
		lat = tp->lat;
242
		lon = tp->lon;
243
244
		pixel_x = lon2pixel (global_zoom, lon);
245
		pixel_y = lat2pixel (global_zoom, lat);
246
247
		x = pixel_x - global_x;
248
		y = pixel_y - global_y;
249
250
		if (!wp_icon) {
251
			gdk_draw_arc (pixmap,
252
			              gc,
253
			              TRUE,
254
			              x-4, y-4,
255
			              8,8,
256
			              0,23040);
257
		} else {
258
			gdk_draw_pixbuf (pixmap,
259
			                 NULL,
260
			                 wp_icon,
261
			                 0,0,
262
			                 x,y-wp_icon_height,
263
			                 wp_icon_width,wp_icon_height,
264
			                 GDK_RGB_DITHER_NONE, 0, 0);
265
266
			gtk_widget_queue_draw_area (map_drawable,
267
			                            x, y-wp_icon_height,
268
			                            wp_icon_width,
269
			                            wp_icon_height);
270
		}
271
	}
272
}
273
305 by Paul Wise
Remove trailing whitespace
274
/**
208.1.4 by Dr. Tilmann Bubeck
Add route-planning functionality
275
 * Find the bounding box of the given GSList containin waypoints.
276
 */
277
bbox_t
278
get_way_bbox (GSList *ways)
279
{
280
	GSList *list;
281
	bbox_t bbox;
282
	double lat, lon;
283
284
	bbox.lat1 =  -90;
285
	bbox.lon1 =  180;
286
	bbox.lat2 =   90;
287
	bbox.lon2 = -180;
288
289
	for (list = ways; list != NULL; list = list->next)
290
	{
291
		waypoint_t *tp = list->data;
292
293
		lat = tp->lat;
294
		lon = tp->lon;
295
		bbox.lat1 = (lat > bbox.lat1) ? lat : bbox.lat1;
296
		bbox.lat2 = (lat < bbox.lat2) ? lat : bbox.lat2;
297
		bbox.lon1 = (lon < bbox.lon1) ? lon : bbox.lon1;
298
		bbox.lon2 = (lon > bbox.lon2) ? lon : bbox.lon2;
299
	}
300
301
	return bbox;
302
}
208.1.5 by Dr. Tilmann Bubeck
Add choose_load_file() and choose_save_file() utility functions
303
208.1.6 by Dr. Tilmann Bubeck
Support saving and loading of planned routes in GPX 1.1 files.
304
/**
305
 * Write a single route point to the XML file in GPX format.
306
 */
307
static
308
void
309
write_rtept (xmlTextWriterPtr writer, waypoint_t *wp, int no)
310
{
311
	xmlTextWriterStartElement (writer, BAD_CAST "rtept");
312
	xmlTextWriterWriteFormatAttribute (writer, BAD_CAST "lat",
313
	                                   "%f", rad2deg (wp->lat));
314
	xmlTextWriterWriteFormatAttribute (writer, BAD_CAST "lon",
315
	                                   "%f", rad2deg (wp->lon));
316
	xmlTextWriterEndElement (writer); /* </rtept> */
317
}
318
319
/**
320
 * Save the route in GPX format to the given URI.
321
 */
322
void
305 by Paul Wise
Remove trailing whitespace
323
save_route_as_gpx (const char *uri)
208.1.6 by Dr. Tilmann Bubeck
Support saving and loading of planned routes in GPX 1.1 files.
324
{
325
	int rc;
326
	xmlTextWriterPtr writer;
327
	int no;
328
	GSList *list;
329
	char now_as_string[200];
330
	struct tm now_as_tm;
331
	time_t now;
332
333
	if (uri == NULL) return;
334
335
	bbox_t bbox = get_way_bbox (route);
336
337
	/*
338
	 * this initialize the library and check potential ABI mismatches
339
	 * between the version it was compiled for and the actual shared
340
	 * library used.
341
	 */
342
	LIBXML_TEST_VERSION
343
344
	/* Create a new XmlWriter for uri, with no compression. */
345
	writer = xmlNewTextWriterFilename (uri, 0);
346
	if (writer == NULL) {
347
		printf ("testXmlwriterFilename: Error creating the xml writer\n");
348
		return;
349
	}
350
351
	/* We would like to indent the elements for better readability. */
352
	xmlTextWriterSetIndent (writer, 1);
353
354
	/* Start the document with the xml default for the version,
355
	 * encoding and the default for the standalone
356
	 * declaration. */
357
	rc = xmlTextWriterStartDocument (writer, "1.0", GPX_ENCODING, "no");
358
	if (rc < 0) {
359
		printf ("testXmlwriterFilename: Error at xmlTextWriterStartDocument\n");
360
		return;
361
	}
362
363
	xmlTextWriterStartElement (writer, BAD_CAST "gpx");
364
	xmlTextWriterWriteAttribute (writer, BAD_CAST "version",
365
	                                     BAD_CAST "1.1");
366
	xmlTextWriterWriteAttribute (writer, BAD_CAST "creator",
367
	                                     BAD_CAST PACKAGE_STRING);
368
	xmlTextWriterWriteAttribute (writer, BAD_CAST "xmlns:xsi",
369
	                                     BAD_CAST "http://www.w3.org/2001/XMLSchema-instance");
370
	xmlTextWriterWriteAttribute (writer, BAD_CAST "xmlns:topografix",
371
	                                     BAD_CAST "http://www.topografix.com/GPX/Private/TopoGrafix/0/1");
372
	xmlTextWriterWriteAttribute (writer, BAD_CAST "xmlns",
373
	                                     BAD_CAST "http://www.topografix.com/GPX/1/1");
374
	xmlTextWriterWriteAttribute (writer, BAD_CAST "xsi:schemaLocation",
375
	                                     BAD_CAST "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd");
376
377
	xmlTextWriterStartElement (writer, BAD_CAST "metadata");
378
379
	/* save current time into GPX file: */
380
	time (&now);
263 by Paul Wise
Use gmtime and the UTC timezone for times in GPX files.
381
	gmtime_r (&now, &now_as_tm);
382
	strftime (now_as_string, sizeof (now_as_string), "%Y-%m-%dT%H:%M:%SZ",
208.1.6 by Dr. Tilmann Bubeck
Support saving and loading of planned routes in GPX 1.1 files.
383
	          &now_as_tm);
384
	xmlTextWriterWriteElement (writer, BAD_CAST "time",
385
	                                   BAD_CAST now_as_string);
386
387
	xmlTextWriterStartElement (writer, BAD_CAST "bounds");
388
	xmlTextWriterWriteFormatAttribute (writer, BAD_CAST "minlat",
389
	                                   "%f", rad2deg (bbox.lat2));
390
	xmlTextWriterWriteFormatAttribute (writer, BAD_CAST "minlon",
391
	                                   "%f", rad2deg (bbox.lon1));
392
	xmlTextWriterWriteFormatAttribute (writer, BAD_CAST "maxlat",
393
	                                   "%f", rad2deg (bbox.lat1));
394
	xmlTextWriterWriteFormatAttribute (writer, BAD_CAST "maxlon",
395
	                                   "%f", rad2deg (bbox.lon2));
396
	xmlTextWriterEndElement (writer); /* </bounds> */
397
398
	xmlTextWriterEndElement (writer); /* </metadata> */
399
400
	xmlTextWriterStartElement (writer, BAD_CAST "rte");
401
402
	for (list = route, no = 1; list != NULL; list = list->next, no++)
403
	{
404
		waypoint_t *wp = list->data;
405
		write_rtept (writer, wp, no);
406
	}
407
408
	xmlTextWriterEndElement (writer); /* </rte> */
409
	xmlTextWriterEndElement (writer); /* </gpx> */
410
411
	xmlTextWriterEndDocument (writer);
412
413
	xmlFreeTextWriter (writer);
414
}
415
416
/**
208.1.7 by Dr. Tilmann Bubeck
Support saving planned routes as TomTom ITN files.
417
 * Save a route to a TomTom ITN file format.
418
 */
419
void
420
save_route_as_tomtom_itn (const char *uri)
421
{
422
	FILE *tomtom;
423
	int no;
424
	GSList *list;
425
426
	if (uri == NULL) return;
427
428
	tomtom = fopen (uri, "w");
429
	if (tomtom == NULL) {
430
		perror ("opening file to save tomtom.");
431
	} else {
432
		for (list = route, no = 1;
433
		     list != NULL;
434
		     list = list->next, no++)
435
		{
436
			waypoint_t *wp = list->data;
305 by Paul Wise
Remove trailing whitespace
437
			fprintf (tomtom, "%d|%d|WP%d|0\n",
208.1.7 by Dr. Tilmann Bubeck
Support saving planned routes as TomTom ITN files.
438
			         (int) (rad2deg (wp->lon) * 100000),
439
			         (int) (rad2deg (wp->lat) * 100000),
440
			         no);
441
		}
442
443
		fclose (tomtom);
444
	}
445
}
446
447
/**
208.1.6 by Dr. Tilmann Bubeck
Support saving and loading of planned routes in GPX 1.1 files.
448
 * Take all routepoints from a DOM tree containing GPX nodes.
449
 */
450
static
451
GSList *
452
parse_gpx_routepoints (xmlNode *node)
453
{
454
	xmlNode *cur_node = NULL;
455
	GSList *list = NULL;
456
457
	for (cur_node = node; cur_node; cur_node = cur_node->next)
458
	{
305 by Paul Wise
Remove trailing whitespace
459
		if (cur_node->type == XML_ELEMENT_NODE)
208.1.6 by Dr. Tilmann Bubeck
Support saving and loading of planned routes in GPX 1.1 files.
460
		{
461
			if (xmlStrEqual (cur_node->name, BAD_CAST "rtept"))
462
			{
463
				double lat, lon;
464
				waypoint_t *tp = g_new0 (waypoint_t, 1);
465
466
				lat = atof ((char *) xmlGetProp (cur_node,
467
				                                 BAD_CAST "lat"));
468
				lon = atof ((char *) xmlGetProp (cur_node,
469
				                                 BAD_CAST "lon"));
470
471
				tp->lat = deg2rad (lat);
472
				tp->lon = deg2rad (lon);
473
474
				list = g_slist_append (list, tp);
475
			}
476
		}
477
		list = g_slist_concat (list,
478
		                       parse_gpx_routepoints (cur_node->children));
479
	}
480
481
	return list;
482
}
483
484
/**
485
 * Load a route from a given GPX file.
486
 */
487
GSList *
305 by Paul Wise
Remove trailing whitespace
488
load_route_as_gpx (const char *file)
208.1.6 by Dr. Tilmann Bubeck
Support saving and loading of planned routes in GPX 1.1 files.
489
{
490
  	GSList *list = NULL;
491
	xmlDoc *doc = NULL;
492
	xmlNode *root_element = NULL;
493
494
	LIBXML_TEST_VERSION
495
496
	doc = xmlReadFile (file, NULL, 0);
497
	if (doc == NULL)
498
	{
499
		printf ("error: could not parse file %s\n", file);
500
	}
501
502
	root_element = xmlDocGetRootElement (doc);
503
	list = parse_gpx_routepoints (root_element);
504
	xmlFreeDoc (doc);
505
	xmlCleanupParser ();
506
507
	return list;
508
}
509
510
/**
511
 * Load a route from a file with a supported file format.
512
 */
513
void
514
load_route (const char *filename)
515
{
516
	bbox_t bbox;
517
518
	if (filename == NULL) return;
519
520
	route = load_route_as_gpx (filename);
521
	if (route != NULL)
522
	{
523
		bbox = get_way_bbox (route);
524
		show_bbox (bbox);
525
	}
526
}
527
208.1.5 by Dr. Tilmann Bubeck
Add choose_load_file() and choose_save_file() utility functions
528
529
char *
530
choose_save_file (char *currentName)
531
{
532
	char *filename = NULL;
533
	GtkWidget *dialog;
534
	dialog = gtk_file_chooser_dialog_new ("Save File",
535
	                                      NULL,
536
	                                      GTK_FILE_CHOOSER_ACTION_SAVE,
537
	                                      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
538
	                                      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
539
	                                      NULL);
540
	gtk_file_chooser_set_do_overwrite_confirmation
541
		(GTK_FILE_CHOOSER (dialog), TRUE);
542
	gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog),
543
	                                   currentName);
544
545
	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
546
	{
547
		filename = gtk_file_chooser_get_filename
548
			(GTK_FILE_CHOOSER (dialog));
549
	}
550
551
	gtk_widget_destroy (dialog);
552
553
	return filename;
554
}
555
556
char *
557
choose_load_file ()
558
{
559
	char *filename = NULL;
560
	GtkWidget *dialog;
561
	dialog = gtk_file_chooser_dialog_new ("Open File", NULL,
562
	                                      GTK_FILE_CHOOSER_ACTION_OPEN,
563
	                                      GTK_STOCK_CANCEL,
564
	                                      GTK_RESPONSE_CANCEL,
565
	                                      GTK_STOCK_OPEN,
566
	                                      GTK_RESPONSE_ACCEPT,
567
	                                      NULL);
568
569
	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
570
	{
571
		filename = gtk_file_chooser_get_filename
572
			(GTK_FILE_CHOOSER (dialog));
573
	}
574
	gtk_widget_destroy (dialog);
575
576
	return filename;
577
}