1 /*
2 Access Magellan Mapsend files.
3
4 Copyright (C) 2002-2006 Robert Lipe, robertlipe@usa.net
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
19 */
20
21 #include <stdio.h>
22 #include <string.h>
23
24 #include "defs.h"
25 #include "mapsend.h"
26 #include "magellan.h"
27
28 static gbfile* mapsend_file_in;
29 static gbfile* mapsend_file_out;
30 static short_handle mkshort_handle;
31 static short_handle wpt_handle;
32
33 static int route_wp_count;
34 static int mapsend_infile_version;
35 static int trk_version = 30;
36
37 #define MYNAME "mapsend"
38
39 static char* mapsend_opt_trkver = NULL;
40 #define MAPSEND_TRKVER_MIN 3
41 #define MAPSEND_TRKVER_MAX 4
42
43 static
44 arglist_t mapsend_args[] = {
45 {
46 "trkver", &mapsend_opt_trkver,
47 "MapSend version TRK file to generate (3,4)",
48 "4", ARGTYPE_INT, "3", "4"
49 },
50 ARG_TERMINATOR
51 };
52
53 static void
mapsend_init_opts(const char isReading)54 mapsend_init_opts(const char isReading) /* 1=read, 2=write */
55 {
56 int opt_trkver;
57
58 /* read & write options here */
59
60 if (isReading) {
61 /* reading-only options here */
62 } else {
63 /* writing-only options here */
64
65 // TRK MapSend version
66 opt_trkver = atoi(mapsend_opt_trkver);
67 if ((opt_trkver < MAPSEND_TRKVER_MIN) || (opt_trkver > MAPSEND_TRKVER_MAX)) {
68 fatal(MYNAME ": Unsupported MapSend TRK version \"%s\"!\n", mapsend_opt_trkver);
69 }
70 trk_version = opt_trkver * 10;
71 }
72 }
73
74 static void
mapsend_rd_init(const char * fname)75 mapsend_rd_init(const char* fname)
76 {
77 mapsend_init_opts(1);
78 mapsend_file_in = gbfopen_le(fname, "rb", MYNAME);
79 }
80
81 static void
mapsend_rd_deinit(void)82 mapsend_rd_deinit(void)
83 {
84 gbfclose(mapsend_file_in);
85 }
86
87 static void
mapsend_wr_init(const char * fname)88 mapsend_wr_init(const char* fname)
89 {
90 mapsend_init_opts(0);
91 mapsend_file_out = gbfopen(fname, "wb", MYNAME);
92 mkshort_handle = mkshort_new_handle();
93
94 wpt_handle = mkshort_new_handle();
95 setshort_whitespace_ok(wpt_handle, 1);
96 setshort_length(wpt_handle, 8);
97
98 route_wp_count = 0;
99 }
100
101 static void
mapsend_wr_deinit(void)102 mapsend_wr_deinit(void)
103 {
104 gbfclose(mapsend_file_out);
105 mkshort_del_handle(&mkshort_handle);
106 mkshort_del_handle(&wpt_handle);
107 }
108
109 static void
mapsend_wpt_read(void)110 mapsend_wpt_read(void)
111 {
112 char tbuf[256];
113 int wpt_count, rte_count, rte_num;
114 int wpt_number;
115 char wpt_icon;
116 char wpt_status;
117 waypoint* wpt_tmp;
118 route_head* rte_head;
119
120 wpt_count = gbfgetint32(mapsend_file_in);
121
122 while (wpt_count--) {
123 wpt_tmp = waypt_new();
124
125 wpt_tmp->shortname = gbfgetpstr(mapsend_file_in);
126 wpt_tmp->description = gbfgetpstr(mapsend_file_in);
127
128 wpt_number = gbfgetint32(mapsend_file_in);
129 wpt_icon = gbfgetc(mapsend_file_in);
130 wpt_status = gbfgetc(mapsend_file_in);
131
132 wpt_tmp->altitude = gbfgetdbl(mapsend_file_in);
133 wpt_tmp->longitude = gbfgetdbl(mapsend_file_in);
134 wpt_tmp->latitude = -gbfgetdbl(mapsend_file_in);
135
136 if (wpt_icon < 26) {
137 sprintf(tbuf, "%c", wpt_icon + 'a');
138 } else {
139 sprintf(tbuf, "a%c", wpt_icon - 26 + 'a');
140 }
141 wpt_tmp->icon_descr = mag_find_descr_from_token(tbuf);
142
143 waypt_add(wpt_tmp);
144 }
145
146 /* now read the routes... */
147 rte_count = gbfgetint32(mapsend_file_in);
148
149 while (rte_count--) {
150 rte_head = route_head_alloc();
151 route_add_head(rte_head);
152
153 /* route name */
154 rte_head->rte_name = gbfgetpstr(mapsend_file_in);
155
156 /* route # */
157 rte_num = gbfgetint32(mapsend_file_in);
158 rte_head->rte_num = rte_num;
159
160 /* points this route */
161 wpt_count = gbfgetint32(mapsend_file_in);
162
163 while (wpt_count--) {
164 wpt_tmp = waypt_new();
165
166 /* waypoint name */
167 wpt_tmp->shortname = gbfgetpstr(mapsend_file_in);
168
169 /* waypoint # */
170 wpt_number = gbfgetint32(mapsend_file_in);
171 wpt_tmp->longitude = gbfgetdbl(mapsend_file_in);
172 wpt_tmp->latitude = -gbfgetdbl(mapsend_file_in);
173
174 gbfread(&wpt_icon, 1, sizeof(wpt_icon), mapsend_file_in);
175
176 if (wpt_icon < 26) {
177 sprintf(tbuf, "%c", wpt_icon + 'a');
178 } else {
179 sprintf(tbuf, "a%c", wpt_icon - 26 + 'a');
180 }
181 wpt_tmp->icon_descr = mag_find_descr_from_token(tbuf);
182
183 route_add_wpt(rte_head, wpt_tmp);
184 }
185 }
186 }
187
188 static void
mapsend_track_read(void)189 mapsend_track_read(void)
190 {
191 unsigned int trk_count;
192 int valid;
193 unsigned char centisecs;
194 route_head* track_head;
195 waypoint* wpt_tmp;
196
197 track_head = route_head_alloc();
198 track_head->rte_name = gbfgetpstr(mapsend_file_in);
199 track_add_head(track_head);
200
201 trk_count = gbfgetuint32(mapsend_file_in);
202
203 while (trk_count--) {
204 wpt_tmp = waypt_new();
205
206 wpt_tmp->longitude = gbfgetdbl(mapsend_file_in);
207 wpt_tmp->latitude = -gbfgetdbl(mapsend_file_in);
208
209 if (mapsend_infile_version < 36) { /* < version 4.0 */
210 wpt_tmp->altitude = gbfgetint32(mapsend_file_in);
211 } else {
212 wpt_tmp->altitude = gbfgetflt(mapsend_file_in);
213 }
214 if (wpt_tmp->altitude < unknown_alt + 1) {
215 wpt_tmp->altitude = unknown_alt;
216 }
217 wpt_tmp->creation_time = gbfgetint32(mapsend_file_in);
218 valid = gbfgetint32(mapsend_file_in);
219
220 /* centiseconds only in >= version 3.0 */
221 if (mapsend_infile_version >= 34) {
222 gbfread(¢isecs, 1, 1, mapsend_file_in);
223 } else {
224 centisecs = 0;
225 }
226 wpt_tmp->microseconds = CENTI_TO_MICRO(centisecs);
227
228 track_add_wpt(track_head, wpt_tmp);
229 }
230 }
231
232 static void
mapsend_read(void)233 mapsend_read(void)
234 {
235 mapsend_hdr hdr;
236 int type, len;
237 char buf[3];
238
239 /*
240 * Because of the silly struct packing and the goofy variable-length
241 * strings, each member has to be read in one at a time. Grrr.
242 */
243
244 len = gbfread(&hdr, 1, sizeof(hdr), mapsend_file_in);
245 is_fatal(len < sizeof(hdr), MYNAME ": No mapsend or empty file!");
246
247 type = le_read16(&hdr.ms_type);
248 strncpy(buf, hdr.ms_version, 2);
249 buf[2] = '\0';
250
251 mapsend_infile_version = atoi(buf);
252
253 switch (type) {
254 case ms_type_wpt:
255 mapsend_wpt_read();
256 break;
257 case ms_type_track:
258 mapsend_track_read();
259 break;
260 case ms_type_log:
261 fatal(MYNAME ", GPS logs not supported.\n");
262 case ms_type_rgn:
263 fatal(MYNAME ", GPS regions not supported.\n");
264 default:
265 fatal(MYNAME ", unknown file type %d\n", type);
266 }
267 }
268
269
270 static void
mapsend_waypt_pr(const waypoint * waypointp)271 mapsend_waypt_pr(const waypoint* waypointp)
272 {
273 unsigned char c;
274 double falt;
275 static int cnt = 0;
276 const char* iconp = NULL;
277 const char* sn = global_opts.synthesize_shortnames ?
278 mkshort_from_wpt(mkshort_handle, waypointp) :
279 waypointp->shortname;
280 char* tmp;
281
282 /*
283 * The format spec doesn't call out the character set of waypoint
284 * name and description. Empirically, we can see that it's 8859-1,
285 * but if we create mapsend files containing those, Mapsend becomes
286 * grumpy uploading the resulting waypoints and being unable to deal
287 * with the resulting comm errors.
288 *
289 * Ironically, our own Magellan serial module strips the "naughty"
290 * characters, keeping it more in definition with their own serial
291 * spec. :-)
292 *
293 * So we just decompose the utf8 strings to ascii before stuffing
294 * them into the Mapsend file.
295 */
296
297
298 tmp = mkshort(wpt_handle, sn);
299 gbfputpstr(tmp, mapsend_file_out);
300 if (tmp) {
301 xfree(tmp);
302 }
303
304 tmp = waypointp->description;
305 if (tmp) {
306 c = strlen(tmp);
307 } else {
308 c = 0;
309 }
310
311 if (c > 30) {
312 c = 30;
313 }
314 gbfputc(c, mapsend_file_out);
315 gbfwrite(tmp, 1, c, mapsend_file_out);
316
317 /* #, icon, status */
318 gbfputint32(++cnt, mapsend_file_out);
319
320 if (waypointp->icon_descr) {
321 iconp = mag_find_token_from_descr(waypointp->icon_descr);
322 if (1 == strlen(iconp)) {
323 c = iconp[0] - 'a';
324 } else {
325 c = iconp[1] - 'a' + 26;
326 }
327 } else {
328 c = 0;
329 }
330 if (get_cache_icon(waypointp)) {
331 iconp = mag_find_token_from_descr(get_cache_icon(waypointp));
332 if (1 == strlen(iconp)) {
333 c = iconp[0] - 'a';
334 } else {
335 c = iconp[1] - 'a' + 26;
336 }
337 }
338
339 gbfwrite(&c, 1, 1, mapsend_file_out);
340 gbfputc(1, mapsend_file_out);
341
342 falt = waypointp->altitude;
343 if (falt == unknown_alt) {
344 falt = 0;
345 }
346 gbfputdbl(falt, mapsend_file_out);
347
348 gbfputdbl(waypointp->longitude, mapsend_file_out);
349 gbfputdbl(-waypointp->latitude, mapsend_file_out);
350 }
351
352 static void
mapsend_route_hdr(const route_head * rte)353 mapsend_route_hdr(const route_head* rte)
354 {
355 char* rname;
356
357 /* route name -- mapsend really seems to want something here.. */
358 if (!rte->rte_name) {
359 rname = xstrdup("Route");
360 } else {
361 rname = xstrdup(rte->rte_name);
362 }
363 gbfputpstr(rname, mapsend_file_out);
364
365 xfree(rname);
366
367 /* route # */
368 gbfputint32(rte->rte_num, mapsend_file_out);
369
370 /* # of waypoints to follow... */
371 gbfputint32(rte->rte_waypt_ct, mapsend_file_out);
372 }
373
374 static void
mapsend_noop(const route_head * wp)375 mapsend_noop(const route_head* wp)
376 {
377 /* no-op */
378 }
379
380 static void
mapsend_route_disp(const waypoint * waypointp)381 mapsend_route_disp(const waypoint* waypointp)
382 {
383 unsigned char c;
384 const char* iconp;
385
386 route_wp_count++;
387
388 /* waypoint name */
389 c = waypointp->shortname ? strlen(waypointp->shortname) : 0;
390 gbfwrite(&c, 1, 1, mapsend_file_out);
391 gbfwrite(waypointp->shortname, 1, c, mapsend_file_out);
392
393 /* waypoint number */
394 gbfputint32(route_wp_count, mapsend_file_out);
395
396 gbfputdbl(waypointp->longitude, mapsend_file_out);
397 gbfputdbl(-waypointp->latitude, mapsend_file_out);
398
399 if (waypointp->icon_descr) {
400 iconp = mag_find_token_from_descr(waypointp->icon_descr);
401 if (1 == strlen(iconp)) {
402 c = iconp[0] - 'a';
403 } else {
404 c = iconp[1] - 'a' + 26;
405 }
406 } else {
407 c = 0;
408 }
409 gbfwrite(&c, 1, 1, mapsend_file_out);
410 }
411
mapsend_track_hdr(const route_head * trk)412 void mapsend_track_hdr(const route_head* trk)
413 {
414 /*
415 * we write mapsend v3.0 tracks as mapsend v2.0 tracks get
416 * tremendously out of whack time/date wise.
417 */
418 char* verstring = "30";
419 queue* elem, *tmp;
420 char* tname;
421 int i;
422 mapsend_hdr hdr = {13, {'4','D','5','3','3','3','3','4',' ','M','S'},
423 {'3','0'}, ms_type_track, {0, 0, 0}
424 };
425
426 switch (trk_version) {
427 case 20:
428 verstring = "30";
429 break;
430 case 30:
431 verstring = "34";
432 break;
433 case 40:
434 /* the signature seems to change with the versions, even though it
435 * shouldn't have according to the document. MapSend V4 doesn't
436 * like the old version.
437 */
438 hdr.ms_signature[7] = '6';
439 verstring = "36";
440 break;
441 default:
442 fatal("Unknown track version.\n");
443 break;
444 }
445
446 hdr.ms_version[0] = verstring[0];
447 hdr.ms_version[1] = verstring[1];
448
449 gbfwrite(&hdr, sizeof(hdr), 1, mapsend_file_out);
450
451 /* track name */
452 if (!trk->rte_name) {
453 tname = xstrdup("Track");
454 } else {
455 tname = xstrdup(trk->rte_name);
456 }
457 gbfputpstr(tname, mapsend_file_out);
458
459 xfree(tname);
460
461 /* total nodes (waypoints) this track */
462 i = 0;
463 QUEUE_FOR_EACH(&trk->waypoint_list, elem, tmp) {
464 i++;
465 }
466
467 gbfputint32(i, mapsend_file_out);
468
469 }
470
mapsend_track_disp(const waypoint * wpt)471 void mapsend_track_disp(const waypoint* wpt)
472 {
473 unsigned char c;
474 int t;
475 static int last_time;
476
477 /*
478 * Firmware Ver 4.06 (at least) has a defect when it's set for .01km
479 * tracking that will sometimes result in timestamps in the track
480 * going BACKWARDS. When mapsend sees this, it (stupidly) advances
481 * the date by one, ignoring the date on the TRK lines. This looks
482 * for time travel and just uses the previous time - it's better to
483 * be thought to be standing still than to be time-travelling!
484 *
485 * This is rumoured (but yet unconfirmed) to be fixed in f/w 5.12.
486 */
487 t = wpt->creation_time;
488 if (t < last_time) {
489 t = last_time;
490 }
491
492 /* x = longitude */
493 gbfputdbl(wpt->longitude, mapsend_file_out);
494
495 /* x = latitude */
496 gbfputdbl(-wpt->latitude, mapsend_file_out);
497
498 /* altitude
499 * in V4.0+ this field is a float, it was previously an int
500 */
501 if (trk_version < 40) {
502 gbfputint32((int) wpt->altitude, mapsend_file_out);
503 } else {
504 gbfputflt((float) wpt->altitude, mapsend_file_out);
505 }
506
507 /* time */
508 gbfputint32(t, mapsend_file_out);
509 last_time = t;
510
511 /* validity */
512 gbfputint32(1, mapsend_file_out);
513
514 /* 0 centiseconds */
515 if (trk_version >= 30) {
516 c = MICRO_TO_CENTI(wpt->microseconds);
517 gbfwrite(&c, 1, 1, mapsend_file_out);
518 }
519 }
520
521 void
mapsend_track_write(void)522 mapsend_track_write(void)
523 {
524 track_disp_all(mapsend_track_hdr, mapsend_noop, mapsend_track_disp);
525 }
526
527 static void
mapsend_wpt_write(void)528 mapsend_wpt_write(void)
529 {
530 mapsend_hdr hdr = {13, {'4','D','5','3','3','3','3','0',' ','M','S'},
531 {'3', '0'}, ms_type_wpt, {0, 0, 0}
532 };
533 int n = 0;
534 int wpt_count = waypt_count();
535
536 if (global_opts.objective == trkdata) {
537 mapsend_track_write();
538 } else {
539 gbfwrite(&hdr, sizeof(hdr), 1, mapsend_file_out);
540
541 if (global_opts.objective == wptdata) {
542 gbfputint32(wpt_count, mapsend_file_out);
543 waypt_disp_all(mapsend_waypt_pr);
544 } else if (global_opts.objective == rtedata) {
545
546 /* # of points - all routes */
547 gbfputint32(route_waypt_count(), mapsend_file_out);
548
549 /* write points - all routes */
550 route_disp_all(mapsend_noop, mapsend_noop, mapsend_waypt_pr);
551 }
552
553 n = route_count();
554
555 gbfputint32(n, mapsend_file_out);
556
557 if (n) {
558 route_disp_all(mapsend_route_hdr, mapsend_noop, mapsend_route_disp);
559 }
560 }
561 }
562
563
564
565 ff_vecs_t mapsend_vecs = {
566 ff_type_file,
567 FF_CAP_RW_ALL,
568 mapsend_rd_init,
569 mapsend_wr_init,
570 mapsend_rd_deinit,
571 mapsend_wr_deinit,
572 mapsend_read,
573 mapsend_wpt_write,
574 NULL,
575 mapsend_args,
576 CET_CHARSET_ASCII, 0 /* CET-REVIEW */
577 };
578