1 // FlightHistoryUriHandler.cxx -- FlightHistory service
3 // Written by Torsten Dreyer, started February 2015.
5 // Copyright (C) 2015 Torsten Dreyer
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "FlightHistoryUriHandler.hxx"
22 #include "SimpleDOM.hxx"
23 #include <3rdparty/cjson/cJSON.h>
25 #include <Aircraft/FlightHistory.hxx>
26 #include <Main/fg_props.hxx>
28 #include <boost/lexical_cast.hpp>
31 using std::stringstream;
33 namespace flightgear {
41 coordinates: [lon,lat,alt],..]
49 static const char * errorPage =
50 "<html><head><title>Flight History</title></head><body>"
51 "<h1>Flight History</h1>"
54 "<li><a href='track.json'>JSON</a></li>"
55 "<li><a href='track.kml'>KML</a></li>"
59 static string FlightHistoryToJson(const SGGeodVec & history, size_t last_seen ) {
60 cJSON * feature = cJSON_CreateObject();
61 cJSON_AddItemToObject(feature, "type", cJSON_CreateString("Feature"));
63 cJSON * lineString = cJSON_CreateObject();
64 cJSON_AddItemToObject(feature, "geometry", lineString );
66 cJSON * properties = cJSON_CreateObject();
67 cJSON_AddItemToObject(feature, "properties", properties );
68 cJSON_AddItemToObject(properties, "type", cJSON_CreateString("FlightHistory"));
69 cJSON_AddItemToObject(properties, "last", cJSON_CreateNumber(last_seen));
71 cJSON_AddItemToObject(lineString, "type", cJSON_CreateString("LineString"));
72 cJSON * coordinates = cJSON_CreateArray();
73 cJSON_AddItemToObject(lineString, "coordinates", coordinates);
74 for (SGGeodVec::const_iterator it = history.begin(); it != history.end();
76 cJSON * coordinate = cJSON_CreateArray();
77 cJSON_AddItemToArray(coordinates, coordinate);
79 cJSON_AddItemToArray(coordinate, cJSON_CreateNumber(it->getLongitudeDeg()));
80 cJSON_AddItemToArray(coordinate, cJSON_CreateNumber(it->getLatitudeDeg()));
81 cJSON_AddItemToArray(coordinate, cJSON_CreateNumber(it->getElevationM()));
85 char * jsonString = cJSON_PrintUnformatted(feature);
86 string reply(jsonString);
88 cJSON_Delete(lineString);
92 static string AutoUpdateResponse(const HTTPRequest & request,
93 const string & base, const string & interval) {
95 string url = "http://";
96 url.append(request.HeaderVariables.get("Host")).append(base);
98 std::string reply("<?xml version='1.0' encoding='UTF-8'?>");
99 DOMNode * kml = new DOMNode("kml");
100 kml->setAttribute("xmlns", "http://www.opengis.net/kml/2.2");
102 DOMNode * document = kml->addChild(new DOMNode("Document"));
104 DOMNode * networkLink = document->addChild(new DOMNode("NetworkLink"));
105 DOMNode * link = networkLink->addChild(new DOMNode("Link"));
106 link->addChild(new DOMNode("href"))->addChild(new DOMTextElement(url));
107 link->addChild(new DOMNode("refreshMode"))->addChild(
108 new DOMTextElement("onInterval"));
109 link->addChild(new DOMNode("refreshInterval"))->addChild(
110 new DOMTextElement(interval.empty() ? "10" : interval));
112 reply.append(kml->render());
117 static string FlightHistoryToKml(const SGGeodVec & history,
118 const HTTPRequest & request) {
119 string interval = request.RequestVariables.get("interval");
120 if (false == interval.empty()) {
121 return AutoUpdateResponse(request, "/flighthistory/track.kml", interval);
124 std::string reply("<?xml version='1.0' encoding='UTF-8'?>");
125 DOMNode * kml = new DOMNode("kml");
126 kml->setAttribute("xmlns", "http://www.opengis.net/kml/2.2");
128 DOMNode * document = kml->addChild(new DOMNode("Document"));
130 document->addChild(new DOMNode("name"))->addChild(
131 new DOMTextElement("FlightGear"));
132 document->addChild(new DOMNode("description"))->addChild(
133 new DOMTextElement("FlightGear Flight History"));
135 DOMNode * style = document->addChild(new DOMNode("Style"))->setAttribute(
136 "id", "flight-history");
137 DOMNode * lineStyle = style->addChild(new DOMNode("LineStyle"));
139 string lineColor = request.RequestVariables.get("LineColor");
140 string lineWidth = request.RequestVariables.get("LineWidth");
141 string polyColor = request.RequestVariables.get("PolyColor");
143 lineStyle->addChild(new DOMNode("color"))->addChild(
144 new DOMTextElement(lineColor.empty() ? "427ebfff" : lineColor));
145 lineStyle->addChild(new DOMNode("width"))->addChild(
146 new DOMTextElement(lineWidth.empty() ? "4" : lineWidth));
148 lineStyle = style->addChild(new DOMNode("PolyStyle"));
149 lineStyle->addChild(new DOMNode("color"))->addChild(
150 new DOMTextElement(polyColor.empty() ? "fbfc4600" : polyColor));
152 DOMNode * placemark = document->addChild(new DOMNode("Placemark"));
153 placemark->addChild(new DOMNode("name"))->addChild(
154 new DOMTextElement("Flight Path"));
155 placemark->addChild(new DOMNode("styleUrl"))->addChild(
156 new DOMTextElement("#flight-history"));
158 DOMNode * linestring = placemark->addChild(new DOMNode("LineString"));
159 linestring->addChild(new DOMNode("extrude"))->addChild(
160 new DOMTextElement("1"));
161 linestring->addChild(new DOMNode("tessalate"))->addChild(
162 new DOMTextElement("1"));
163 linestring->addChild(new DOMNode("altitudeMode"))->addChild(
164 new DOMTextElement("absolute"));
168 for (SGGeodVec::const_iterator it = history.begin(); it != history.end();
170 ss << (*it).getLongitudeDeg() << "," << (*it).getLatitudeDeg() << ","
171 << it->getElevationM() << " ";
174 linestring->addChild(new DOMNode("coordinates"))->addChild(
175 new DOMTextElement(ss.str()));
177 reply.append(kml->render());
182 static bool GetJsonDouble(cJSON * json, const char * item, double & out) {
183 cJSON * cj = cJSON_GetObjectItem(json, item);
187 if (cj->type != cJSON_Number)
190 out = cj->valuedouble;
195 static bool GetJsonBool(cJSON * json, const char * item, bool & out) {
196 cJSON * cj = cJSON_GetObjectItem(json, item);
200 if (cj->type == cJSON_True) {
204 if (cj->type == cJSON_False) {
212 bool FlightHistoryUriHandler::handleRequest(const HTTPRequest & request,
213 HTTPResponse & response, Connection * connection) {
214 response.Header["Access-Control-Allow-Origin"] = "*";
215 response.Header["Access-Control-Allow-Methods"] = "OPTIONS, GET, POST";
216 response.Header["Access-Control-Allow-Headers"] =
217 "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token";
219 if (request.Method == "OPTIONS") {
220 return true; // OPTIONS only needs the headers
223 FGFlightHistory* history =
224 static_cast<FGFlightHistory*>(globals->get_subsystem("history"));
226 double minEdgeLengthM = 50;
227 string requestPath = request.Uri.substr(getUri().length());
229 if (request.Method == "GET") {
230 } else if (request.Method == "POST") {
233 * sampleIntervalSec: (number),
234 * maxMemoryUseBytes: (number),
235 * clearOnTakeoff: (bool),
239 cJSON * json = cJSON_Parse(request.Content.c_str());
243 bool doReinit = false;
244 if (GetJsonDouble(json, "sampleIntervalSec", d)) {
245 fgSetDouble("/sim/history/sample-interval-sec", d);
248 if (GetJsonDouble(json, "maxMemoryUseBytes", d)) {
249 fgSetDouble("/sim/history/max-memory-use-bytes", d);
253 if (GetJsonBool(json, "clearOnTakeoff", b)) {
254 fgSetBool("/sim/history/clear-on-takeoff", b);
257 if (GetJsonBool(json, "enabled", b)) {
258 fgSetBool("/sim/history/enabled", b);
268 response.Content = "{}";
272 SG_LOG(SG_NETWORK, SG_INFO,
273 "PkgUriHandler: invalid request method '" << request.Method << "'");
274 response.Header["Allow"] = "OPTIONS, GET, POST";
275 response.StatusCode = 405;
276 response.Content = "{}";
280 if (requestPath == "track.kml") {
281 response.Header["Content-Type"] =
282 "application/vnd.google-earth.kml+xml; charset=UTF-8";
284 response.Content = FlightHistoryToKml(
285 history->pathForHistory(minEdgeLengthM), request);
287 } else if (requestPath == "track.json") {
290 count = boost::lexical_cast<size_t>(request.RequestVariables.get("count"));
296 last = boost::lexical_cast<size_t>(request.RequestVariables.get("last"));
301 response.Header["Content-Type"] = "application/json; charset=UTF-8";
302 PagedPathForHistory_ptr h = history->pagedPathForHistory( count, last );
303 response.Content = FlightHistoryToJson( h->path, h->last_seen );
306 response.Header["Content-Type"] = "text/html";
307 response.Content = errorPage;
314 } // namespace flightgear