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(&centisecs, 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