Add support for Google Earth (KML)

This patch adds an 'Export to KML' option to the ride
menu. It will create a .kml file including power, hr,
torque etc. These can be viewed alongside the map view
in Google Earth 5.2.

Please note this requires libkml. The features of libkml
that are required were introduced in revision 852 which
means that as of Aug 2010 you will need to checkout from
the SVN source repo and build;

svn checkout http://libkml.googlecode.com/svn/trunk/ libkml-read-only

and the ./configure mantra that worked successfully for
me on Mac OS X was;

./configure CC="gcc -arch i386" CXX="g++ -arch i386" --disable-swig

Building on WIN32 is currently fraught with issues, unless
you build via MSVC 2010. Linux is straight forward but you will
need to install / apt-get libcurl.

Fixes #133.
This commit is contained in:
Mark Liversedge
2010-08-26 10:47:49 +01:00
parent 48a25081ed
commit f6eb97ec0f
6 changed files with 391 additions and 0 deletions

305
src/KmlRideFile.cpp Normal file
View File

@@ -0,0 +1,305 @@
/*
* Copyright (c) 2010 Mark Liversedge (liversedge@gmail.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "KmlRideFile.h"
#include <QDebug>
#include <time.h>
#include <iostream>
#include <string>
#include "boost/scoped_ptr.hpp"
#include "kml/base/date_time.h"
#include "kml/base/expat_parser.h"
#include "kml/base/file.h"
#include "kml/base/vec3.h"
#include "kml/convenience/convenience.h"
#include "kml/convenience/gpx_trk_pt_handler.h"
#include "kml/dom.h"
#include "kml/dom/kml_ptr.h"
// majority of code swiped from the libkml example gpx2kml.cc
using kmlbase::ExpatParser;
using kmlbase::DateTime;
using kmlbase::Vec3;
using kmlbase::Attributes;
using kmldom::ContainerPtr;
using kmldom::FolderPtr;
using kmldom::IconStylePtr;
using kmldom::IconStyleIconPtr;
using kmldom::KmlFactory;
using kmldom::KmlPtr;
using kmldom::LabelStylePtr;
using kmldom::ListStylePtr;
using kmldom::PairPtr;
using kmldom::PointPtr;
using kmldom::SchemaPtr;
using kmldom::ExtendedDataPtr;
using kmldom::SchemaDataPtr;
using kmldom::GxSimpleArrayFieldPtr;
using kmldom::GxSimpleArrayDataPtr;
using kmldom::GxMultiTrackPtr;
using kmldom::GxTrackPtr;
using kmldom::PlacemarkPtr;
using kmldom::TimeStampPtr;
using kmldom::StylePtr;
using kmldom::StyleMapPtr;
//
// Utility functions
//
static const char kDotIcon[] =
"http://maps.google.com/mapfiles/kml/shapes/shaded_dot.png";
static ExtendedDataPtr CreateExtendedData() {
KmlFactory* kml_factory = KmlFactory::GetFactory();
ExtendedDataPtr ed = kml_factory->CreateExtendedData();
return ed;
}
static SchemaDataPtr CreateSchemaData(string name) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
SchemaDataPtr sd = kml_factory->CreateSchemaData();
sd->set_schemaurl("#" + name);
return sd;
}
static GxSimpleArrayDataPtr CreateGxSimpleArrayData(string name) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
GxSimpleArrayDataPtr sa = kml_factory->CreateGxSimpleArrayData();
sa->set_name(name);
return sa;
}
static PlacemarkPtr CreateGxTrackPlacemark(string name, GxTrackPtr tracks) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
PlacemarkPtr placemark = kml_factory->CreatePlacemark();
placemark->set_name(name);
placemark->set_id(name);
placemark->set_geometry(tracks);
return placemark;
}
static GxTrackPtr CreateGxTrack(string name) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
GxTrackPtr track = kml_factory->CreateGxTrack();
track->set_id(name);
return track;
}
static SchemaPtr CreateSchema(string name) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
SchemaPtr schema = kml_factory->CreateSchema();
schema->set_id(name);
schema->set_name(name);
return schema;
}
static GxSimpleArrayFieldPtr CreateGxSimpleArrayField(string name) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
GxSimpleArrayFieldPtr field = kml_factory->CreateGxSimpleArrayField();
field->set_type("float");
field->set_name(name);
field->set_displayname(name);
return field;
}
static IconStylePtr CreateIconStyle(double scale) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
IconStyleIconPtr icon = kml_factory->CreateIconStyleIcon();
icon->set_href(kDotIcon);
IconStylePtr icon_style = kml_factory->CreateIconStyle();
icon_style->set_icon(icon);
icon_style->set_scale(scale);
return icon_style;
}
static LabelStylePtr CreateLabelStyle(double scale) {
LabelStylePtr label_style = KmlFactory::GetFactory()->CreateLabelStyle();
label_style->set_scale(scale);
return label_style;
}
static PairPtr CreatePair(int style_state, double icon_scale) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
PairPtr pair = kml_factory->CreatePair();
pair->set_key(style_state);
StylePtr style = kml_factory->CreateStyle();
style->set_iconstyle(CreateIconStyle(icon_scale));
// Hide the label in normal style state, visible in highlight.
style->set_labelstyle(CreateLabelStyle(
style_state == kmldom::STYLESTATE_NORMAL ? 0 : 1 ));
pair->set_styleselector(style);
return pair;
}
static StylePtr CreateRadioFolder(const char* id) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
ListStylePtr list_style = kml_factory->CreateListStyle();
list_style->set_listitemtype(kmldom::LISTITEMTYPE_RADIOFOLDER);
StylePtr style = kml_factory->CreateStyle();
style->set_liststyle(list_style);
style->set_id(id);
return style;
}
static StyleMapPtr CreateStyleMap(const char* id) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
StyleMapPtr style_map = kml_factory->CreateStyleMap();
style_map->set_id(id);
style_map->add_pair(CreatePair(kmldom::STYLESTATE_NORMAL, 0.1));
style_map->add_pair(CreatePair(kmldom::STYLESTATE_HIGHLIGHT, 0.3));
return style_map;
}
//
// Serialise the ride
//
bool
KmlFileReader::writeRideFile(const RideFile * ride, QFile &file) const
{
// Create a new DOM document and setup styles et al
kmldom::KmlFactory* kml_factory = kmldom::KmlFactory::GetFactory();
kmldom::DocumentPtr document = kml_factory->CreateDocument();
const char* kRadioFolderId = "radio-folder-style";
document->add_styleselector(CreateRadioFolder(kRadioFolderId));
document->set_styleurl(std::string("#") + kRadioFolderId);
const char* kStyleMapId = "style-map";
document->add_styleselector(CreateStyleMap(kStyleMapId));
document->set_name("Golden Cheetah");
// add the schema elements for each data series
SchemaPtr schemadef = CreateSchema("schema");
// gx:SimpleArrayField ...
if (ride->areDataPresent()->cad)
schemadef->add_gx_simplearrayfield(CreateGxSimpleArrayField("cadence"));
if (ride->areDataPresent()->hr)
schemadef->add_gx_simplearrayfield(CreateGxSimpleArrayField("heartrate"));
if (ride->areDataPresent()->watts)
schemadef->add_gx_simplearrayfield(CreateGxSimpleArrayField("power"));
if (ride->areDataPresent()->nm)
schemadef->add_gx_simplearrayfield(CreateGxSimpleArrayField("torque"));
if (ride->areDataPresent()->headwind)
schemadef->add_gx_simplearrayfield(CreateGxSimpleArrayField("headwind"));
document->add_schema(schemadef);
// setup trip folder (shown on lhs of google earth
FolderPtr folder = kmldom::KmlFactory::GetFactory()->CreateFolder();
folder->set_name("Bike Rides");
document->add_feature(folder);
// Create a track for the entire ride
GxTrackPtr track = CreateGxTrack("Entire Ride");
PlacemarkPtr placemark = CreateGxTrackPlacemark(QString("Bike %1")
.arg(ride->startTime().toString()).toStdString(), track);
folder->add_feature(placemark);
//
// Basic Data -- Lat/Lon/Alt and Timestamp
//
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
// lots of arsing around with dates XXX clean this up
QDateTime timestamp(ride->startTime().addSecs(datapoint->secs));
string stdctimestamp = timestamp.toString(Qt::ISODate).toStdString() + "Z"; //<< 'Z' fixes crash!
kmlbase::DateTime *when = kmlbase::DateTime::Create(stdctimestamp.data());
if (datapoint->lat && datapoint->lon) track->add_when(when->GetXsdDateTime());
}
// <when> loop through the entire ride
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) track->add_gx_coord(kmlbase::Vec3(datapoint->lon, datapoint->lat, datapoint->alt));
}
//
// Extended Data -- cadence, heartrate, power, torque, headwind
//
ExtendedDataPtr extended = CreateExtendedData();
track->set_extendeddata(extended);
SchemaDataPtr schema = CreateSchemaData("schema");
extended->add_schemadata(schema);
// power
if (ride->areDataPresent()->watts) {
GxSimpleArrayDataPtr power = CreateGxSimpleArrayData("power");
schema->add_gx_simplearraydata(power);
// now create a GxSimpleArrayData
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) power->add_gx_value(QString("%1").arg(datapoint->watts).toStdString());
}
}
// cadence
if (ride->areDataPresent()->cad) {
GxSimpleArrayDataPtr cadence = CreateGxSimpleArrayData("cadence");
schema->add_gx_simplearraydata(cadence);
// now create a GxSimpleArrayData
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) cadence->add_gx_value(QString("%1").arg(datapoint->cad).toStdString());
}
}
// heartrate
if (ride->areDataPresent()->hr) {
GxSimpleArrayDataPtr heartrate = CreateGxSimpleArrayData("heartrate");
schema->add_gx_simplearraydata(heartrate);
// now create a GxSimpleArrayData
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) heartrate->add_gx_value(QString("%1").arg(datapoint->hr).toStdString());
}
}
// torque
if (ride->areDataPresent()->nm) {
GxSimpleArrayDataPtr torque = CreateGxSimpleArrayData("torque");
schema->add_gx_simplearraydata(torque);
// now create a GxSimpleArrayData
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) torque->add_gx_value(QString("%1").arg(datapoint->nm).toStdString());
}
}
// headwind
if (ride->areDataPresent()->headwind) {
GxSimpleArrayDataPtr headwind = CreateGxSimpleArrayData("headwind");
schema->add_gx_simplearraydata(headwind);
// now create a GxSimpleArrayData
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) headwind->add_gx_value(QString("%1").arg(datapoint->headwind).toStdString());
}
}
// Create KML document
kmldom::KmlPtr kml = kml_factory->CreateKml();
kml->set_feature(document);
// make sure the google extensions are added in!
kmlbase::Attributes gxxmlns22;
gxxmlns22.SetValue("gx", "http://www.google.com/kml/ext/2.2");
kml->MergeXmlns(gxxmlns22);
// Serialize
if (!file.open(QIODevice::WriteOnly)) return(false);
file.write(kmldom::SerializePretty(kml).data());
file.close();
return(true);
}