1 /*
2     Read and write cotoGPS files.
3 
4     Copyright (C) 2005 Tobias Minich,
5 
6     Based on the Cetus I/O Filter,
7     Copyright (C) 2002 Robert Lipe, robertlipe@usa.net
8 
9     This program is free software; you can redistribute it and/or modify
10     it under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 2 of the License, or
12     (at your option) any later version.
13 
14     This program is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License
20     along with this program; if not, write to the Free Software
21     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
22 
23  */
24 
25 
26 #include "defs.h"
27 #if PDBFMTS_ENABLED
28 #include "csv_util.h"
29 #include "pdbfile.h"
30 #include "grtcirc.h"
31 
32 #define MYNAME "cotoGPS"
33 
34 #define MYTYPETRACK	0x5452434b  	/* TRCK */
35 #define MYTYPEWPT	0x44415441  	/* DATA */
36 #define MYCREATOR	0x636f4750	/* coGP */
37 
38 #define NOTESZ 4096
39 #define DESCSZ 4096
40 
41 #define MAX_MARKER_NAME_LENGTH 20
42 #define CATEGORY_NAME_LENGTH 16
43 
44 typedef enum {
45   cotofixNone = 0,	/* No Fix or Warning */
46   cotofixReserved = 1,	/* Shouldn't occur*/
47   cotofix2D = 2,		/* retrieved from a GPS with a 2D fix */
48   cotofix3D = 3,		/* retrieved from a GPS with a 3D fix  */
49   cotofixDGPS = 4,	/* retrieved from a GPS with a DGPS signal */
50 } fix_quality;
51 
52 struct record_track {
53 
54   pdb_double latitude;	/* radians, s=negative */
55   pdb_double longitude;	/* same as lat; e=negative */
56   pdb_double distance;	/* Distance to thel last point; discarded since it's calculated by gpsbabel on write */
57   pdb_double arc;		/* Course, unknown dimension */
58   pdb_double x,y;		/* Internal virtual coordinates used for drawing the track on the Palm */
59 
60   uint16_t alt;		/* Altitude */
61 
62   /* accuracy and precision information for use where applicable */
63   uint16_t hdop; /* _dop * 10 */
64   uint16_t vdop;
65   uint16_t pdop;
66   uint8_t sat_tracked;
67   uint8_t fix_quality;
68 
69   uint16_t speed; /* *10 */
70   uint32_t time; /* Palm Time */
71 };
72 
73 struct record_wpt {
74   char lon[8];
75   char lat[8];
76   char name[MAX_MARKER_NAME_LENGTH];
77   char notes[1];
78 };
79 
80 
81 // We need the pdb AppInfo for waypoint categories
82 
83 typedef char appinfo_category[16];
84 
85 typedef struct appinfo {
86   uint8_t U0;
87   uint8_t renamedCategories;
88   appinfo_category categories[CATEGORY_NAME_LENGTH];
89   uint8_t ids[16];
90   uint8_t maxid;
91 } appinfo_t;
92 
93 #define APPINFO_SIZE sizeof(appinfo_t)
94 
95 static pdbfile* file_in, *file_out;
96 static const char* out_fname;
97 static const char* in_fname; /* We might need that for naming tracks */
98 static short_handle  mkshort_wr_handle;
99 static int ct;
100 
101 static char* zerocat = NULL;
102 static char* internals = NULL;
103 
104 static
105 arglist_t coto_args[] = {
106   {
107     "zerocat", &zerocat, "Name of the 'unassigned' category", NULL,
108     ARGTYPE_STRING, ARG_NOMINMAX
109   },
110   {
111     "internals", &internals, "Export some internal stuff to notes", NULL,
112     ARGTYPE_STRING | ARGTYPE_HIDDEN, ARG_NOMINMAX
113   },
114   ARG_TERMINATOR
115 };
116 
117 static void
rd_init(const char * fname)118 rd_init(const char* fname)
119 {
120   file_in = pdb_open(fname, MYNAME);
121   in_fname = fname;
122 }
123 
124 static void
rd_deinit(void)125 rd_deinit(void)
126 {
127   pdb_close(file_in);
128 }
129 
130 static void
wr_init(const char * fname)131 wr_init(const char* fname)
132 {
133   file_out = pdb_create(fname, MYNAME);
134   out_fname = fname;
135   ct = 0;
136 }
137 
138 static void
wr_deinit(void)139 wr_deinit(void)
140 {
141   // pdb_close() will only free appinfo in read mode, and pdb_create() sets the mode to write.
142   struct appinfo* ai = (struct appinfo*) file_out->appinfo;
143   pdb_close(file_out);
144   if (ai) {
145     free(ai);
146   }
147 }
148 
149 /* helpers */
150 
151 static QString
coto_get_icon_descr(int category,const appinfo_t * app)152 coto_get_icon_descr(int category, const appinfo_t* app)
153 {
154   char buff[CATEGORY_NAME_LENGTH + 1] = "Not Assigned";
155   if ((category >= 0) && (category < 16)) {
156     if ((category > 0) && (app->categories[category][0] == '\0')) {
157       category = 0;
158     }
159 
160     strncpy(buff, app->categories[category], sizeof(buff) - 1);
161     if (buff[0] == '\0') {
162       return QString();
163     }
164   }
165   return QString(buff);
166 }
167 
168 static void
coto_track_read(void)169 coto_track_read(void)
170 {
171   struct record_track* rec;
172   pdbrec_t* pdb_rec;
173   route_head* trk_head;
174   char* track_name;
175 
176   if (strncmp(file_in->name, "cotoGPS TrackDB", PDB_DBNAMELEN) != 0)
177     // Use database name if not default
178   {
179     track_name = xstrndup(file_in->name, PDB_DBNAMELEN);
180   } else {
181     // Use filename for new track title
182     const char* fnametmp = strrchr(in_fname, '/');
183     if (fnametmp == NULL) {
184       fnametmp = strrchr(in_fname, '\\');
185     }
186     if (fnametmp) {
187       fnametmp++;
188     } else {
189       fnametmp = in_fname;
190     }
191     if (strrchr(fnametmp, '.') != NULL) {
192       track_name = xstrndup(fnametmp, strrchr(fnametmp,'.') - fnametmp);
193     } else {
194       track_name = xstrdup(fnametmp);
195     }
196   }
197 
198   trk_head = route_head_alloc();
199   track_add_head(trk_head);
200 
201   trk_head->rte_name = track_name;
202 
203   for (pdb_rec = file_in->rec_list; pdb_rec; pdb_rec = pdb_rec->next) {
204     waypoint* wpt_tmp;
205 
206     wpt_tmp = waypt_new();
207 
208     rec = (struct record_track*) pdb_rec->data;
209 
210     wpt_tmp->longitude = DEG(-pdb_read_double(&rec->longitude));
211     wpt_tmp->latitude = DEG(pdb_read_double(&rec->latitude));
212 
213     // It's not the course, so leave it out for now
214     // WAYPT_SET(wpt_tmp, course, pdb_read_double(&rec->arc));
215     wpt_tmp->altitude = be_read16(&rec->alt);
216 
217     if (internals) {
218       // Parse the option as xcsv delimiter
219       const char* inter = xcsv_get_char_from_constant_table(internals);
220       char temp[256];
221       snprintf(temp, sizeof(temp), "%.20f%s%.20f%s%.20f%s%.20f", pdb_read_double(&rec->distance), inter,
222                pdb_read_double(&rec->arc), inter, pdb_read_double(&rec->x), inter, pdb_read_double(&rec->y));
223       wpt_tmp->notes = xstrdup(temp);
224     }
225 
226     wpt_tmp->pdop = be_read16(&rec->pdop)/10.0;
227     wpt_tmp->hdop = be_read16(&rec->hdop)/10.0;
228     wpt_tmp->vdop = be_read16(&rec->vdop)/10.0;
229     wpt_tmp->sat = rec->sat_tracked;
230     switch (rec->fix_quality) {
231     case cotofixNone:
232       wpt_tmp->fix = fix_none;
233       break;
234     case cotofixReserved:
235       wpt_tmp->fix = fix_unknown;
236       break;
237     case cotofix2D:
238       wpt_tmp->fix = fix_2d;
239       break;
240     case cotofix3D:
241       wpt_tmp->fix = fix_3d;
242       break;
243     case cotofixDGPS:
244       wpt_tmp->fix = fix_dgps;
245       break;
246     }
247     WAYPT_SET(wpt_tmp, speed, be_read16(&rec->speed)/10.0);
248     rec->time = be_read32(&rec->time);
249     if (rec->time != 0) {
250       rec->time -= 2082844800U;
251       wpt_tmp->SetCreationTime(rec->time);
252     }
253     track_add_wpt(trk_head, wpt_tmp);
254   }
255 }
256 
257 static void
coto_wpt_read(void)258 coto_wpt_read(void)
259 {
260   struct record_wpt* rec;
261   pdbrec_t* pdb_rec;
262   appinfo_t* app;
263   app = (struct appinfo*) file_in->appinfo;
264 
265   for (pdb_rec = file_in->rec_list; pdb_rec; pdb_rec = pdb_rec->next) {
266     waypoint* wpt_tmp;
267     char* c;
268 
269     wpt_tmp = waypt_new();
270 
271     rec = (struct record_wpt*) pdb_rec->data;
272 
273     wpt_tmp->longitude = DEG(-pdb_read_double(&rec->lon));
274     wpt_tmp->latitude = DEG(pdb_read_double(&rec->lat));
275 
276     wpt_tmp->shortname = xstrndup(rec->name, sizeof(rec->name));
277 
278     wpt_tmp->icon_descr = coto_get_icon_descr(pdb_rec->category, app);
279 
280     if ((c = strstr(rec->notes, "\nNotes:\n"))) {	/* remove our contruct */
281       wpt_tmp->notes = xstrdup(c + 8);
282       if (c != rec->notes) {
283         wpt_tmp->description = xstrndup(rec->notes, c - rec->notes);
284       }
285     } else {
286       wpt_tmp->notes = xstrdup(rec->notes);
287     }
288 
289     waypt_add(wpt_tmp);
290   }
291 }
292 
293 static void
data_read(void)294 data_read(void)
295 {
296   if ((file_in->creator != MYCREATOR) || ((file_in->type != MYTYPETRACK) && (file_in->type != MYTYPEWPT))) {
297     warning("Creator %x Type %x Version %d\n", (int) file_in->creator, (int) file_in->type, (int) file_in->version);
298     fatal(MYNAME ": Not a cotoGPS file.\n");
299   }
300 
301   is_fatal((file_in->version > 0),
302            MYNAME ": This file is from an unsupported newer version of cotoGPS.  It may be supported in a newer version of GPSBabel.\n");
303 
304   switch (file_in->type) {
305   case MYTYPETRACK:
306     coto_track_read();
307     break;
308   case MYTYPEWPT:
309     coto_wpt_read();
310     break;
311   }
312 }
313 
314 static void
coto_prepare_wpt_write(void)315 coto_prepare_wpt_write(void)
316 {
317   struct appinfo* ai;
318 
319   file_out->name[PDB_DBNAMELEN-1] = 0;
320   file_out->attr = PDB_FLAG_BACKUP;
321   file_out->type = MYTYPEWPT;
322   file_out->creator = MYCREATOR;
323   file_out->version = 0;
324 
325   strncpy(file_out->name, "cotoGPS MarkerDB", PDB_DBNAMELEN);
326 
327   file_out->appinfo_len = APPINFO_SIZE;
328   file_out->appinfo = calloc(APPINFO_SIZE,1);
329 
330   ai = (struct appinfo*) file_out->appinfo;
331   be_write16(&ai->renamedCategories, 31); // Don't ask me why...
332   if (zerocat) {
333     strncpy(ai->categories[0], zerocat, 16);
334   } else {
335     strncpy(ai->categories[0], "Not Assigned", 16);  // FIXME: Replace by default English Palm 'Not Assigned' category
336   }
337 
338 }
339 
340 static void
coto_wpt_write(const waypoint * wpt)341 coto_wpt_write(const waypoint* wpt)
342 {
343   struct record_wpt* rec;
344   struct appinfo* ai = (struct appinfo*) file_out->appinfo;
345   char* notes = NULL;
346   char* shortname = NULL;
347   int size;
348   uint8_t cat = 0;
349   int i;
350 
351   mkshort_wr_handle = mkshort_new_handle();
352   setshort_length(mkshort_wr_handle, MAX_MARKER_NAME_LENGTH);
353   setshort_whitespace_ok(mkshort_wr_handle, 1);
354 
355   if ((global_opts.synthesize_shortnames && wpt->description) || (wpt->shortname == NULL)) {
356     shortname = mkshort_from_wpt(mkshort_wr_handle, wpt);
357   } else {
358     shortname = xstrdup(wpt->shortname);
359   }
360 
361   if ((wpt->description) && ((strlen(wpt->description) > MAX_MARKER_NAME_LENGTH) || (strcmp(wpt->description, wpt->shortname)))) {
362     if ((wpt->notes) && (strcmp(wpt->description, wpt->notes) != 0)) {
363       notes = (char*) xcalloc(strlen(wpt->description) + strlen(wpt->notes) + 9, 1);
364       sprintf(notes, "%s\nNotes:\n%s", wpt->description, wpt->notes);
365     } else {
366       notes = xstrdup(wpt->description);
367     }
368   } else if (wpt->notes != NULL) {
369     notes = xstrdup(wpt->notes);
370   }
371 
372   size = sizeof(*rec);
373   if (notes != NULL) {
374     size += strlen(notes);
375   }
376   rec = (struct record_wpt*) xcalloc(size, 1);
377 
378   pdb_write_double(&rec->lon, RAD(-wpt->longitude));
379   pdb_write_double(&rec->lat, RAD(wpt->latitude));
380   strncpy(rec->name, shortname, MAX_MARKER_NAME_LENGTH);
381 
382   if (notes) {
383     strcpy(rec->notes, notes);
384     xfree(notes);
385   }
386 
387   if (!wpt->icon_descr.isNull()) {
388     for (i = 1; i < 16; i++)
389       if (!strncmp(wpt->icon_descr.toUtf8().data(), ai->categories[i], 16)) {
390         cat=i;
391         break;
392       }
393     if (!cat) {
394       // We have a new one
395       if (ai->maxid<15) {
396         i = ++ai->maxid;
397         snprintf(ai->categories[i], 16, "%s", wpt->icon_descr.toUtf8().data());
398         cat = ai->ids[i] = i;
399       } else {
400         // We're full!
401         warning(MYNAME ": Categories full. Category '%s' written as %s.\n", wpt->icon_descr.toUtf8().data(), zerocat?zerocat:"Not Assigned");
402       }
403     }
404   }
405 
406   pdb_write_rec(file_out, 0, cat, ct++, (const uint8_t*)rec, size);
407 
408   xfree(shortname);
409   xfree(rec);
410 
411   mkshort_del_handle(&mkshort_wr_handle);
412 }
413 
414 static void
data_write(void)415 data_write(void)
416 {
417   coto_prepare_wpt_write();
418   waypt_disp_all(coto_wpt_write);
419 }
420 
421 
422 ff_vecs_t coto_vecs = {
423   ff_type_file,
424   {(ff_cap)(ff_cap_read|ff_cap_write), ff_cap_read, ff_cap_none},
425   rd_init,
426   wr_init,
427   rd_deinit,
428   wr_deinit,
429   data_read,
430   data_write,
431   NULL,
432   coto_args,
433   CET_CHARSET_ASCII, 0	/* CET-REVIEW */
434 };
435 #endif
436