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