]> git.mxchange.org Git - flightgear.git/blob - src/Network/http/NavdbUriHandler.cxx
Interim windows build fix
[flightgear.git] / src / Network / http / NavdbUriHandler.cxx
1 // NavdbUriHandler.cxx -- Access the nav database
2 //
3 // Written by Torsten Dreyer, started April 2014.
4 //
5 // Copyright (C) 2014  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 "NavdbUriHandler.hxx"
22 #include <simgear/debug/logstream.hxx>
23 #include <Navaids/navrecord.hxx>
24 #include <Airports/airport.hxx>
25 #include <ATC/CommStation.hxx>
26 #include <3rdparty/cjson/cJSON.h>
27 #include <boost/lexical_cast.hpp>
28
29 using std::string;
30
31 namespace flightgear {
32 namespace http {
33
34 static cJSON * createPositionArray(double x, double y, double z)
35 {
36   cJSON * p = cJSON_CreateArray();
37   cJSON_AddItemToArray(p, cJSON_CreateNumber(x));
38   cJSON_AddItemToArray(p, cJSON_CreateNumber(y));
39   cJSON_AddItemToArray(p, cJSON_CreateNumber(z));
40   return p;
41 }
42
43 static cJSON * createPositionArray(double x, double y)
44 {
45   cJSON * p = cJSON_CreateArray();
46   cJSON_AddItemToArray(p, cJSON_CreateNumber(x));
47   cJSON_AddItemToArray(p, cJSON_CreateNumber(y));
48   return p;
49 }
50
51 static cJSON * createLOCGeometry(FGNavRecord * navRecord)
52 {
53   assert( navRecord != NULL );
54
55   cJSON * geometry = cJSON_CreateObject();
56   int range = navRecord->get_range();
57
58   double width = navRecord->localizerWidth();
59   double course = navRecord->get_multiuse();
60
61   double px[4];
62   double py[4];
63
64   px[0] = navRecord->longitude();
65   py[0] = navRecord->latitude();
66
67   for (int i = -1; i <= +1; i++) {
68     double c = SGMiscd::normalizeAngle((course + 180 + i * width / 2) * SG_DEGREES_TO_RADIANS);
69     SGGeoc geoc = SGGeoc::fromGeod(navRecord->geod());
70     SGGeod p2 = SGGeod::fromGeoc(geoc.advanceRadM(c, range * SG_NM_TO_METER));
71     px[i + 2] = p2.getLongitudeDeg();
72     py[i + 2] = p2.getLatitudeDeg();
73   }
74   // Add three lines: centerline, left and right edge
75   cJSON_AddItemToObject(geometry, "type", cJSON_CreateString("MultiLineString"));
76   cJSON * coordinates = cJSON_CreateArray();
77   cJSON_AddItemToObject(geometry, "coordinates", coordinates);
78   for (int i = 1; i < 4; i++) {
79     cJSON * line = cJSON_CreateArray();
80     cJSON_AddItemToArray(coordinates, line);
81     cJSON_AddItemToArray(line, createPositionArray(px[0], py[0]));
82     cJSON_AddItemToArray(line, createPositionArray(px[i], py[i]));
83   }
84
85   return geometry;
86 }
87
88 static cJSON * createPointGeometry(FGPositioned * positioned )
89 {
90   cJSON * geometry = cJSON_CreateObject();
91   cJSON_AddItemToObject(geometry, "type", cJSON_CreateString("Point"));
92   cJSON_AddItemToObject(geometry, "coordinates", 
93     createPositionArray(positioned ->longitude(), positioned->latitude(), positioned->elevationM()));
94   return geometry;
95 }
96
97 static cJSON * createRunwayPolygon( FGRunwayBase * rwy )
98 {
99   cJSON * polygon = cJSON_CreateObject(); 
100   cJSON_AddItemToObject(polygon, "type", cJSON_CreateString("Polygon"));
101   cJSON * coordinates = cJSON_CreateArray();
102   cJSON_AddItemToObject(polygon, "coordinates", coordinates );
103   cJSON * linearRing = cJSON_CreateArray();
104   cJSON_AddItemToArray( coordinates, linearRing );
105
106   // compute the four corners of the runway
107   SGGeod p1 = rwy->pointOffCenterline( 0.0, rwy->widthM()/2 );
108   SGGeod p2 = rwy->pointOffCenterline( 0.0, -rwy->widthM()/2 );
109   SGGeod p3 = rwy->pointOffCenterline( rwy->lengthM(), -rwy->widthM()/2 );
110   SGGeod p4 = rwy->pointOffCenterline( rwy->lengthM(), rwy->widthM()/2 );
111   cJSON_AddItemToArray( linearRing, createPositionArray(p1.getLongitudeDeg(), p1.getLatitudeDeg()) );
112   cJSON_AddItemToArray( linearRing, createPositionArray(p2.getLongitudeDeg(), p2.getLatitudeDeg()) );
113   cJSON_AddItemToArray( linearRing, createPositionArray(p3.getLongitudeDeg(), p3.getLatitudeDeg()) );
114   cJSON_AddItemToArray( linearRing, createPositionArray(p4.getLongitudeDeg(), p4.getLatitudeDeg()) );
115   // close the ring
116   cJSON_AddItemToArray( linearRing, createPositionArray(p1.getLongitudeDeg(), p1.getLatitudeDeg()) );
117   return polygon;
118 }
119
120 static cJSON * createAirportGeometry(FGAirport * airport )
121 {
122   assert( airport != NULL );
123   FGRunwayList runways = airport->getRunwaysWithoutReciprocals();
124
125   if( runways.empty() ) {
126     // no runways? Create a Point geometry
127     return createPointGeometry( airport );
128   }
129
130   cJSON * geometry = cJSON_CreateObject();
131
132   // if there are runways, create a geometry collection
133   cJSON_AddItemToObject(geometry, "type", cJSON_CreateString("GeometryCollection"));
134   cJSON * geometryCollection = cJSON_CreateArray();
135   cJSON_AddItemToObject(geometry, "geometries", geometryCollection);
136
137   // the first item is the aerodrome reference point
138   cJSON_AddItemToArray( geometryCollection, createPointGeometry(airport) );
139
140   // followed by the runway polygons
141   for( FGRunwayList::iterator it = runways.begin(); it != runways.end(); ++it ) {
142     cJSON_AddItemToArray( geometryCollection, createRunwayPolygon(*it) );
143   }
144
145   FGTaxiwayList taxiways = airport->getTaxiways();
146   // followed by the taxiway polygons
147   for( FGTaxiwayList::iterator it = taxiways.begin(); it != taxiways.end(); ++it ) {
148     cJSON_AddItemToArray( geometryCollection, createRunwayPolygon(*it) );
149   }
150
151   return geometry;
152 }
153
154 static cJSON * createGeometryFor(FGPositioned * positioned)
155 {
156   switch( positioned->type() ) {
157     case FGPositioned::LOC:
158     case FGPositioned::ILS:
159       return createLOCGeometry( dynamic_cast<FGNavRecord*>(positioned) );
160
161     case FGPositioned::AIRPORT:
162       return createAirportGeometry( dynamic_cast<FGAirport*>(positioned) );
163
164     default:
165       return createPointGeometry( positioned );
166   }
167 }
168
169 static void addAirportProperties(cJSON * json, FGAirport * airport )
170 {
171   if( NULL == airport ) return;
172   double longestRunwayLength = 0.0;
173   double longestRunwayHeading = 0.0;
174   const char * longestRunwaySurface = "";
175
176   cJSON_AddItemToObject(json, "name", cJSON_CreateString(airport->getName().c_str()));
177   cJSON * runwaysJson = cJSON_CreateArray();
178   cJSON_AddItemToObject(json, "runways", runwaysJson);
179
180   FGRunwayList runways = airport->getRunways();
181   for( FGRunwayList::iterator it = runways.begin(); it != runways.end(); ++it ) {
182     FGRunway * runway = *it;
183     cJSON * runwayJson = cJSON_CreateObject();
184     cJSON_AddItemToArray( runwaysJson, runwayJson );
185     cJSON_AddItemToObject(runwayJson, "id", cJSON_CreateString(runway->ident().c_str()));
186     cJSON_AddItemToObject(runwayJson, "length_m", cJSON_CreateNumber(runway->lengthM()));
187     cJSON_AddItemToObject(runwayJson, "width_m", cJSON_CreateNumber(runway->widthM()));
188     cJSON_AddItemToObject(runwayJson, "surface", cJSON_CreateString(runway->surfaceName()));
189     cJSON_AddItemToObject(runwayJson, "heading_deg", cJSON_CreateNumber(runway->headingDeg()));
190     double d = runway->displacedThresholdM();
191     if( d > .0 )
192       cJSON_AddItemToObject(runwayJson, "dispacedThreshold_m", cJSON_CreateNumber(d));
193
194     d = runway->stopwayM();
195     if( d > .0 )
196       cJSON_AddItemToObject(runwayJson, "stopway_m", cJSON_CreateNumber(d));
197
198     if( runway->lengthM() > longestRunwayLength ) {
199       longestRunwayLength = runway->lengthM();
200       longestRunwayHeading = runway->headingDeg();
201       longestRunwaySurface = runway->surfaceName();
202     }
203   }
204   cJSON_AddItemToObject(json, "longestRwyLength_m", cJSON_CreateNumber(longestRunwayLength));
205   cJSON_AddItemToObject(json, "longestRwyHeading_deg", cJSON_CreateNumber(longestRunwayHeading));
206   cJSON_AddItemToObject(json, "longestRwySurface", cJSON_CreateString(longestRunwaySurface));
207   if( airport->getMetar() ) {
208     cJSON_AddItemToObject(json, "metar", cJSON_CreateTrue());
209   }
210
211   cJSON * commsJson = cJSON_CreateArray();
212   cJSON_AddItemToObject(json, "comm", commsJson);
213   flightgear::CommStationList comms = airport->commStations();
214   for( flightgear::CommStationList::iterator it = comms.begin(); it != comms.end(); ++it ) {
215     flightgear::CommStation * comm = *it;
216     cJSON * commJson = cJSON_CreateObject();
217     cJSON_AddItemToArray( commsJson, commJson );
218     cJSON_AddItemToObject(commJson, "id", cJSON_CreateString(comm->ident().c_str()));
219     cJSON_AddItemToObject(commJson, "mhz", cJSON_CreateNumber(comm->freqMHz()));
220   }
221 }
222
223 static void addNAVProperties(cJSON * json, FGNavRecord * navRecord )
224 {
225   if( NULL == navRecord ) return;
226   cJSON_AddItemToObject(json, "range_nm", cJSON_CreateNumber(navRecord->get_range()));
227   cJSON_AddItemToObject(json, "frequency", cJSON_CreateNumber((double) navRecord->get_freq() / 100.0));
228   switch (navRecord->type()) {
229     case FGPositioned::ILS:
230     case FGPositioned::LOC:
231       cJSON_AddItemToObject(json, "localizer-course", cJSON_CreateNumber(navRecord->get_multiuse()));
232       break;
233
234     case FGPositioned::VOR:
235       cJSON_AddItemToObject(json, "variation", cJSON_CreateNumber(navRecord->get_multiuse()));
236       break;
237
238     default:
239       break;
240   }
241 }
242
243 static cJSON * createPropertiesFor(FGPositioned * positioned)
244 {
245   cJSON * properties = cJSON_CreateObject();
246
247   cJSON_AddItemToObject(properties, "name", cJSON_CreateString(positioned->name().c_str()));
248   // also add id to properties
249   cJSON_AddItemToObject(properties, "id", cJSON_CreateString(positioned->ident().c_str()));
250   cJSON_AddItemToObject(properties, "type", cJSON_CreateString(positioned->typeString()));
251   cJSON_AddItemToObject(properties, "elevation-m", cJSON_CreateNumber(positioned->elevationM()));
252   addNAVProperties( properties, dynamic_cast<FGNavRecord*>(positioned) );
253   addAirportProperties( properties, dynamic_cast<FGAirport*>(positioned) );
254   return properties;
255 }
256
257 static cJSON * createFeatureFor(FGPositioned * positioned)
258 {
259   cJSON * feature = cJSON_CreateObject();
260
261   // A GeoJSON object with the type "Feature" is a feature object.
262   cJSON_AddItemToObject(feature, "type", cJSON_CreateString("Feature"));
263
264   // A feature object must have a member with the name "geometry".
265   // The value of the geometry member is a geometry object as defined above or a JSON null value.
266   cJSON_AddItemToObject(feature, "geometry", createGeometryFor(positioned));
267
268   // A feature object must have a member with the name "properties".
269   // The value of the properties member is an object (any JSON object or a JSON null value).
270   cJSON_AddItemToObject(feature, "properties", createPropertiesFor(positioned));
271
272   // If a feature has a commonly used identifier, that identifier should be included
273   // as a member of the feature object with the name "id".
274   cJSON_AddItemToObject(feature, "id", cJSON_CreateString(positioned->ident().c_str()));
275
276   return feature;
277 }
278
279 bool NavdbUriHandler::handleRequest(const HTTPRequest & request, HTTPResponse & response, Connection * connection)
280 {
281
282   response.Header["Content-Type"] = "application/json; charset=UTF-8";
283   response.Header["Access-Control-Allow-Origin"] = "*";
284   response.Header["Access-Control-Allow-Methods"] = "OPTIONS, GET";
285   response.Header["Access-Control-Allow-Headers"] = "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token";
286
287   if( request.Method == "OPTIONS" ){
288       return true; // OPTIONS only needs the headers
289   }
290
291   if( request.Method != "GET" ){
292     response.Header["Allow"] = "OPTIONS, GET";
293     response.StatusCode = 405;
294     response.Content = "{}";
295     return true; 
296   }
297
298   bool indent = request.RequestVariables.get("i") == "y";
299
300   string query = request.RequestVariables.get("q");
301   FGPositionedList result;
302
303   if (query == "findWithinRange") {
304     // ?q=findWithinRange&lat=53.5&lon=10.0&range=100&type=vor,ils
305
306     double lat, lon, range = -1;
307     try {
308       lat = boost::lexical_cast<double>(request.RequestVariables.get("lat"));
309       lon = boost::lexical_cast<double>(request.RequestVariables.get("lon"));
310       range = boost::lexical_cast<double>(request.RequestVariables.get("range"));
311     }
312     catch (...) {
313       goto fail;
314     }
315
316     if (range <= 1.0) goto fail;
317     // In remembrance of a famous bug
318
319     SGGeod pos = SGGeod::fromDeg(lon, lat);
320     FGPositioned::TypeFilter filter;
321     try {
322       filter = FGPositioned::TypeFilter::fromString(request.RequestVariables.get("type"));
323     }
324     catch (...) {
325       goto fail;
326     }
327
328     result = FGPositioned::findWithinRange(pos, range, &filter);
329   } else if (query == "airports") {
330     cJSON * json = cJSON_CreateArray();
331     for( char ** airports = FGAirport::searchNamesAndIdents(""); *airports; airports++ ) {
332       cJSON_AddItemToArray(json, cJSON_CreateString(*airports));
333     }
334     char * jsonString = indent ? cJSON_Print(json) : cJSON_PrintUnformatted(json);
335     cJSON_Delete(json);
336     response.Content = jsonString;
337     free(jsonString);
338     return true;
339
340   } else if (query == "airport") {
341     FGAirportRef airport = FGAirport::findByIdent(request.RequestVariables.get("id"));
342     if( airport.valid() )
343       result.push_back( airport );
344   } else {
345     goto fail;
346   }
347
348  { // create some GeoJSON from the result list
349     // GeoJSON always consists of a single object.
350     cJSON * geoJSON = cJSON_CreateObject();
351
352     // The GeoJSON object must have a member with the name "type".
353     // This member's value is a string that determines the type of the GeoJSON object.
354     cJSON_AddItemToObject(geoJSON, "type", cJSON_CreateString("FeatureCollection"));
355
356     // we send zero to many features - let's make it a FeatureCollection
357     // A GeoJSON object with the type "FeatureCollection" is a feature collection object.
358     // An object of type "FeatureCollection" must have a member with the name "features".
359     // The value corresponding to "features" is an array.
360     cJSON * featureCollection = cJSON_CreateArray();
361     cJSON_AddItemToObject(geoJSON, "features", featureCollection);
362
363     for (FGPositionedList::iterator it = result.begin(); it != result.end(); ++it) {
364       // Each element in the array is a feature object as defined above.
365       cJSON_AddItemToArray(featureCollection, createFeatureFor(*it));
366     }
367
368     char * jsonString = indent ? cJSON_Print(geoJSON) : cJSON_PrintUnformatted(geoJSON);
369     cJSON_Delete(geoJSON);
370     response.Content = jsonString;
371     free(jsonString);
372   }
373
374   return true;
375
376   fail: response.StatusCode = 400;
377   response.Content = "{ 'error': 'bad request' }";
378   return true;
379 }
380
381 } // namespace http
382 } // namespace flightgear
383