/************************************************************************* * Data2Kml - Converting OBD/GPS data to KML * Distributed under GPL v3.0 license * (c)2013 Written by Stanley Huang *************************************************************************/ #include #include #include #include #include #include #include #include "logdata.h" #include "data2kml.h" uint16_t hex2uint16(const char *p); int ishex(char c); void WriteKMLData(KML_DATA* kd, uint32_t timestamp, uint16_t pid, float value[]) { // in the case timestamp overflowed or device reset if (timestamp + kd->tsOffset < kd->cur.timestamp && timestamp < 120000) { kd->tsOffset = kd->cur.timestamp; } timestamp += kd->tsOffset; if (kd->cur.timestamp != timestamp && kd->cur.time != kd->last.time && (kd->cur.flags & (FLAG_HAVE_LAT | FLAG_HAVE_LNG)) == (FLAG_HAVE_LAT | FLAG_HAVE_LNG)) do { // look for correct position to insert data DATASET* lastpd = 0; for (DATASET* pd = kd->data; pd && pd->timestamp <= kd->cur.timestamp; pd = pd->next) { lastpd = pd; } // filter out duplicated data if (lastpd && lastpd->timestamp == kd->cur.timestamp) { kd->last = kd->cur; break; } // filter out invalid coordinates if (kd->last.timestamp) { if (kd->cur.lat - kd->last.lat > 1 || kd->cur.lat - kd->last.lat < -1 || kd->cur.lng - kd->last.lng > 1 || kd->cur.lng - kd->last.lng < -1) { break; } // calculate boundaries if (kd->cur.lat < kd->bounds[0].lat) kd->bounds[0].lat = kd->cur.lat; else if (kd->cur.lat > kd->bounds[1].lat) kd->bounds[1].lat = kd->cur.lat; if (kd->cur.lng < kd->bounds[0].lng) kd->bounds[0].lng = kd->cur.lng; else if (kd->cur.lng > kd->bounds[1].lng) kd->bounds[1].lng = kd->cur.lng; } else { // first data kd->bounds[1].lat = kd->bounds[0].lat = kd->cur.lat; kd->bounds[1].lng = kd->bounds[0].lng = kd->cur.lng; } DATASET* newdata = malloc(sizeof(DATASET)); memcpy(newdata, &kd->cur, sizeof(DATASET)); if (newdata->pidCount) { newdata->pidData = malloc(newdata->pidCount * sizeof(float)); memcpy(newdata->pidData, kd->cur.pidData, newdata->pidCount * sizeof(float)); } if (!lastpd) { if (kd->data) { newdata->next = kd->data; kd->data = newdata; } else { kd->data = newdata; } } else if (lastpd->next) { // inserting newdata->next = lastpd->next; lastpd->next = newdata; } else { lastpd->next = newdata; } kd->datacount++; // calculate distance between two points if (kd->last.lat || kd->last.lng) { float a = (kd->last.lat - kd->cur.lat) * 100000; float b = (kd->last.lng - kd->cur.lng) * 100000; float distance = sqrtf(a * a + b * b);; if (distance >= 10000) { // invalid coordinates return; } kd->distance += distance; } fprintf(kd->fp, ""); if (kd->cur.date) { fprintf(kd->fp, "%04u-%02u-%02u", 2000 + (kd->cur.date % 100), (kd->cur.date / 100) % 100, kd->cur.date / 10000); } else { time_t yesterday = time(0) - 86400; struct tm* btm = localtime(&yesterday); fprintf(kd->fp, "%04d-%02d-%02d", 1900 + btm->tm_year, btm->tm_mon + 1, btm->tm_mday); } if (kd->cur.time) { fprintf(kd->fp, "T%02u:%02u:%02u", kd->cur.time / 1000000, (kd->cur.time / 10000) % 100, (kd->cur.time / 100) % 100); if (kd->cur.time % 100) { fprintf(kd->fp, ".%02u0Z", kd->cur.time % 100); } } fprintf(kd->fp, ""); fprintf(kd->fp, "%f %f %d", kd->cur.lng, kd->cur.lat, (int)kd->cur.alt); // keep as last coordinates kd->last = kd->cur; } while (0); kd->cur.timestamp = timestamp; switch (pid) { case PID_GPS_LATITUDE: kd->cur.lat = value[0]; kd->cur.flags |= FLAG_HAVE_LAT; break; case PID_GPS_LONGITUDE: kd->cur.lng = value[0]; kd->cur.flags |= FLAG_HAVE_LNG; break; case PID_GPS_ALTITUDE: kd->cur.alt = value[0]; break; case PID_GPS_SPEED: kd->cur.speed = value[0]; break; case PID_ACC: kd->cur.acc[0] = (int16_t)value[0]; kd->cur.acc[1] = (int16_t)value[1]; kd->cur.acc[2] = (int16_t)value[2]; break; case PID_GPS_DATE: kd->cur.date = (uint32_t)value[0]; kd->cur.timestamp = kd->cur.timestamp; break; case PID_GPS_TIME: kd->cur.time = (uint32_t)value[0]; kd->cur.timestamp = kd->cur.timestamp; break; case PID_BATTERY_VOLTAGE: kd->cur.battery = (uint16_t)value[0]; break; default: if (pid >= 0x100) { pid -= 0x100; if (!kd->cur.pidData) { kd->cur.pidData = calloc(pid + 1, sizeof(float)); kd->cur.pidCount = pid; } else if (pid > kd->cur.pidCount) { kd->cur.pidData = realloc(kd->cur.pidData, (pid + 1) * sizeof(float)); kd->cur.pidCount = pid; } kd->cur.pidData[pid] = value[0]; } } } void WriteExtData(KML_DATA* kd, int pid) { if (pid < 0x100) return; fprintf(kd->fp, "", pid); pid -= 0x100; for (DATASET* pd = kd->data; pd; pd = pd->next) { if (pid < pd->pidCount) { fprintf(kd->fp, "%d", (int)pd->pidData[pid]); } else { fprintf(kd->fp, ""); } } fprintf(kd->fp, ""); } void WriteKMLTail(KML_DATA* kd) { DATASET* pd; printf("Generating extended data\n"); fprintf(kd->fp, ""); WriteExtData(kd, PID_SPEED); WriteExtData(kd, PID_RPM); WriteExtData(kd, PID_COOLANT_TEMP); WriteExtData(kd, PID_ENGINE_LOAD); WriteExtData(kd, PID_THROTTLE); fprintf(kd->fp, "", PID_BATTERY_VOLTAGE); for (pd = kd->data; pd; pd = pd->next) { fprintf(kd->fp, "%.1f", (float)pd->battery / 100); } fprintf(kd->fp, ""); fprintf(kd->fp, "", PID_GPS_ALTITUDE); for (pd = kd->data; pd; pd = pd->next) { fprintf(kd->fp, "%d", (int)pd->alt); } fprintf(kd->fp, ""); fprintf(kd->fp, "", PID_ACC); for (pd = kd->data; pd; pd = pd->next) { fprintf(kd->fp, "%d/%d/%d", pd->acc[0], pd->acc[1], pd->acc[2]); } fprintf(kd->fp, ""); fprintf(kd->fp, "\r\n"); #if 0 n = 0; for (i = 0; i < kd->datacount - 1; i++) { float g = 0; if (kd->dataset[i].speed < 25) { continue; } if (kd->dataset[i].throttle > lowThrottle + 2) { // throttle pedal is still down continue; } if (kd->dataset[i + 1].speed < kd->dataset[i].speed) g = (((float)kd->dataset[i + 1].speed - kd->dataset[i].speed) * 1000 / (kd->dataset[i + 1].timestamp - kd->dataset[i].timestamp) / 3.6f) / 9.8f; else continue; if (g <= -0.2f) { uint32_t t = kd->dataset[i].timestamp + 1000; while (kd->dataset[++i].timestamp < t); n++; fprintf(kd->fp, "#%d %u:%02u", n, kd->dataset[i].timestamp / 60000, (kd->dataset[i].timestamp / 1000) % 60); fprintf(kd->fp, "#brakepoint%f,%f", kd->dataset[i].lng, kd->dataset[i].lat); fprintf(kd->fp, ""); fprintf(kd->fp, "%d", kd->dataset[i].speed); fprintf(kd->fp, "%d", kd->dataset[i].rpm); fprintf(kd->fp, "%.2fG", g); fprintf(kd->fp, ""); fprintf(kd->fp, "\r\n"); } } #endif fprintf(kd->fp, ""); if (kd->fp) fclose(kd->fp); } void CleanupKML(KML_DATA* kd) { for (DATASET* pd = kd->data; pd; ) { DATASET* next = pd->next; if (pd->pidData) free(pd->pidData); free(pd); pd = next; } kd->cur.next = 0; } int ConvertToKML(KML_DATA* kd, FILE* fp, const char* kmlfile, uint32_t startpos, uint32_t endpos) { int pid; uint32_t ts = 0; char line[1024]; if (!kd) return -1; kd->fp = fopen(kmlfile, "wb"); if (!fp || !kd->fp) return -1; fprintf(stderr, "Opened %s for writing\n", kmlfile); FILE* fpHeader = fopen("config/kmlstyle.tpl", "rb"); if (fpHeader) { for (;;) { int n = fread(line, 1, sizeof(line), fpHeader); if (n <= 0) break; fwrite(line, 1, n, kd->fp); } fclose(fpHeader); } //write UTF-8 file mark //fprintf(kd.fp, "%c%c%c", 0xEF, 0xBB, 0xBF); fprintf(kd->fp, ""); while (fscanf(fp, "%1024s\n", line) > 0) { for (char* p = strtok(line, ","); p; p = strtok(0, ",")) { if (!ishex(*p)) break; pid = hex2uint16(p); if (!(p = strchr(p, ':'))) break; float value[3] = { 0 }; for (int n = 0; n < 3; n++) { value[n] = (float)atof(++p); if (!(p = strchr(p, ';'))) break; } if (pid == 0) ts = (uint32_t)value[0]; if (ts < startpos) { continue; } else if (endpos && ts > endpos) { break; } if (pid) { kd->pidMap[pid] = 1; WriteKMLData(kd, ts, pid, value); } } if (endpos && ts > endpos) break; } WriteKMLTail(kd); return kd->datacount; }