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