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