summaryrefslogtreecommitdiff
path: root/utilities
diff options
context:
space:
mode:
authorStanley Huang <stanleyhuangyc@gmail.com>2013-12-15 22:34:46 +1100
committerStanley Huang <stanleyhuangyc@gmail.com>2013-12-15 22:34:46 +1100
commit2e009a031d1fe4aff95da3bc1b981d9ace697dd3 (patch)
tree4f01eb3fd1fd9c89f221b0e1afa211e0b439edc6 /utilities
parent5837da971a01b8853b3f04809844c898eb79d5a3 (diff)
download2021-arduino-obd-2e009a031d1fe4aff95da3bc1b981d9ace697dd3.tar.gz
2021-arduino-obd-2e009a031d1fe4aff95da3bc1b981d9ace697dd3.tar.bz2
2021-arduino-obd-2e009a031d1fe4aff95da3bc1b981d9ace697dd3.zip
add Data2KML source code
Diffstat (limited to 'utilities')
-rw-r--r--utilities/data2kml/README.txt2
-rw-r--r--utilities/data2kml/data2kml.cpp361
-rw-r--r--utilities/data2kml/data2kml.sln20
-rw-r--r--utilities/data2kml/data2kml.vcxproj93
-rw-r--r--utilities/data2kml/data2kml.vcxproj.filters30
-rw-r--r--utilities/data2kml/kmlhead.txt159
-rw-r--r--utilities/data2kml/logdata.h100
7 files changed, 765 insertions, 0 deletions
diff --git a/utilities/data2kml/README.txt b/utilities/data2kml/README.txt
new file mode 100644
index 0000000..8114da1
--- /dev/null
+++ b/utilities/data2kml/README.txt
@@ -0,0 +1,2 @@
+Data2KML is an open-source command line utility written by Stanley Huang for Arduino OBD-II Data Logger (http://arduinodev.com/hardware).
+Simply drag and drop a data log CSV file to the data2kml.exe and a KML file will be generated. \ No newline at end of file
diff --git a/utilities/data2kml/data2kml.cpp b/utilities/data2kml/data2kml.cpp
new file mode 100644
index 0000000..0875503
--- /dev/null
+++ b/utilities/data2kml/data2kml.cpp
@@ -0,0 +1,361 @@
+/*************************************************************************
+* Data2Kml - Converting OBD-II/GPS logger data to KML (Google Earth)
+* Distributed under GPL v2.0
+* (c)2013 Written by Stanley Huang <stanleyhuangyc@gmail.com>
+*************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <time.h>
+#include "logdata.h"
+
+typedef struct {
+ uint32_t timestamp;
+ float lat;
+ float lon;
+ uint16_t speed;
+ uint16_t speedgps;
+ uint16_t rpm;
+ uint16_t throttle;
+ uint16_t coolant;
+ uint16_t intake;
+ uint16_t load;
+ uint16_t absload;
+ uint16_t alt;
+ int16_t acc[3];
+} DATASET;
+
+typedef struct {
+ int state;
+ FILE* fp;
+ HEADER hdr;
+ char buffer[256];
+ int bufbytes;
+ DATASET* dataset;
+ int datacount;
+ float startLat;
+ float startLon;
+ uint32_t curDate;
+ uint32_t curTime;
+ uint32_t ts;
+ uint32_t lastts;
+ float lastLat;
+ float lastLon;
+ uint32_t lastTime;
+ DATASET datas;
+} KML_DATA;
+
+void WriteKMLData(KML_DATA* kd, uint32_t timestamp, uint16_t pid, float value[])
+{
+ kd->ts = timestamp;
+ switch (pid) {
+ case PID_GPS_COORDINATES:
+ kd->datas.lat = value[0];
+ kd->datas.lon = value[1];
+ if (!kd->startLat) {
+ kd->startLat = kd->datas.lat;
+ kd->startLon = kd->datas.lon;
+ }
+ break;
+ case PID_GPS_ALTITUDE:
+ kd->datas.alt = (uint16_t)value[0];
+ break;
+ case PID_SPEED:
+ kd->datas.speed = (uint16_t)value[0];
+ break;
+ case PID_RPM:
+ kd->datas.rpm = (uint16_t)value[0];
+ break;
+ case PID_THROTTLE:
+ kd->datas.throttle = (uint16_t)value[0];
+ break;
+ case PID_COOLANT_TEMP:
+ kd->datas.coolant = (uint16_t)value[0];
+ break;
+ case PID_INTAKE_TEMP:
+ kd->datas.intake = (uint16_t)value[0];
+ break;
+ case PID_ENGINE_LOAD:
+ kd->datas.load = (uint16_t)value[0];
+ break;
+ case PID_ABS_ENGINE_LOAD:
+ kd->datas.absload = (uint16_t)value[0];
+ break;
+ case PID_GPS_SPEED:
+ kd->datas.speedgps = (uint16_t)value[0];
+ break;
+ case PID_ACC:
+ kd->datas.acc[0] = (int16_t)value[0];
+ kd->datas.acc[1] = (int16_t)value[1];
+ kd->datas.acc[2] = (int16_t)value[2];
+ break;
+ case PID_GPS_TIME: {
+ uint32_t date = (uint32_t)value[1];
+ if (date > 10000 && (kd->curDate == 0 || (date % 100) <= (kd->curDate % 100) + 1)) {
+ kd->curDate = date;
+ kd->curTime = (uint32_t)value[0];
+ }
+ } break;
+ }
+ if (kd->curTime != kd->lastTime && kd->datas.lat && kd->datas.lon) {
+ fprintf(kd->fp, "<when>");
+ if (kd->curDate) {
+ fprintf(kd->fp, "%04u-%02u-%02u", 2000 + (kd->curDate % 100), (kd->curDate / 100) % 100, kd->curDate / 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->curTime) {
+ fprintf(kd->fp, "T%02u:%02u:%02u.%03uZ", kd->curTime / 10000000, (kd->curTime / 100000) % 100, (kd->curTime / 1000) % 100, kd->curTime % 1000);
+ }
+ fprintf(kd->fp, "</when>");
+ fprintf(kd->fp, "<gx:coord>%f %f %f</gx:coord>", kd->datas.lon, kd->datas.lat, kd->datas.alt);
+
+ kd->datas.timestamp = timestamp;
+ kd->dataset = (DATASET*)realloc(kd->dataset, sizeof(DATASET) * (kd->datacount + 1));
+ memcpy(kd->dataset + kd->datacount, &kd->datas, sizeof(DATASET));
+ kd->datacount++;
+
+ kd->lastLat = kd->datas.lat;
+ kd->lastLon = kd->datas.lon;
+ kd->lastTime = kd->curTime;
+ }
+}
+
+void AppendFile(FILE* fp, char* filename)
+{
+ int uint8_ts;
+ char buffer[256];
+ FILE* fpHeader = fopen(filename, "rb");
+ if (!fpHeader) return;
+ while ((uint8_ts = fread(buffer, 1, sizeof(buffer), fpHeader)) > 0) {
+ fwrite(buffer, 1, uint8_ts, fp);
+ }
+ fclose(fpHeader);
+}
+
+void WriteKMLTail(KML_DATA* kd)
+{
+ int i;
+ int lowThrottle = 50;
+ printf("Generating extended data\n");
+
+ fprintf(kd->fp, "<ExtendedData><SchemaData schemaUrl=\"#schema\">");
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"speed\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%d</gx:value>", kd->dataset[i].speed);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+ /*
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"speedgps\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%d</gx:value>", kd->dataset[i].speedgps);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+ */
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"rpm\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%d</gx:value>", kd->dataset[i].rpm);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"gear\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%d</gx:value>", kd->dataset[i].speed ? kd->dataset[i].rpm / kd->dataset[i].speed : 1);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"coolant\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%d</gx:value>", kd->dataset[i].coolant);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"intake\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%d</gx:value>", kd->dataset[i].intake);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"load\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%d</gx:value>", kd->dataset[i].load);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+
+ /*
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"abs\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%d</gx:value>", kd->dataset[i].absload);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+ */
+
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"thr\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%d</gx:value>", kd->dataset[i].throttle);
+ if (kd->dataset[i].speed == 0)
+ lowThrottle = kd->dataset[i].throttle;
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"alt\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%.2f</gx:value>", (float)kd->dataset[i].alt / 100);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+
+ /*
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"sats\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%d</gx:value>", kd->dataset[i].sats);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+ */
+
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"acc\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>X:%d Y:%d Z:%d</gx:value>", kd->dataset[i].acc[0] / 64, kd->dataset[i].acc[1] / 64, kd->dataset[i].acc[2] / 64);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+ fprintf(kd->fp, "<gx:SimpleArrayData name=\"ts\">");
+ for (i = 0; i < kd->datacount; i++) {
+ fprintf(kd->fp, "<gx:value>%u</gx:value>", kd->dataset[i].timestamp);
+ }
+ fprintf(kd->fp, "</gx:SimpleArrayData>");
+ fprintf(kd->fp, "</SchemaData></ExtendedData>\r\n</gx:Track></Placemark>");
+
+ int n = 0;
+ for (i = 0; i < kd->datacount - 1; i++) {
+ if (kd->dataset[i].speed < 25) {
+ continue;
+ }
+ if (kd->dataset[i].throttle > lowThrottle + 2) {
+ // throttle pedal is still down
+ continue;
+ }
+ float g = 0;
+ 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.6) / 9.8f;
+ else if (kd->dataset[i].speed < kd->dataset[i - 1].speed)
+ g = (((float)kd->dataset[i].speed - kd->dataset[i - 1].speed) * 1000 / (kd->dataset[i].timestamp - kd->dataset[i - 1].timestamp) / 3.6) / 9.8f;
+ else
+ continue;
+
+ if (g <= -0.15f) {
+ n++;
+ fprintf(kd->fp, "<Placemark><name>#%d %u:%02u</name>", n, kd->dataset[i].timestamp / 60000, (kd->dataset[i].timestamp / 1000) % 60);
+ fprintf(kd->fp, "<styleUrl>#brakepoint</styleUrl><Point><coordinates>%f,%f</coordinates></Point>", kd->dataset[i].lon, kd->dataset[i].lat);
+ fprintf(kd->fp, "<ExtendedData>");
+ fprintf(kd->fp, "<Data name=\"Speed\"><value>%d</value></Data>", kd->dataset[i].speed);
+ fprintf(kd->fp, "<Data name=\"RPM\"><value>%d</value></Data>", kd->dataset[i].rpm);
+ fprintf(kd->fp, "<Data name=\"ACC\"><value>%.2fG</value></Data>", g);
+ fprintf(kd->fp, "</ExtendedData>");
+ fprintf(kd->fp, "</Placemark>\r\n");
+ uint32_t t = kd->dataset[i].timestamp + 500;
+ while (kd->dataset[++i].timestamp < t);
+ }
+ }
+ fprintf(kd->fp, "</Folder></Document></kml>");
+
+}
+
+void Cleanup(KML_DATA* kd)
+{
+ if (kd->dataset) free(kd->dataset);
+ if (kd->fp) fclose(kd->fp);
+ free(kd);
+}
+
+int ConvertToKML(const char* logfile, const char* kmlfile, uint32_t startpos, uint32_t endpos)
+{
+ FILE* fp = fopen(logfile, "r");
+ if (!fp) {
+ printf("Error opening file - %s\n", logfile);
+ return -1;
+ }
+
+ uint32_t ts = 0;
+ KML_DATA* kd = (KML_DATA*)calloc(1, sizeof(KML_DATA));
+ kd->fp = fopen(kmlfile, "w");
+
+ AppendFile(kd->fp, "kmlhead.txt");
+
+ int elapsed;
+ int pid;
+
+ while (fscanf(fp, "%d,%X,", &elapsed, &pid) > 0) {
+ char c;
+ char data[32];
+ int i = 0;
+ int index = 0;
+ float value[3] = {0};
+ while ((c = fgetc(fp)) != '\n') {
+ if (i == sizeof(data)) continue;
+ data[i++] = c;
+ if (c == ' ') {
+ if (index < 2) {
+ data[i] = 0;
+ value[index++] = atof(data);
+ i = 0;
+ }
+ }
+ }
+ data[i] = 0;
+ value[index] = atof(data);
+ ts += elapsed;
+ if (ts < startpos)
+ continue;
+
+ printf("Time=%.1f PID=%X", (float)ts / 1000, pid);
+ for (int n = 0; n <= index; n++) {
+ if (value[n] == (int)value[n])
+ printf(" D%d=%d", n, (int)value[n]);
+ else
+ printf(" D%d=%f", n, value[n]);
+ }
+ putchar('\n');
+ WriteKMLData(kd, ts, pid, value);
+
+ if (endpos && ts > endpos)
+ break;
+ }
+
+ WriteKMLTail(kd);
+ Cleanup(kd);
+ return 0;
+}
+
+int main(int argc, const char* argv[])
+{
+ int startpos = 0;
+ int endpos = 0;
+ char outfile[256];
+
+ printf("Data2KML (C)2013 ArduinoDev.com Written by Stanley Huang\n\n");
+ if (argc <= 1) {
+ printf("Usage: %s [Input file] [Output file] [Start Pos] [End Pos]\n\n", argv[0]);
+ printf("Description about the arguments:\n\n\
+Input file: path to logged CSV file\n\
+Output file: path to KML file (output in the input directory if unspecified)\n\
+Start Pos: start time (seconds) for processing\n\
+End Pos: end time (seconds) for processing\n");
+ return -1;
+ }
+
+ if (argc > 3)
+ startpos = (uint32_t)atoi(argv[3]) * 1000;
+ if (argc > 4)
+ endpos = (uint32_t)atoi(argv[4]) * 1000;
+
+ _snprintf(outfile, sizeof(outfile), "%s.kml", argv[1]);
+
+ ConvertToKML(argv[1], argc > 2 ? argv[2] : outfile, startpos, endpos);
+
+ return 0;
+}
diff --git a/utilities/data2kml/data2kml.sln b/utilities/data2kml/data2kml.sln
new file mode 100644
index 0000000..2738d93
--- /dev/null
+++ b/utilities/data2kml/data2kml.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "data2kml", "data2kml.vcxproj", "{E4ED3BCD-0D31-4960-BD22-399FB53D760F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E4ED3BCD-0D31-4960-BD22-399FB53D760F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E4ED3BCD-0D31-4960-BD22-399FB53D760F}.Debug|Win32.Build.0 = Debug|Win32
+ {E4ED3BCD-0D31-4960-BD22-399FB53D760F}.Release|Win32.ActiveCfg = Release|Win32
+ {E4ED3BCD-0D31-4960-BD22-399FB53D760F}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/utilities/data2kml/data2kml.vcxproj b/utilities/data2kml/data2kml.vcxproj
new file mode 100644
index 0000000..6911892
--- /dev/null
+++ b/utilities/data2kml/data2kml.vcxproj
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{E4ED3BCD-0D31-4960-BD22-399FB53D760F}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>data2kmz</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v110</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v110_xp</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(ProjectDir)\</OutDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(ProjectDir)\</OutDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="data2kml.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="kmlhead.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="logdata.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/utilities/data2kml/data2kml.vcxproj.filters b/utilities/data2kml/data2kml.vcxproj.filters
new file mode 100644
index 0000000..6ee7fbb
--- /dev/null
+++ b/utilities/data2kml/data2kml.vcxproj.filters
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="data2kml.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="kmlhead.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="logdata.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/utilities/data2kml/kmlhead.txt b/utilities/data2kml/kmlhead.txt
new file mode 100644
index 0000000..93334b1
--- /dev/null
+++ b/utilities/data2kml/kmlhead.txt
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2">
+<Document>
+<name>OBD-II/GPS Data Logger</name>
+<Style id="brakepoint">
+<BalloonStyle>
+ <text><strong>Brakepoint</strong><br/>
+ <![CDATA[
+ Speed: $[Speed] km/h<br/>
+ RPM: $[RPM]<br/>
+ ACC: $[ACC]
+ ]]>
+ </text>
+</BalloonStyle>
+</Style>
+<!-- Normal track style -->
+<Style id="track_n">
+ <IconStyle>
+ <scale>.5</scale>
+ <Icon>
+ <href>http://earth.google.com/images/kml-icons/track-directional/track-none.png</href>
+ </Icon>
+ </IconStyle>
+ <LabelStyle>
+ <scale>0</scale>
+ </LabelStyle>
+</Style>
+<!-- Highlighted track style -->
+<Style id="track_h">
+ <IconStyle>
+ <scale>1.2</scale>
+ <Icon>
+ <href>http://earth.google.com/images/kml-icons/track-directional/track-none.png</href>
+ </Icon>
+ </IconStyle>
+</Style>
+<StyleMap id="track">
+ <Pair>
+ <key>normal</key>
+ <styleUrl>#track_n</styleUrl>
+ </Pair>
+ <Pair>
+ <key>highlight</key>
+ <styleUrl>#track_h</styleUrl>
+ </Pair>
+</StyleMap>
+<!-- Normal multiTrack style -->
+<Style id="multiTrack_n">
+ <IconStyle>
+ <Icon>
+ <href>http://earth.google.com/images/kml-icons/track-directional/track-0.png</href>
+ </Icon>
+ </IconStyle>
+ <LineStyle>
+ <color>99ffac59</color>
+ <width>6</width>
+ </LineStyle>
+
+</Style>
+<!-- Highlighted multiTrack style -->
+<Style id="multiTrack_h">
+ <IconStyle>
+ <scale>1.2</scale>
+ <Icon>
+ <href>http://earth.google.com/images/kml-icons/track-directional/track-0.png</href>
+ </Icon>
+ </IconStyle>
+ <LineStyle>
+ <color>99ffac59</color>
+ <width>8</width>
+ </LineStyle>
+</Style>
+<StyleMap id="multiTrack">
+ <Pair>
+ <key>normal</key>
+ <styleUrl>#multiTrack_n</styleUrl>
+ </Pair>
+ <Pair>
+ <key>highlight</key>
+ <styleUrl>#multiTrack_h</styleUrl>
+ </Pair>
+</StyleMap>
+<!-- Normal waypoint style -->
+<Style id="waypoint_n">
+ <IconStyle>
+ <Icon>
+ <href>http://maps.google.com/mapfiles/kml/pal4/icon61.png</href>
+ </Icon>
+ </IconStyle>
+</Style>
+<!-- Highlighted waypoint style -->
+<Style id="waypoint_h">
+ <IconStyle>
+ <scale>1.2</scale>
+ <Icon>
+ <href>http://maps.google.com/mapfiles/kml/pal4/icon61.png</href>
+ </Icon>
+ </IconStyle>
+</Style>
+<StyleMap id="waypoint">
+ <Pair>
+ <key>normal</key>
+ <styleUrl>#waypoint_n</styleUrl>
+ </Pair>
+ <Pair>
+ <key>highlight</key>
+ <styleUrl>#waypoint_h</styleUrl>
+ </Pair>
+</StyleMap>
+<Style id="lineStyle">
+ <LineStyle>
+ <color>99ffac29</color>
+ <width>3</width>
+ </LineStyle>
+</Style>
+<Schema id="schema">
+ <gx:SimpleArrayField name="speed" type="int">
+ <displayName>Speed (km/h)</displayName>
+ </gx:SimpleArrayField>
+ <!--gx:SimpleArrayField name="speedgps" type="int">
+ <displayName>GPS Speed (km/h)</displayName>
+ </gx:SimpleArrayField-->
+ <gx:SimpleArrayField name="rpm" type="int">
+ <displayName>Engine RPM</displayName>
+ </gx:SimpleArrayField>
+ <gx:SimpleArrayField name="acc" type="string">
+ <displayName>Acceleration</displayName>
+ </gx:SimpleArrayField>
+ <gx:SimpleArrayField name="gear" type="int">
+ <displayName>Gear Ratio</displayName>
+ </gx:SimpleArrayField>
+ <gx:SimpleArrayField name="coolant" type="int">
+ <displayName>Coolant Temperature (°C)</displayName>
+ </gx:SimpleArrayField>
+ <gx:SimpleArrayField name="intake" type="int">
+ <displayName>Intake Temperature (°C)</displayName>
+ </gx:SimpleArrayField>
+ <gx:SimpleArrayField name="load" type="int">
+ <displayName>Engine Load (%)</displayName>
+ </gx:SimpleArrayField>
+ <!--gx:SimpleArrayField name="abs" type="int">
+ <displayName>Abs. Engine Load (%)</displayName>
+ </gx:SimpleArrayField-->
+ <gx:SimpleArrayField name="thr" type="int">
+ <displayName>Throttle Position (%)</displayName>
+ </gx:SimpleArrayField>
+ <gx:SimpleArrayField name="alt" type="float">
+ <displayName>Altitude (m)</displayName>
+ </gx:SimpleArrayField>
+ <gx:SimpleArrayField name="ts" type="int">
+ <displayName>Timestamp (ms)</displayName>
+ </gx:SimpleArrayField>
+</Schema>
+<Folder>
+ <name>Tracks</name>
+ <Placemark>
+ <name>Main Track</name>
+ <styleUrl>#multiTrack</styleUrl>
+ <gx:Track>
diff --git a/utilities/data2kml/logdata.h b/utilities/data2kml/logdata.h
new file mode 100644
index 0000000..68eb36f
--- /dev/null
+++ b/utilities/data2kml/logdata.h
@@ -0,0 +1,100 @@
+#define PID_RPM 0x10C
+#define PID_SPEED 0x10D
+#define PID_THROTTLE 0x111
+#define PID_ENGINE_LOAD 0x104
+#define PID_COOLANT_TEMP 0x105
+#define PID_INTAKE_TEMP 0x10F
+#define PID_MAF_FLOW 0x110
+#define PID_ABS_ENGINE_LOAD 0x143
+#define PID_AMBIENT_TEMP 0x146
+#define PID_FUEL_PRESSURE 0x10A
+#define PID_INTAKE_PRESSURE 0x10B
+#define PID_BAROMETRIC 0x133
+#define PID_TIMING_ADVANCE 0x10E
+#define PID_FUEL_LEVEL 0x12F
+#define PID_RUNTIME 0x11F
+#define PID_DISTANCE 0x131
+
+#define PID_TIME_DATE 0xF001
+#define PID_TIME_TIME 0xF002
+#define PID_TIME_PLAY_SPEED 0xF003
+
+#define PID_TURBO_BOOST 0xF108
+
+#define PID_STAT_0_60 0xF100
+#define PID_STAT_0_100 0xF101
+#define PID_STAT_0_160 0xF102
+#define PID_STAT_0_400 0xF103
+#define PID_STAT_CUR_LAP 0xF110
+#define PID_STAT_LAST_LAP 0xF111
+#define PID_STAT_BEST_LAP 0xF112
+#define PID_STAT_LAP_PROGRESS 0xF113
+
+#define PID_COMMAND 0xFFFE
+#define PID_SYNC 0xFFFF
+
+enum {
+ PID_STAT_DISTANCE = 0xF200,
+ PID_STAT_TRIP_TIME,
+ PID_STAT_WAIT_TIME,
+ PID_STAT_SPEED_MAX,
+ PID_STAT_SPEED_AVG,
+ PID_STAT_RPM_MAX,
+ PID_STAT_RPM_MIN,
+ PID_STAT_RPM_AVG,
+ PID_STAT_INTAKE_MAX,
+ PID_STAT_INTAKE_MIN,
+ PID_STAT_INTAKE_SUM,
+ PID_STAT_ACC_FORWARD,
+ PID_STAT_ACC_BACKWARD,
+};
+
+enum {
+ PID_LOCAL_DATA = 0xF300,
+ PID_LOCAL_RATE,
+ PID_REMOTE_DATA,
+ PID_REMOTE_RATE,
+ PID_TIME_REMAIN,
+ PID_BATTERY,
+ PID_DEVICE_TEMP,
+};
+
+#define PID_GPS_COORDINATES 0xF00A
+#define PID_GPS_ALTITUDE 0xF00C
+#define PID_GPS_SPEED 0xF00D
+#define PID_GPS_HEADING 0xF00E
+#define PID_GPS_SAT_COUNT 0xF00F
+#define PID_GPS_TIME 0xF010
+
+#define PID_ACC 0xF020
+#define PID_GYRO 0xF021
+
+#define PID_VIDEO_FRAME 0xFF00
+
+#define HEADER_ID ('S' << 24 | 'U' << 16 | 'D' << 8 | 'U')
+
+typedef struct {
+ uint32_t time;
+ uint16_t pid;
+ uint16_t flags;
+ uint32_t value;
+} LOG_DATA;
+
+typedef struct {
+ uint32_t id; //4
+ uint32_t dataOffset; //4
+ uint8_t ver; //1
+ uint8_t ununsed; //1
+ uint16_t flags; //2
+ uint32_t date;
+ uint32_t time;
+ uint32_t startTick;
+ uint32_t unused[2];
+ uint64_t devid; //8
+ uint8_t ununsed2[20];
+ uint8_t vin[32]; //32
+ int32_t videoOffset; //4
+ uint8_t stats[84]; //84
+ uint8_t reserved[72]; //72
+ uint32_t tail; //4
+} HEADER; //256