1 // Copyright 2008, Google Inc. All rights reserved.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are met:
5 //
6 //  1. Redistributions of source code must retain the above copyright notice,
7 //     this list of conditions and the following disclaimer.
8 //  2. Redistributions in binary form must reproduce the above copyright notice,
9 //     this list of conditions and the following disclaimer in the documentation
10 //     and/or other materials provided with the distribution.
11 //  3. Neither the name of Google Inc. nor the names of its contributors may be
12 //     used to endorse or promote products derived from this software without
13 //     specific prior written permission.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
16 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
18 // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 
26 // This program converts a GPX track log to KML.  Each GPX <trkpt> is converted
27 // to a KML <Point> <Placemark> with <TimeStamp> and <ExtendedData>.   All
28 // other GPX elements are ignored.  The <Placemark>'s are arranged in
29 // <Folder>'s by date and "trip".  Note that "trip" is not the same as the GPX
30 // file <trk> or <trkseg>; "trip" is determined solely by point timestamp and
31 // an internal threshold for determining when a new trip begins.
32 
33 #include <time.h>
34 #include <iostream>
35 #include <string>
36 #include "boost/scoped_ptr.hpp"
37 #include "kml/base/date_time.h"
38 #include "kml/base/expat_parser.h"
39 #include "kml/base/file.h"
40 #include "kml/base/vec3.h"
41 #include "kml/convenience/convenience.h"
42 #include "kml/convenience/gpx_trk_pt_handler.h"
43 #include "kml/dom.h"
44 
45 using kmlbase::ExpatParser;
46 using kmlbase::DateTime;
47 using kmlbase::Vec3;
48 using kmldom::ContainerPtr;
49 using kmldom::FolderPtr;
50 using kmldom::IconStylePtr;
51 using kmldom::IconStyleIconPtr;
52 using kmldom::KmlFactory;
53 using kmldom::KmlPtr;
54 using kmldom::LabelStylePtr;
55 using kmldom::ListStylePtr;
56 using kmldom::PairPtr;
57 using kmldom::PointPtr;
58 using kmldom::PlacemarkPtr;
59 using kmldom::TimeStampPtr;
60 using kmldom::StylePtr;
61 using kmldom::StyleMapPtr;
62 
63 static const char kDotIcon[] =
64     "http://maps.google.com/mapfiles/kml/shapes/shaded_dot.png";
65 
66 static const int kTripThreshold = 600;  // 10 minutes.
67 
CreateIconStyle(double scale)68 static IconStylePtr CreateIconStyle(double scale) {
69   KmlFactory* kml_factory = KmlFactory::GetFactory();
70   IconStyleIconPtr icon = kml_factory->CreateIconStyleIcon();
71   icon->set_href(kDotIcon);
72   IconStylePtr icon_style = kml_factory->CreateIconStyle();
73   icon_style->set_icon(icon);
74   icon_style->set_scale(scale);
75   return icon_style;
76 }
77 
CreateLabelStyle(double scale)78 static LabelStylePtr CreateLabelStyle(double scale) {
79   LabelStylePtr label_style = KmlFactory::GetFactory()->CreateLabelStyle();
80   label_style->set_scale(scale);
81   return label_style;
82 }
83 
CreatePair(int style_state,double icon_scale)84 static PairPtr CreatePair(int style_state, double icon_scale) {
85   KmlFactory* kml_factory = KmlFactory::GetFactory();
86   PairPtr pair = kml_factory->CreatePair();
87   pair->set_key(style_state);
88   StylePtr style = kml_factory->CreateStyle();
89   style->set_iconstyle(CreateIconStyle(icon_scale));
90   // Hide the label in normal style state, visible in highlight.
91   style->set_labelstyle(CreateLabelStyle(
92       style_state == kmldom::STYLESTATE_NORMAL ? 0 : 1 ));
93   pair->set_styleselector(style);
94   return pair;
95 }
96 
CreateRadioFolder(const char * id)97 static StylePtr CreateRadioFolder(const char* id) {
98   KmlFactory* kml_factory = KmlFactory::GetFactory();
99   ListStylePtr list_style = kml_factory->CreateListStyle();
100   list_style->set_listitemtype(kmldom::LISTITEMTYPE_RADIOFOLDER);
101   StylePtr style = kml_factory->CreateStyle();
102   style->set_liststyle(list_style);
103   style->set_id(id);
104   return style;
105 }
106 
CreateStyleMap(const char * id)107 static StyleMapPtr CreateStyleMap(const char* id) {
108   KmlFactory* kml_factory = KmlFactory::GetFactory();
109   StyleMapPtr style_map = kml_factory->CreateStyleMap();
110   style_map->set_id(id);
111   style_map->add_pair(CreatePair(kmldom::STYLESTATE_NORMAL, 1.1));
112   style_map->add_pair(CreatePair(kmldom::STYLESTATE_HIGHLIGHT, 1.3));
113   return style_map;
114 }
115 
116 // Convert GPX <trkpt>'s to KML <Placemark>'s.
117 // For example, this GPX <trkpt>:
118 // <trkpt lat="-33.911973070" lon="18.422974152">
119 //   <ele>4.943848</ele>
120 //   <time>2008-10-11T14:55:41Z</time>
121 // </trkpt>
122 // ...is translated to this KML <Placemark>:
123 // <Placemark>
124 //   <name>15:50:48</name>
125 //   <TimeStamp><when>2008-10-01T15:50:48Z</when></TimeStamp>
126 //   <ExtendedData>
127 //     <Data name="date"><value>2008-10-01</value></Data>
128 //     <Data name="time"><value>15:50:48</value></Data>
129 //   </ExtendedData>
130 //   <Point><coordinates>18.427167954,-33.911966113,0</coordinates></Point>
131 // </Placemark>
132 // There is a <Folder> for each day named in xsd:date format (YYYY-MM-DD) and
133 // a sub-<Folder> within each day for each segment of <trkpt>'s.  Note that
134 // this is not the same as the GPX <trgseg>.  Instead a threshold is used to
135 // determine a break in a segment.  (In practice a GPS unit may lose signal
136 // for far longer than is represented in a <trkseg>).  This threshold is
137 // intended to represent a best guess for when one real-world trip ends and a
138 // new one begins.  This handler ignores all other elements in GPX.
139 // TODO: Ideally each user-visible time is converted to the timezone of the
140 // point.
141 class TrkPtHandler : public kmlconvenience::GpxTrkPtHandler {
142  public:
143   // Create the TrkPtHandler with a KML Container into which all KML generated
144   // here is appended.  Each <trkpt> is saved as a <Point> <Placemark> whose
145   // <styleUrl> is set to the given shared style.
TrkPtHandler(ContainerPtr container,const char * point_style_id)146   TrkPtHandler(ContainerPtr container, const char* point_style_id)
147     : root_container_(container),
148       point_style_id_(point_style_id),
149       last_time_(0) {
150   }
151 
152   // This is called for each <trkpt>.
153   // Create a <Point> <Placemark> at the location specified by Vec3 and give
154   // it a <TimeStamp> based on when.
HandlePoint(const kmlbase::Vec3 & where,const std::string & when)155   virtual void HandlePoint(const kmlbase::Vec3& where,
156                            const std::string& when) {
157     boost::scoped_ptr<DateTime> date_time(DateTime::Create(when));
158     if (!date_time.get()) {
159       std::cerr << "bad DateTime " << when << std::endl;
160       return;
161     }
162     PointPtr point = kmlconvenience::CreatePointFromVec3(where);
163     FolderPtr folder = ManageFolders(*date_time);
164     folder->add_feature(kmlconvenience::CreatePointPlacemarkWithTimeStamp(
165         point, *date_time, point_style_id_));
166   }
167 
168  private:
169   // Create and/or return the folder for this DateTime.  This method owns the
170   // creation of the folder hierarchy.  The first level of hierarchy is a
171   // folder for each date within which is a folder for each "trip".  It is
172   // presumed that the DateTime's are presented to this method in time-order.
ManageFolders(DateTime & date_time)173   FolderPtr ManageFolders(/*const*/ DateTime& date_time) {
174     if (date_time.GetTimeT() - last_time_ > kTripThreshold) {
175       // Threshold exceeded.  Save trip and start a new folder.
176       if (!date_folder_) {  // First time around.
177         date_folder_ = kmldom::KmlFactory::GetFactory()->CreateFolder();
178         date_folder_->set_name(last_date_ = date_time.GetXsdDate());
179       }
180       date_folder_->add_feature(trip_folder_);
181       trip_folder_ = kmldom::KmlFactory::GetFactory()->CreateFolder();
182       trip_folder_->set_name(date_time.GetXsdTime());
183     }
184     last_time_ = date_time.GetTimeT();
185     if (last_date_ != date_time.GetXsdDate()) {
186       // Dawn.  Save the previous day.
187       root_container_->add_feature(date_folder_);
188       date_folder_ = kmldom::KmlFactory::GetFactory()->CreateFolder();
189       date_folder_->set_name(last_date_ = date_time.GetXsdDate());
190     }
191     return trip_folder_;
192   }
193 
194   ContainerPtr root_container_;
195   const char* point_style_id_;
196   FolderPtr date_folder_;
197   FolderPtr trip_folder_;
198   PointPtr point_;
199   boost::scoped_ptr<DateTime> date_time_;
200   std::string char_data_;
201   time_t last_time_;
202   std::string last_date_;
203 };
204 
ConvertGpxTrkPtsToKml(const char * gpx_pathname,const char * kml_pathname)205 static bool ConvertGpxTrkPtsToKml(const char* gpx_pathname,
206                                   const char* kml_pathname) {
207   kmldom::KmlFactory* kml_factory = kmldom::KmlFactory::GetFactory();
208   // Create a <Document> to hand to the GPX parser.
209   kmldom::DocumentPtr document = kml_factory->CreateDocument();
210   // Make the <Document> a radio folder.
211   const char* kRadioFolderId = "radio-folder-style";
212   document->add_styleselector(CreateRadioFolder(kRadioFolderId));
213   // <Document> has no inline style of its own so point its styleUrl to the
214   // shared style.
215   document->set_styleurl(std::string("#") + kRadioFolderId);
216   // Add a shared style for all the Placemark's to use.
217   const char* kStyleMapId = "style-map";
218   document->add_styleselector(CreateStyleMap(kStyleMapId));
219 
220   // Create the GPX parser directing its output to the <Document>.
221   TrkPtHandler trk_pt_handler(document, kStyleMapId);
222 
223   // Read the GPX file contents.
224   std::string gpx_data;
225   if (!kmlbase::File::ReadFileToString(gpx_pathname, &gpx_data)) {
226     std::cerr << "read failed: " << gpx_pathname << std::endl;
227     return false;
228   }
229 
230   // Parse the GPX data into the <Document>.
231   std::string errors;
232 
233   if (!ExpatParser::ParseString(gpx_data, &trk_pt_handler, &errors, false)) {
234     std::cerr << "parse failed: " << gpx_pathname << std::endl;
235     return false;
236   }
237 
238   // Put the <Document> in a <kml> element and write everything out to the
239   // supplied pathname.
240   kmldom::KmlPtr kml = kml_factory->CreateKml();
241   kml->set_feature(document);
242   std::string kml_data = kmldom::SerializePretty(kml);
243 
244   if (!kmlbase::File::WriteStringToFile(kml_data, kml_pathname)) {
245     std::cerr << "write failed: " << kml_pathname << std::endl;
246     return false;
247   }
248   return true;
249 }
250 
main(int argc,char ** argv)251 int main(int argc, char **argv) {
252   if (argc != 3) {
253     std::cerr << "usage: " << argv[0] << " input.gpx output.kml" << std::endl;
254     return 1;
255   }
256   return ConvertGpxTrkPtsToKml(argv[1], argv[2]) ? 0 : 1;
257 }
258 
259