1 /*
2
3 Support for Destinator POI's, Itineraries and Tracklogs.
4 ( as described at "http://mozoft.com/d3log.html" )
5
6 Copyright (C) 2008 Olaf Klein, o.b.klein@gpsbabel.org
7
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 #include <QtCore/QDebug>
25
26 #include <cassert> // for assert
27 #include <cmath> // for fabs, lround
28 #include <cstdio> // for NULL, SEEK_CUR, snprintf
29 #include <cstdint>
30 #include <cstring> // for strcmp, memmove, memset, strlen
31 #include <ctime> // for gmtime
32
33 #include <QtCore/QByteArray> // for QByteArray
34 #include <QtCore/QScopedPointer> // for QScopedPointer
35 #include <QtCore/QString> // for QString
36 #include <QtCore/QTextCodec> // for QTextCodec, QTextCodec::IgnoreHeader
37 #include <QtCore/QTextDecoder> // for QTextDecoder
38 #include <QtCore/QTextEncoder> // for QTextEncoder
39 #include <QtCore/QTime> // for QTime
40 #include <QtCore/QVector> // for QVector
41
42 #include "defs.h"
43 #include "garmin_fs.h" // for garmin_fs_t, garmin_fs_flags_t, GMSD_GET, GMSD_SETSTRQ, garmin_fs_alloc, GMSD_FIND
44 #include "gbfile.h" // for gbfputdbl, gbfgetdbl, gbfputint32, gbfeof, gbfgetint32, gbfread, gbfrewind, gbfseek, gbfclose, gbfputflt, gbfgetc, gbfgetflt, gbfputc, gbfputcstr, gbfputint16, gbfwrite, gbfile, gbfopen_le
45 #include "src/core/datetime.h" // for DateTime
46 #include "strptime.h" // for strptime
47
48
49 #define MYNAME "destinator"
50 #define DST_DYN_POI "Dynamic POI"
51 #define DST_ITINERARY "City->Street"
52
53 static
54 QVector<arglist_t> destinator_args = {
55 };
56
57 static gbfile* fin, *fout;
58 static gpsdata_type data_type;
59 static QTextCodec* utf16le_codec{nullptr};
60
61
62 /*******************************************************************************/
63 /* READER */
64 /*-----------------------------------------------------------------------------*/
65
66 static garmin_fs_t*
gmsd_init(Waypoint * wpt)67 gmsd_init(Waypoint* wpt)
68 {
69 garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
70 if (gmsd == nullptr) {
71 gmsd = garmin_fs_alloc(-1);
72 wpt->fs.FsChainAdd(gmsd);
73 }
74 return gmsd;
75 }
76
77 static QString
read_wcstr()78 read_wcstr()
79 {
80 QScopedPointer<QTextDecoder> decoder(utf16le_codec->makeDecoder(QTextCodec::IgnoreHeader));
81 QString result;
82 bool done;
83 do {
84 QByteArray chunk = gbfreadbuf(2, fin);
85 assert(chunk.size() == 2);
86 if ((chunk.at(0) != 0) || (chunk.at(1) != 0)) {
87 result += decoder->toUnicode(chunk);
88 done = false;
89 } else {
90 done = true;
91 }
92 } while (!done);
93 return result.trimmed();
94 }
95
96 static void
write_wcstr(const QString & str)97 write_wcstr(const QString& str)
98 {
99 /* use an encoder to avoid generating a BOM. */
100 QScopedPointer<QTextEncoder> encoder(utf16le_codec->makeEncoder(QTextCodec::IgnoreHeader));
101 QByteArray qba = encoder->fromUnicode(str).append(2, 0);
102 assert((qba.size() % 2) == 0);
103 gbfwrite(qba.constData(), 1, qba.size(), fout);
104 }
105
106 static int
read_until_wcstr(const QString & str)107 read_until_wcstr(const QString& str)
108 {
109 QScopedPointer<QTextEncoder> encoder(utf16le_codec->makeEncoder(QTextCodec::IgnoreHeader));
110 QByteArray target = encoder->fromUnicode(str).append(2, 0);
111 assert((target.size() % 2) == 0);
112
113 int eos = 0;
114
115 int sz = target.size();
116 QByteArray buff(sz, 0);
117
118 while (! gbfeof(fin)) {
119
120 char c = gbfgetc(fin);
121 buff = buff.right(sz-1);
122 buff.append(c);
123
124 if (c == 0) {
125 eos++;
126 if (eos >= 2) { /* two or more zero bytes => end of string */
127 // QByteArray::compare introduced in Qt 5.12, but we can use
128 // QByteArray::startsWith as buff.size() == target.size().
129 if (buff.startsWith(target)) {
130 return 1;
131 }
132 }
133 } else {
134 eos = 0;
135 }
136 }
137 return 0;
138 }
139
140 static void
destinator_read_poi()141 destinator_read_poi()
142 {
143 int count = 0;
144
145 gbfrewind(fin);
146
147 while (!(gbfeof(fin))) {
148 QString str;
149 garmin_fs_t* gmsd;
150
151 if (count == 0) {
152 str = read_wcstr();
153 if ((str != DST_DYN_POI)) {
154 fatal(MYNAME "_poi: Invalid record header!\n");
155 }
156 } else if (! read_until_wcstr(DST_DYN_POI)) {
157 break;
158 }
159
160 count++;
161
162 auto* wpt = new Waypoint;
163
164 wpt->shortname = read_wcstr();
165 wpt->notes = read_wcstr(); /* comment */
166
167 QString hnum = read_wcstr(); /* house number */
168
169 str = read_wcstr(); /* street */
170 if (str.isEmpty()) {
171 str = hnum;
172 hnum = QString();
173 }
174 if (!str.isEmpty()) {
175 gmsd = gmsd_init(wpt);
176 if (!hnum.isEmpty()) {
177 str += " ";
178 str += hnum;
179 }
180 garmin_fs_t::set_addr(gmsd, str);
181 }
182
183 if (!(str = read_wcstr()).isEmpty()) { /* city */
184 gmsd = gmsd_init(wpt);
185 garmin_fs_t::set_city(gmsd, str);
186 }
187
188 (void) read_wcstr(); /* unknown */
189
190 if (!(str = read_wcstr()).isEmpty()) { /* postcode */
191 gmsd = gmsd_init(wpt);
192 garmin_fs_t::set_postal_code(gmsd, str);
193 }
194
195 (void) read_wcstr(); /* unknown */
196
197 (void) gbfgetdbl(fin);
198
199 wpt->longitude = gbfgetdbl(fin);
200 wpt->latitude = gbfgetdbl(fin);
201 double ll = gbfgetdbl(fin);
202 if (ll != wpt->longitude) {
203 fatal(MYNAME "_poi: Invalid file!\n");
204 }
205 ll = gbfgetdbl(fin);
206 if (ll != wpt->latitude) {
207 fatal(MYNAME "_poi: Invalid file!\n");
208 }
209
210 waypt_add(wpt);
211 }
212 }
213
214 static void
destinator_read_rte()215 destinator_read_rte()
216 {
217 int count = 0;
218 route_head* rte = nullptr;
219
220 gbfrewind(fin);
221
222 while (!(gbfeof(fin))) {
223 if (count == 0) {
224 QString str = read_wcstr();
225 if ((str != DST_ITINERARY)) {
226 fatal(MYNAME "_itn: Invalid record header!\n");
227 }
228 } else if (! read_until_wcstr(DST_ITINERARY)) {
229 break;
230 }
231
232 count++;
233
234 auto* wpt = new Waypoint;
235
236 wpt->shortname = read_wcstr();
237 wpt->notes = read_wcstr();
238
239 (void) gbfgetint32(fin);
240 (void) gbfgetdbl(fin);
241 (void) gbfgetdbl(fin);
242
243 wpt->longitude = gbfgetdbl(fin);
244 wpt->latitude = gbfgetdbl(fin);
245 if (gbfgetdbl(fin) != wpt->longitude) {
246 fatal(MYNAME "_itn: Invalid file!\n");
247 }
248 if (gbfgetdbl(fin) != wpt->latitude) {
249 fatal(MYNAME "_itn: Invalid file!\n");
250 }
251
252 if (! rte) {
253 rte = new route_head;
254 route_add_head(rte);
255 }
256 route_add_wpt(rte, wpt);
257
258 (void) gbfgetdbl(fin);
259 (void) gbfgetdbl(fin);
260 }
261 }
262
263 static void
destinator_read_trk()264 destinator_read_trk()
265 {
266 char TXT[4] = "TXT";
267 int recno = -1;
268 route_head* trk = nullptr;
269
270 gbfrewind(fin);
271
272 while (!(gbfeof(fin))) {
273 struct tm tm;
274 char buff[20];
275
276 recno++;
277
278 if (gbfeof(fin)) {
279 break;
280 }
281
282 auto* wpt = new Waypoint;
283
284 wpt->longitude = gbfgetdbl(fin);
285 wpt->latitude = gbfgetdbl(fin);
286 wpt->altitude = gbfgetdbl(fin);
287
288 (void) gbfgetdbl(fin); /* unknown */
289 (void) gbfgetdbl(fin); /* unknown */
290 (void) gbfgetdbl(fin); /* unknown */
291
292 wpt->fix = (fix_type) gbfgetint32(fin);
293 wpt->sat = gbfgetint32(fin);
294
295 gbfseek(fin, 12 * sizeof(int32_t), SEEK_CUR); /* SAT info */
296
297 int date = gbfgetint32(fin);
298 double milliseconds = gbfgetflt(fin);
299
300 gbfseek(fin, 2 * 12, SEEK_CUR); /* SAT info */
301
302 gbfread(TXT, 1, 3, fin);
303 if (strcmp(TXT, "TXT") != 0) {
304 fatal(MYNAME "_trk: No (or unknown) file!\n");
305 }
306
307 gbfseek(fin, 13, SEEK_CUR); /* unknown */
308
309 memset(&tm, 0, sizeof(tm));
310
311 snprintf(buff, sizeof(buff), "%06d%.f", date, milliseconds);
312 strptime(buff, "%d%m%y%H%M%S", &tm);
313 int millisecs = lround(milliseconds) % 1000;
314 wpt->SetCreationTime(mkgmtime(&tm), millisecs);
315
316 if (wpt->fix > 0) {
317 wpt->fix = (fix_type)(wpt->fix + 1);
318 }
319
320 if (! trk) {
321 trk = new route_head;
322 track_add_head(trk);
323 }
324 track_add_wpt(trk, wpt);
325 }
326 }
327
328 static void
destinator_read()329 destinator_read()
330 {
331 double d0, d1;
332 char buff[16];
333
334 if (! gbfread(buff, 1, sizeof(buff), fin)) {
335 fatal(MYNAME ": Unexpected EOF (end of file)!\n");
336 }
337
338 int i0 = le_read32(&buff[0]);
339 int i1 = le_read32(&buff[4]);
340
341 if ((i0 == 0x690043) && (i1 == 0x790074)) {
342 if (data_type != rtedata) {
343 warning(MYNAME ": Using Destinator Itinerary Format!\n");
344 }
345 destinator_read_rte();
346 } else if ((i0 == 0x790044) && (i1 == 0x61006e)) {
347 if (data_type != wptdata) {
348 warning(MYNAME ": Using Destinator POI Format!\n");
349 }
350 destinator_read_poi();
351 } else {
352 if (data_type != trkdata) {
353 warning(MYNAME ": Using Destinator Tracklog Format!\n");
354 }
355
356 le_read64(&d0, &buff[0]);
357 le_read64(&d1, &buff[8]);
358 if ((fabs(d0) > 180) || (fabs(d1) > 90)) {
359 fatal(MYNAME ": No Destinator (.dat) file!\n");
360 }
361 destinator_read_trk();
362 }
363 }
364
365 /*******************************************************************************/
366 /* WRITER */
367 /*-----------------------------------------------------------------------------*/
368
369 static void
destinator_wpt_disp(const Waypoint * wpt)370 destinator_wpt_disp(const Waypoint* wpt)
371 {
372 garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
373
374 write_wcstr(DST_DYN_POI);
375 write_wcstr((!wpt->shortname.isEmpty()) ? wpt->shortname : "WPT");
376 write_wcstr((!wpt->notes.isEmpty()) ? wpt->notes : wpt->description);
377
378 write_wcstr(nullptr); /* house number */
379 write_wcstr(garmin_fs_t::get_addr(gmsd, nullptr)); /* street */
380 write_wcstr(garmin_fs_t::get_city(gmsd, nullptr)); /* city */
381 write_wcstr(nullptr); /* unknown */
382 write_wcstr(garmin_fs_t::get_postal_code(gmsd, nullptr)); /* postcode */
383 write_wcstr(nullptr); /* unknown */
384
385 gbfputint32(0, fout);
386 gbfputint32(0, fout);
387
388 gbfputdbl(wpt->longitude, fout);
389 gbfputdbl(wpt->latitude, fout);
390 gbfputdbl(wpt->longitude, fout);
391 gbfputdbl(wpt->latitude, fout);
392
393 gbfputdbl(0, fout);
394 gbfputdbl(0, fout);
395 }
396
397 static void
destinator_trkpt_disp(const Waypoint * wpt)398 destinator_trkpt_disp(const Waypoint* wpt)
399 {
400 int i;
401
402 gbfputdbl(wpt->longitude, fout);
403 gbfputdbl(wpt->latitude, fout);
404 gbfputdbl(wpt->altitude, fout);
405 gbfputdbl(0, fout);
406 gbfputdbl(0, fout);
407 gbfputdbl(0, fout);
408 gbfputint32(wpt->fix > fix_unknown ? wpt->fix - 1 : 0, fout);
409 gbfputint32(wpt->sat, fout);
410 for (i = 0; i < 12; i++) {
411 gbfputint32(0, fout);
412 }
413
414 if (wpt->creation_time.isValid()) {
415 const time_t ct = wpt->GetCreationTime().toTime_t();
416 struct tm tm = *gmtime(&ct);
417 tm.tm_mon += 1;
418 tm.tm_year -= 100;
419 int date = (tm.tm_mday * 10000) + (tm.tm_mon * 100) + tm.tm_year;
420 gbfputint32(date, fout);
421 double milliseconds = (tm.tm_hour * 10000) +
422 (tm.tm_min * 100) + tm.tm_sec;
423 milliseconds = (milliseconds * 1000) + (wpt->GetCreationTime().time().msec());
424
425 gbfputflt(milliseconds, fout);
426 } else {
427 gbfputint32(0, fout); /* Is this invalid ? */
428 gbfputflt(0, fout);
429 }
430
431 for (i = 0; i < 12; i++) {
432 gbfputint16(0, fout);
433 }
434 gbfputcstr("TXT", fout);
435 for (i = 0; i < 12; i++) {
436 gbfputc(0, fout);
437 }
438 }
439
440 static void
destinator_rtept_disp(const Waypoint * wpt)441 destinator_rtept_disp(const Waypoint* wpt)
442 {
443 write_wcstr(DST_ITINERARY);
444 write_wcstr((!wpt->shortname.isEmpty()) ? wpt->shortname : "RTEPT");
445 write_wcstr((!wpt->notes.isEmpty()) ? wpt->notes : wpt->description);
446
447 gbfputint32(0, fout);
448 gbfputdbl(0, fout);
449 gbfputdbl(0, fout);
450
451 gbfputdbl(wpt->longitude, fout);
452 gbfputdbl(wpt->latitude, fout);
453 gbfputdbl(wpt->longitude, fout);
454 gbfputdbl(wpt->latitude, fout);
455
456 gbfputdbl(0, fout);
457 gbfputdbl(0, fout);
458 }
459
460 /*******************************************************************************
461 * %%% global callbacks called by gpsbabel main process %%% *
462 *******************************************************************************/
463
464 static void
destinator_rd_init(const QString & fname)465 destinator_rd_init(const QString& fname)
466 {
467 fin = gbfopen_le(fname, "rb", MYNAME);
468 utf16le_codec = QTextCodec::codecForName("UTF-16LE");
469 }
470
471 static void
destinator_rd_deinit()472 destinator_rd_deinit()
473 {
474 gbfclose(fin);
475 utf16le_codec = nullptr;
476 }
477
478 static void
destinator_read_poi_wrapper()479 destinator_read_poi_wrapper()
480 {
481 data_type = wptdata;
482 destinator_read();
483 }
484
485 static void
destinator_read_rte_wrapper()486 destinator_read_rte_wrapper()
487 {
488 data_type = rtedata;
489 destinator_read();
490 }
491
492 static void
destinator_read_trk_wrapper()493 destinator_read_trk_wrapper()
494 {
495 data_type = trkdata;
496 destinator_read();
497 }
498
499 static void
destinator_wr_init(const QString & fname)500 destinator_wr_init(const QString& fname)
501 {
502 fout = gbfopen_le(fname, "wb", MYNAME);
503 utf16le_codec = QTextCodec::codecForName("UTF-16LE");
504 }
505
506 static void
destinator_wr_deinit()507 destinator_wr_deinit()
508 {
509 gbfclose(fout);
510 utf16le_codec = nullptr;
511 }
512
513 static void
destinator_write_poi()514 destinator_write_poi()
515 {
516 waypt_disp_all(destinator_wpt_disp);
517 }
518
519 static void
destinator_write_rte()520 destinator_write_rte()
521 {
522 route_disp_all(nullptr, nullptr, destinator_rtept_disp);
523 }
524
525 static void
destinator_write_trk()526 destinator_write_trk()
527 {
528 track_disp_all(nullptr, nullptr, destinator_trkpt_disp);
529 }
530
531 /**************************************************************************/
532
533 ff_vecs_t destinator_poi_vecs = {
534 ff_type_file,
535 {
536 (ff_cap)(ff_cap_read | ff_cap_write) /* waypoints */,
537 ff_cap_none /* tracks */,
538 ff_cap_none /* routes */
539 },
540 destinator_rd_init,
541 destinator_wr_init,
542 destinator_rd_deinit,
543 destinator_wr_deinit,
544 destinator_read_poi_wrapper,
545 destinator_write_poi,
546 nullptr,
547 &destinator_args,
548 CET_CHARSET_UTF8, 1 /* fixed */
549 , NULL_POS_OPS,
550 nullptr};
551
552 ff_vecs_t destinator_itn_vecs = {
553 ff_type_file,
554 {
555 ff_cap_none /* waypoints */,
556 ff_cap_none /* tracks */,
557 (ff_cap)(ff_cap_read | ff_cap_write) /* routes */
558 },
559 destinator_rd_init,
560 destinator_wr_init,
561 destinator_rd_deinit,
562 destinator_wr_deinit,
563 destinator_read_rte_wrapper,
564 destinator_write_rte,
565 nullptr,
566 &destinator_args,
567 CET_CHARSET_UTF8, 1 /* fixed */
568 , NULL_POS_OPS,
569 nullptr};
570
571 ff_vecs_t destinator_trl_vecs = {
572 ff_type_file,
573 {
574 ff_cap_none /* waypoints */,
575 (ff_cap)(ff_cap_read | ff_cap_write) /* tracks */,
576 ff_cap_none /* routes */
577 },
578 destinator_rd_init,
579 destinator_wr_init,
580 destinator_rd_deinit,
581 destinator_wr_deinit,
582 destinator_read_trk_wrapper,
583 destinator_write_trk,
584 nullptr,
585 &destinator_args,
586 CET_CHARSET_UTF8, 1 /* fixed */
587 , NULL_POS_OPS,
588 nullptr };
589
590 /**************************************************************************/
591