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