You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

814 lines
18KB

  1. /*
  2. Support for Suunto Trackmanager SDF format.
  3. Copyright (C) 2005,2007 Olaf Klein, o.b.klein@gpsbabel.org
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
  15. */
  16. /*
  17. 2006/04/05: initial release (not published in GPSBbabel)
  18. 2006/07/19: finished reader and writer for type 4,5,28 of ver. 1
  19. 2006/10/31: remove wptdata from case statement (data_write)
  20. ToDo: Ascending/Descending
  21. */
  22. #include "defs.h"
  23. #if CSVFMTS_ENABLED
  24. #include "cet_util.h"
  25. #include "csv_util.h"
  26. #include "jeeps/gpsmath.h"
  27. #include "grtcirc.h"
  28. #include <time.h>
  29. #include <stdio.h>
  30. #include <stdlib.h>
  31. #define MYNAME "stmsdf"
  32. #define ALT(a) (a->altitude != unknown_alt) ? a->altitude : 0
  33. typedef enum {
  34. sdf_unknown,
  35. sdf_header,
  36. sdf_points,
  37. sdf_custom
  38. } sdf_section_e;
  39. static gbfile* fin, *fout;
  40. static int lineno;
  41. static int datum;
  42. static int filetype;
  43. static route_head* route;
  44. static queue trackpts;
  45. static QString rte_name;
  46. static QString rte_desc;
  47. static Waypoint* trkpt_out;
  48. static route_head* trk_out;
  49. static double trkpt_dist;
  50. static double minalt, maxalt, maxspeed;
  51. static double this_distance, all_dist;
  52. static time_t this_time, all_time;
  53. static double all_asc, all_desc;
  54. static int this_index; /* from 1 to ... */
  55. static int all_points;
  56. static int this_points;
  57. static int saved_points;
  58. static time_t start_time;
  59. static unsigned char this_valid;
  60. static short_handle short_h;
  61. #define route_index this_index
  62. #define track_index this_index
  63. #define all_route_points all_points
  64. #define all_track_points all_points
  65. #define route_points this_points
  66. #define track_points this_points
  67. #define saved_track_points saved_points
  68. #define this_route_valid this_valid
  69. /* placeholders for options */
  70. static char* opt_route_index;
  71. static int opt_route_index_value;
  72. static
  73. arglist_t stmsdf_args[] = {
  74. {
  75. "index", &opt_route_index,
  76. "Index of route (if more than one in source)", "1", ARGTYPE_INT, "1", NULL
  77. },
  78. ARG_TERMINATOR
  79. };
  80. /* ----------------------------------------------------------- */
  81. static void
  82. parse_header(char* line)
  83. {
  84. char* str;
  85. char* key = NULL;
  86. const char* prod = NULL;
  87. int column = -1;
  88. while ((str = csv_lineparse(line, "=", "", lineno))) {
  89. line = NULL;
  90. column++;
  91. switch (column) {
  92. case 0:
  93. key = xstrdup(str);
  94. break;
  95. case 1:
  96. if (case_ignore_strcmp(key, "DATUM") == 0) {
  97. datum = GPS_Lookup_Datum_Index(str);
  98. } else if (case_ignore_strcmp(key, "FILEVERSION") == 0) {
  99. int ver = atoi(str);
  100. is_fatal((ver != 1),
  101. MYNAME ": This version '%d' is not yet supported. Please report!", ver);
  102. } else if (case_ignore_strcmp(key, "NAME") == 0) {
  103. rte_name = str;
  104. } else if (case_ignore_strcmp(key, "NOTES") == 0) /* ToDo */;
  105. else if (case_ignore_strcmp(key, "SOURCE") == 0) {
  106. rte_desc = str;
  107. } else if (case_ignore_strcmp(key, "TYPE") == 0) {
  108. filetype = atoi(str);
  109. switch (filetype) {
  110. case 4: /* M9 TrackLog (Suunto Sail Manager) */
  111. case 5: /* route */
  112. case 28: /* X9 TrackLog (Suunto Trek Manager */
  113. break;
  114. // 2014-02-11: Added breaks after 78 and 79 as the author "obviously"
  115. // meant to treat those as handled.
  116. case 78:
  117. prod = "S6 SkiChrono";
  118. break;
  119. case 79:
  120. prod = "S6 Skilog";
  121. break;
  122. default:
  123. if (prod == NULL) {
  124. prod = "unknown";
  125. }
  126. fatal(MYNAME ": Unsupported file type (%s, type %d)!\n", prod, filetype);
  127. }
  128. }
  129. break;
  130. }
  131. }
  132. if (key) {
  133. xfree(key);
  134. }
  135. }
  136. static int
  137. track_qsort_cb(const void* a, const void* b)
  138. {
  139. const Waypoint* wa = *(Waypoint**)a;
  140. const Waypoint* wb = *(Waypoint**)b;
  141. return wa->GetCreationTime().toTime_t() - wb->GetCreationTime().toTime_t();
  142. }
  143. static void
  144. finalize_tracks(void)
  145. {
  146. Waypoint** list;
  147. int count = 0;
  148. queue* elem, *tmp;
  149. int index;
  150. route_head* track = NULL;
  151. int trackno = 0;
  152. count = 0;
  153. QUEUE_FOR_EACH(&trackpts, elem, tmp) {
  154. count++;
  155. };
  156. if (count == 0) {
  157. return;
  158. }
  159. list = (Waypoint**)xmalloc(count * sizeof(*list));
  160. index = 0;
  161. QUEUE_FOR_EACH(&trackpts, elem, tmp) {
  162. list[index] = (Waypoint*)elem;
  163. dequeue(elem);
  164. index++;
  165. }
  166. qsort(list, count, sizeof(*list), track_qsort_cb);
  167. for (index = 0; index < count; index++) {
  168. Waypoint* wpt = list[index];
  169. if (wpt->wpt_flags.fmt_use == 2) { /* log continued */
  170. track = NULL;
  171. }
  172. if (track == NULL) {
  173. track = route_head_alloc();
  174. track_add_head(track);
  175. trackno++;
  176. if (rte_name != NULL) {
  177. if (trackno > 1) {
  178. track->rte_name = QString("%1 (%2)").arg(rte_name).arg(trackno);
  179. } else {
  180. track->rte_name = rte_name;
  181. }
  182. }
  183. if (rte_desc != NULL) {
  184. track->rte_desc = rte_desc;
  185. }
  186. }
  187. track_add_wpt(track, wpt);
  188. if (wpt->wpt_flags.fmt_use == 1) { /* log pause */
  189. track = NULL;
  190. }
  191. wpt->wpt_flags.fmt_use = 0;
  192. }
  193. xfree(list);
  194. }
  195. static void
  196. parse_point(char* line)
  197. {
  198. char* str;
  199. int column = -1;
  200. int what = -1; /* -1 = unknown, 0 = tp, 1 = mp, 2 = wp, 3 = ap */
  201. Waypoint* wpt = NULL;
  202. char* cx;
  203. int hour, min, sec, day, month, year;
  204. year = hour = -1;
  205. while ((str = csv_lineparse(line, ",", "", lineno))) {
  206. line = NULL;
  207. column++;
  208. switch (column) {
  209. case 0:
  210. if (strcmp(str, "\"TP\"") == 0) {
  211. what = 0;
  212. column++; /* skip name */
  213. } else if (strcmp(str, "\"MP\"") == 0) {
  214. what = 1;
  215. } else if (strcmp(str, "\"WP\"") == 0) {
  216. what = 2;
  217. } else if (strcmp(str, "\"AP\"") == 0) {
  218. what = 3;
  219. } else {
  220. warning(MYNAME ": Unknown point type %s at line %d!\n", str, lineno);
  221. return;
  222. }
  223. wpt = new Waypoint;
  224. break;
  225. case 1:
  226. wpt->shortname = csv_stringclean(str, QString("\""));
  227. if ((what == 2) || (what == 3)) {
  228. column += 2; /* doesn't have date and time */
  229. }
  230. break;
  231. case 2:
  232. sscanf(str, "%d.%d.%d", &day, &month, &year);
  233. break;
  234. case 3:
  235. while ((cx = strchr(str, '.'))) {
  236. *cx = ':';
  237. }
  238. sscanf(str, "%d:%d:%d", &hour, &min, &sec);
  239. break;
  240. case 4:
  241. wpt->latitude = atof(str);
  242. break;
  243. case 5:
  244. wpt->longitude = atof(str);
  245. break;
  246. case 6:
  247. wpt->altitude = atof(str);
  248. break;
  249. case 7:
  250. switch (what) {
  251. case 0:
  252. WAYPT_SET(wpt, speed, atof(str) * 3.6);
  253. break;
  254. case 3:
  255. WAYPT_SET(wpt, proximity, atof(str));
  256. wpt->notes = QString().sprintf("Alarm point: radius=%s", str);
  257. break;
  258. }
  259. break;
  260. case 8:
  261. if (what == 0) {
  262. WAYPT_SET(wpt, course, atof(str));
  263. }
  264. break;
  265. case 9:
  266. case 10:
  267. break;
  268. case 11:
  269. if (what == 1) {
  270. wpt->wpt_flags.fmt_use = atoi(str); /* memory point type */
  271. }
  272. break;
  273. }
  274. }
  275. if ((year > -1) && (hour > -1)) {
  276. struct tm tm;
  277. memset(&tm, 0, sizeof(tm));
  278. tm.tm_year = year - 1900;
  279. tm.tm_mon = month - 1;
  280. tm.tm_mday = day;
  281. tm.tm_hour = hour;
  282. tm.tm_min = min;
  283. tm.tm_sec = sec;
  284. wpt->SetCreationTime(mklocaltime(&tm));
  285. }
  286. if (datum != DATUM_WGS84) {
  287. double ht;
  288. GPS_Math_WGS84_To_Known_Datum_M(wpt->latitude, wpt->longitude, 0,
  289. &wpt->latitude, &wpt->longitude, &ht, datum);
  290. }
  291. switch (what) {
  292. case 0:
  293. case 1:
  294. ENQUEUE_TAIL(&trackpts, &wpt->Q);
  295. break;
  296. case 2:
  297. case 3:
  298. if (route == NULL) {
  299. route = route_head_alloc();
  300. route_add_head(route);
  301. }
  302. route_add_wpt(route, wpt);
  303. break;
  304. }
  305. }
  306. /* ----------------------------------------------------------- */
  307. static void
  308. rd_init(const QString& fname)
  309. {
  310. fin = gbfopen(fname, "r", MYNAME);
  311. lineno = 0;
  312. route = NULL;
  313. datum = DATUM_WGS84;
  314. filetype = 28;
  315. rte_name = rte_desc = QString();
  316. QUEUE_INIT(&trackpts);
  317. }
  318. static void
  319. rd_deinit(void)
  320. {
  321. gbfclose(fin);
  322. rte_name = QString();
  323. rte_desc = QString();
  324. }
  325. static void
  326. data_read(void)
  327. {
  328. char* buf;
  329. sdf_section_e section = sdf_unknown;
  330. while ((buf = gbfgetstr(fin))) {
  331. char* cin = lrtrim(buf);
  332. if ((lineno++ == 0) && fin->unicode) {
  333. cet_convert_init(CET_CHARSET_UTF8, 1);
  334. }
  335. if (*cin == '\0') {
  336. continue;
  337. }
  338. if (*cin == '[') {
  339. char* cend = strchr(++cin, ']');
  340. if (cend != NULL) {
  341. *cend = '\0';
  342. cin = lrtrim(cin);
  343. }
  344. if ((*cin == '\0') || (cend == NULL)) {
  345. fatal(MYNAME ": Invalid section header!\n");
  346. }
  347. if (case_ignore_strcmp(cin, "HEADER") == 0) {
  348. section = sdf_header;
  349. } else if (case_ignore_strcmp(cin, "POINTS") == 0) {
  350. section = sdf_points;
  351. } else if (case_ignore_strncmp(cin, "CUSTOM", 6) == 0) {
  352. section = sdf_custom;
  353. } else {
  354. warning(MYNAME ": Unknown section \"%s\". Please report.\n", cin);
  355. section = sdf_unknown;
  356. }
  357. } else switch (section) {
  358. case sdf_header:
  359. parse_header(cin);
  360. break;
  361. case sdf_points:
  362. parse_point(cin);
  363. break;
  364. case sdf_custom:
  365. case sdf_unknown:
  366. break;
  367. }
  368. }
  369. finalize_tracks(); /* memory points can be at the end of all trackpoints */
  370. }
  371. static void
  372. calculate(const Waypoint* wpt, double* dist, double* speed, double* course,
  373. double* asc, double* desc)
  374. {
  375. if (trkpt_out != NULL) {
  376. time_t time;
  377. *course = heading_true_degrees(
  378. RAD(trkpt_out->latitude), RAD(trkpt_out->longitude),
  379. RAD(wpt->latitude), RAD(wpt->longitude));
  380. *dist = radtometers(gcdist(
  381. RAD(trkpt_out->latitude), RAD(trkpt_out->longitude),
  382. RAD(wpt->latitude), RAD(wpt->longitude)));
  383. if (*dist < 0.1) {
  384. *dist = 0; /* calc. diffs on 32- and 64-bit hosts */
  385. }
  386. time = wpt->creation_time.toTime_t() - trkpt_out->GetCreationTime().toTime_t();
  387. if (time == 0) {
  388. *speed = 0;
  389. } else {
  390. *speed = *dist / (double)time;
  391. }
  392. if (asc && desc && (trkpt_out->altitude != unknown_alt) && (wpt->altitude != unknown_alt)) {
  393. double dh = wpt->altitude - trkpt_out->altitude;
  394. if (dh > 0) {
  395. *asc += dh;
  396. } else {
  397. *desc -= dh;
  398. }
  399. }
  400. } else {
  401. *speed = 0;
  402. *dist = 0;
  403. *course = 0;
  404. if (asc) {
  405. *asc = 0;
  406. }
  407. if (desc) {
  408. *desc = 0;
  409. }
  410. }
  411. if WAYPT_HAS(wpt, speed) {
  412. *speed = wpt->speed / 3.6; /* -> meters per second */
  413. }
  414. if WAYPT_HAS(wpt, course) {
  415. *course = wpt->course;
  416. }
  417. }
  418. /* pre-calculation callbacks */
  419. static void
  420. any_hdr_calc_cb(const route_head* trk)
  421. {
  422. trkpt_out = NULL;
  423. this_distance = 0;
  424. this_time = 0;
  425. this_points = 0;
  426. this_index++;
  427. this_valid = ((opt_route_index_value < 1) || (opt_route_index_value == this_index));
  428. if (! this_valid) {
  429. return;
  430. }
  431. if (rte_name.isEmpty() && !trk->rte_name.isEmpty()) {
  432. rte_name = trk->rte_name;
  433. rte_desc = trk->rte_desc;
  434. }
  435. trk_out = (route_head*)trk;
  436. }
  437. static void
  438. any_waypt_calc_cb(const Waypoint* wpt)
  439. {
  440. double speed, course, dist;
  441. /* we can only write ONE route */
  442. if (! this_valid) {
  443. return;
  444. }
  445. if ((all_points == 0) && (this_points == 0)) {
  446. start_time = wpt->GetCreationTime().toTime_t();
  447. }
  448. this_points++;
  449. if ((wpt->altitude != unknown_alt) && (wpt->altitude < minalt)) {
  450. minalt = wpt->altitude;
  451. }
  452. if ((wpt->altitude != unknown_alt) && (wpt->altitude > maxalt)) {
  453. maxalt = wpt->altitude;
  454. }
  455. calculate(wpt, &dist, &speed, &course, &all_asc, &all_desc);
  456. if (speed > maxspeed) {
  457. maxspeed = speed;
  458. }
  459. this_distance = this_distance + dist;
  460. if (trkpt_out != NULL) {
  461. this_time += (wpt->GetCreationTime().toTime_t() - trkpt_out->GetCreationTime().toTime_t());
  462. }
  463. trkpt_out = (Waypoint*)wpt;
  464. }
  465. static void
  466. any_tlr_calc_cb(const route_head* trk)
  467. {
  468. if (! this_valid) {
  469. return;
  470. }
  471. all_dist += this_distance;
  472. all_time += this_time;
  473. all_points += this_points;
  474. }
  475. /* write callbacks */
  476. static void
  477. track_disp_hdr_cb(const route_head* trk)
  478. {
  479. track_index++;
  480. track_points = 0;
  481. trk_out = (route_head*)trk;
  482. trkpt_out = NULL;
  483. }
  484. static void
  485. track_disp_wpt_cb(const Waypoint* wpt)
  486. {
  487. struct tm tm;
  488. char tbuf[32];
  489. double course, speed, dist;
  490. int flag = 0;
  491. track_points++;
  492. all_track_points++;
  493. time_t ct = wpt->GetCreationTime().toTime_t();
  494. tm = *localtime(&ct);
  495. strftime(tbuf, sizeof(tbuf), "%d.%m.%Y,%H:%M.%S", &tm);
  496. calculate(wpt, &dist, &speed, &course, NULL, NULL);
  497. trkpt_dist = trkpt_dist + dist;
  498. if (track_points == trk_out->rte_waypt_ct) { /* I'm the last in that list */
  499. if (all_track_points != saved_track_points) { /* but not the overall latest */
  500. flag = 1;
  501. }
  502. } else if (track_points == 1) { /* I'm the first in that list */
  503. if (all_track_points > 1) { /* but not the first ever seen */
  504. flag = 2;
  505. }
  506. }
  507. if (flag == 1) {
  508. QString name = wpt->shortname;
  509. if (name == NULL) {
  510. name = "Log paused";
  511. }
  512. gbfprintf(fout, "\"MP\",\"%s\"", CSTR(name));
  513. } else if (flag == 2) {
  514. QString name = wpt->shortname;
  515. if (name == NULL) {
  516. name = "Log continued";
  517. }
  518. gbfprintf(fout, "\"MP\",\"%s\"", CSTR(name));
  519. } else {
  520. gbfprintf(fout, "\"TP\"");
  521. }
  522. gbfprintf(fout, ",%s,%.6lf,%.6lf,%.f,%.2f",
  523. tbuf,
  524. wpt->latitude, wpt->longitude, ALT(wpt), speed);
  525. if (flag) {
  526. gbfprintf(fout, ",0,0,%d", flag); /* press, temperature, memory point type */
  527. } else {
  528. gbfprintf(fout, ",%.1f", course);
  529. }
  530. if (trkpt_dist != 0) {
  531. gbfprintf(fout, ",%.6f\n", trkpt_dist);
  532. } else {
  533. gbfprintf(fout, ",0\n");
  534. }
  535. trkpt_out = (Waypoint*)wpt;
  536. }
  537. static void
  538. track_disp_tlr_cb(const route_head* rte)
  539. {
  540. trkpt_out = NULL;
  541. }
  542. static void
  543. route_disp_hdr_cb(const route_head* rte)
  544. {
  545. route_index++;
  546. this_route_valid = ((opt_route_index_value < 1) || (opt_route_index_value == track_index));
  547. }
  548. static void
  549. route_disp_wpt_cb(const Waypoint* wpt)
  550. {
  551. if (this_route_valid) {
  552. QString sn;
  553. if (global_opts.synthesize_shortnames) {
  554. sn = mkshort_from_wpt(short_h, wpt);
  555. } else {
  556. sn = mkshort(short_h, wpt->shortname);
  557. }
  558. gbfprintf(fout, "\"WP\",\"%s\",%.8lf,%.8lf,%.f\n",
  559. CSTR(sn), wpt->latitude, wpt->longitude, ALT(wpt));
  560. }
  561. }
  562. static void
  563. track_disp_custom_cb(const Waypoint* wpt)
  564. {
  565. if (wpt->GetCreationTime().isValid() && (wpt->altitude != unknown_alt)) {
  566. gbfprintf(fout, "%d,%.f\n", (int)(wpt->GetCreationTime().toTime_t() - start_time), wpt->altitude);
  567. }
  568. }
  569. static void
  570. wr_init(const QString& fname)
  571. {
  572. fout = gbfopen(fname, "w", MYNAME);
  573. short_h = mkshort_new_handle();
  574. }
  575. static void
  576. wr_deinit(void)
  577. {
  578. mkshort_del_handle(&short_h);
  579. gbfclose(fout);
  580. }
  581. static void
  582. data_write(void)
  583. {
  584. gbfprintf(fout, "[HEADER]\n");
  585. gbfprintf(fout, "FILEVERSION=1\n");
  586. gbfprintf(fout, "SOURCE=FILE\n");
  587. gbfprintf(fout, "DATUM=WGS84\n");
  588. rte_name = QString();
  589. rte_desc = QString();
  590. trkpt_out = NULL;
  591. opt_route_index_value = -1; /* take all tracks from data pool */
  592. track_index = 0;
  593. minalt = -unknown_alt;
  594. maxalt = unknown_alt;
  595. maxspeed = 0;
  596. all_dist = 0;
  597. all_time = 0;
  598. all_asc = 0;
  599. all_desc = 0;
  600. all_points = 0;
  601. start_time = 0;
  602. setshort_length(short_h, 100);
  603. setshort_badchars(short_h, "\r\n");
  604. setshort_mustupper(short_h, 0);
  605. setshort_mustuniq(short_h, 0);
  606. setshort_whitespace_ok(short_h, 1);
  607. setshort_repeating_whitespace_ok(short_h, 1);
  608. switch (global_opts.objective) {
  609. case wptdata:
  610. case unknown_gpsdata:
  611. break;
  612. case rtedata:
  613. gbfprintf(fout, "TYPE=5\n");
  614. opt_route_index_value = atoi(opt_route_index);
  615. route_disp_all(any_hdr_calc_cb, any_tlr_calc_cb, any_waypt_calc_cb);
  616. gbfprintf(fout, "DISTANCE=%.f\n", all_dist);
  617. if (!rte_name.isEmpty()) {
  618. gbfprintf(fout, "NAME=%s\n", CSTR(rte_name));
  619. }
  620. gbfprintf(fout, "[POINTS]\n");
  621. if (route_points > 0) {
  622. track_index = 0;
  623. route_disp_all(route_disp_hdr_cb, NULL, route_disp_wpt_cb);
  624. }
  625. break;
  626. case trkdata:
  627. gbfprintf(fout, "TYPE=28\n");
  628. track_disp_all(any_hdr_calc_cb, any_tlr_calc_cb, any_waypt_calc_cb);
  629. if (all_track_points > 0) {
  630. if (!rte_name.isEmpty()) {
  631. gbfprintf(fout, "NAME=%s\n", CSTR(rte_name));
  632. }
  633. if (minalt != -unknown_alt) {
  634. gbfprintf(fout, "MINALT=%.f\n", minalt);
  635. }
  636. if (maxalt != unknown_alt) {
  637. gbfprintf(fout, "MAXALT=%.f\n", maxalt);
  638. }
  639. gbfprintf(fout, "MAXSPEED=%.2f\n", maxspeed);
  640. gbfprintf(fout, "DISTANCE=%.f\n", all_dist);
  641. gbfprintf(fout, "DURATION=%lu\n", all_time);
  642. // gbfprintf(fout, "TOTASC=%.f\n", all_asc);
  643. // gbfprintf(fout, "TOTDSC=%.f\n", all_desc);
  644. if (start_time) {
  645. struct tm tm;
  646. char tbuf[32];
  647. tm = *localtime(&start_time);
  648. strftime(tbuf, sizeof(tbuf), "%d.%m.%Y %H:%M.%S", &tm);
  649. gbfprintf(fout, "DATE=%s\n", tbuf);
  650. }
  651. if (all_time) {
  652. gbfprintf(fout, "AVGSPEED=%.2f\n", all_dist / (double)all_time);
  653. }
  654. }
  655. gbfprintf(fout, "[POINTS]\n");
  656. if (all_track_points > 0) {
  657. trkpt_dist = 0;
  658. saved_track_points = all_track_points;
  659. all_track_points = 0;
  660. track_disp_all(track_disp_hdr_cb, track_disp_tlr_cb, track_disp_wpt_cb);
  661. if (start_time) {
  662. gbfprintf(fout, "[CUSTOM1]\n");
  663. track_index = 0;
  664. track_disp_all(NULL, NULL, track_disp_custom_cb);
  665. }
  666. }
  667. break;
  668. case posndata:
  669. fatal(MYNAME ": Realtime positioning not supported.\n");
  670. break;
  671. }
  672. }
  673. /* ------------------------------------------------------------------ */
  674. ff_vecs_t stmsdf_vecs = {
  675. ff_type_file,
  676. {
  677. ff_cap_none,
  678. (ff_cap)(ff_cap_read | ff_cap_write),
  679. (ff_cap)(ff_cap_read | ff_cap_write)
  680. },
  681. rd_init,
  682. wr_init,
  683. rd_deinit,
  684. wr_deinit,
  685. data_read,
  686. data_write,
  687. NULL,
  688. stmsdf_args,
  689. CET_CHARSET_MS_ANSI, 0 /* CET-REVIEW */
  690. };
  691. /* ================================================================== */
  692. #endif /* CSVFMTS_ENABLED */