]> git.mxchange.org Git - flightgear.git/blob - src/Network/http/FlightHistoryUriHandler.cxx
Interim windows build fix
[flightgear.git] / src / Network / http / FlightHistoryUriHandler.cxx
1 // FlightHistoryUriHandler.cxx -- FlightHistory service
2 //
3 // Written by Torsten Dreyer, started February 2015.
4 //
5 // Copyright (C) 2015  Torsten Dreyer
6 //
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.
11 //
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.
16 //
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.
20
21 #include "FlightHistoryUriHandler.hxx"
22 #include "SimpleDOM.hxx"
23 #include <3rdparty/cjson/cJSON.h>
24
25 #include <Aircraft/FlightHistory.hxx>
26 #include <Main/fg_props.hxx>
27 #include <sstream>
28 #include <boost/lexical_cast.hpp>
29
30 using std::string;
31 using std::stringstream;
32
33 namespace flightgear {
34 namespace http {
35
36 /*
37 {
38   type: "Feature",
39   geometry: {
40     type: "LineString",
41     coordinates: [lon,lat,alt],..]
42   },
43   properties: {
44     type: "FlightHistory"
45   },
46 }
47 */
48
49 static const char * errorPage =
50                 "<html><head><title>Flight History</title></head><body>"
51                 "<h1>Flight History</h1>"
52                 "Supported formats:"
53                 "<ul>"
54                 "<li><a href='track.json'>JSON</a></li>"
55                 "<li><a href='track.kml'>KML</a></li>"
56                 "</ul>"
57                 "</body></html>";
58
59 static string FlightHistoryToJson(const SGGeodVec & history, size_t last_seen ) {
60         cJSON * feature = cJSON_CreateObject();
61         cJSON_AddItemToObject(feature, "type", cJSON_CreateString("Feature"));
62
63         cJSON * lineString = cJSON_CreateObject();
64         cJSON_AddItemToObject(feature, "geometry", lineString );
65
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));
70
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();
75                         ++it) {
76                 cJSON * coordinate = cJSON_CreateArray();
77                 cJSON_AddItemToArray(coordinates, coordinate);
78
79                 cJSON_AddItemToArray(coordinate, cJSON_CreateNumber(it->getLongitudeDeg()));
80                 cJSON_AddItemToArray(coordinate, cJSON_CreateNumber(it->getLatitudeDeg()));
81                 cJSON_AddItemToArray(coordinate, cJSON_CreateNumber(it->getElevationM()));
82
83         }
84
85         char * jsonString = cJSON_PrintUnformatted(feature);
86         string reply(jsonString);
87         free(jsonString);
88         cJSON_Delete(lineString);
89         return reply;
90 }
91
92 static string AutoUpdateResponse(const HTTPRequest & request,
93                 const string & base, const string & interval) {
94
95         string url = "http://";
96         url.append(request.HeaderVariables.get("Host")).append(base);
97
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");
101
102         DOMNode * document = kml->addChild(new DOMNode("Document"));
103
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));
111
112         reply.append(kml->render());
113         delete kml;
114         return reply;
115 }
116
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);
122         }
123
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");
127
128         DOMNode * document = kml->addChild(new DOMNode("Document"));
129
130         document->addChild(new DOMNode("name"))->addChild(
131                         new DOMTextElement("FlightGear"));
132         document->addChild(new DOMNode("description"))->addChild(
133                         new DOMTextElement("FlightGear Flight History"));
134
135         DOMNode * style = document->addChild(new DOMNode("Style"))->setAttribute(
136                         "id", "flight-history");
137         DOMNode * lineStyle = style->addChild(new DOMNode("LineStyle"));
138
139         string lineColor = request.RequestVariables.get("LineColor");
140         string lineWidth = request.RequestVariables.get("LineWidth");
141         string polyColor = request.RequestVariables.get("PolyColor");
142
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));
147
148         lineStyle = style->addChild(new DOMNode("PolyStyle"));
149         lineStyle->addChild(new DOMNode("color"))->addChild(
150                         new DOMTextElement(polyColor.empty() ? "fbfc4600" : polyColor));
151
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"));
157
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"));
165
166         stringstream ss;
167
168         for (SGGeodVec::const_iterator it = history.begin(); it != history.end();
169                         ++it) {
170                 ss << (*it).getLongitudeDeg() << "," << (*it).getLatitudeDeg() << ","
171                                 << it->getElevationM() << " ";
172         }
173
174         linestring->addChild(new DOMNode("coordinates"))->addChild(
175                         new DOMTextElement(ss.str()));
176
177         reply.append(kml->render());
178         delete kml;
179         return reply;
180 }
181
182 static bool GetJsonDouble(cJSON * json, const char * item, double & out) {
183         cJSON * cj = cJSON_GetObjectItem(json, item);
184         if (NULL == cj)
185                 return false;
186
187         if (cj->type != cJSON_Number)
188                 return false;
189
190         out = cj->valuedouble;
191
192         return true;
193 }
194
195 static bool GetJsonBool(cJSON * json, const char * item, bool & out) {
196         cJSON * cj = cJSON_GetObjectItem(json, item);
197         if (NULL == cj)
198                 return false;
199
200         if (cj->type == cJSON_True) {
201                 out = true;
202                 return true;
203         }
204         if (cj->type == cJSON_False) {
205                 out = true;
206                 return true;
207
208         }
209         return false;
210 }
211
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";
218
219         if (request.Method == "OPTIONS") {
220                 return true; // OPTIONS only needs the headers
221         }
222
223         FGFlightHistory* history =
224                         static_cast<FGFlightHistory*>(globals->get_subsystem("history"));
225
226         double minEdgeLengthM = 50;
227         string requestPath = request.Uri.substr(getUri().length());
228
229         if (request.Method == "GET") {
230         } else if (request.Method == "POST") {
231                 /*
232                  * {
233                  *   sampleIntervalSec: (number),
234                  *   maxMemoryUseBytes: (number),
235                  *   clearOnTakeoff: (bool),
236                  *   enabled: (bool),
237                  * }
238                  */
239                 cJSON * json = cJSON_Parse(request.Content.c_str());
240                 if ( NULL != json) {
241                         double d = .0;
242                         bool b = false;
243                         bool doReinit = false;
244                         if (GetJsonDouble(json, "sampleIntervalSec", d)) {
245                                 fgSetDouble("/sim/history/sample-interval-sec", d);
246                                 doReinit = true;
247                         }
248                         if (GetJsonDouble(json, "maxMemoryUseBytes", d)) {
249                                 fgSetDouble("/sim/history/max-memory-use-bytes", d);
250                                 doReinit = true;
251                         }
252
253                         if (GetJsonBool(json, "clearOnTakeoff", b)) {
254                                 fgSetBool("/sim/history/clear-on-takeoff", b);
255                                 doReinit = true;
256                         }
257                         if (GetJsonBool(json, "enabled", b)) {
258                                 fgSetBool("/sim/history/enabled", b);
259                         }
260
261                         if (doReinit) {
262                                 history->reinit();
263                         }
264
265                         cJSON_Delete(json);
266                 }
267
268                 response.Content = "{}";
269                 return true;
270
271         } else {
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 = "{}";
277                 return true;
278         }
279
280         if (requestPath == "track.kml") {
281                 response.Header["Content-Type"] =
282                                 "application/vnd.google-earth.kml+xml; charset=UTF-8";
283
284                 response.Content = FlightHistoryToKml(
285                                 history->pathForHistory(minEdgeLengthM), request);
286
287         } else if (requestPath == "track.json") {
288                 size_t count = -1;
289                 try {
290                   count = boost::lexical_cast<size_t>(request.RequestVariables.get("count"));
291                 }
292                 catch( ... ) {
293                 }
294                 size_t last = 0;
295                 try {
296                         last = boost::lexical_cast<size_t>(request.RequestVariables.get("last"));
297                 }
298                 catch( ... ) {
299                 }
300
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 );
304
305         } else {
306                 response.Header["Content-Type"] = "text/html";
307                 response.Content = errorPage;
308         }
309
310         return true;
311 }
312
313 } // namespace http
314 } // namespace flightgear
315