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