]> git.mxchange.org Git - flightgear.git/blob - src/Airports/apt_loader.cxx
apt.dat parser: various little improvements
[flightgear.git] / src / Airports / apt_loader.cxx
1 // apt_loader.cxx -- a front end loader of the apt.dat file.  This loader
2 //                   populates the runway and basic classes.
3 //
4 // Written by Curtis Olson, started August 2000.
5 //
6 // Copyright (C) 2000  Curtis L. Olson  - http://www.flightgear.org/~curt
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22 // $Id$
23
24
25 #ifdef HAVE_CONFIG_H
26 #  include <config.h>
27 #endif
28
29 #include "apt_loader.hxx"
30
31 #include <simgear/compiler.h>
32
33 #include <stdlib.h> // atof(), atoi()
34 #include <string.h> // memchr()
35 #include <ctype.h> // isspace()
36
37 #include <simgear/constants.h>
38 #include <simgear/debug/logstream.hxx>
39 #include <simgear/misc/sgstream.hxx>
40 #include <simgear/misc/strutils.hxx>
41 #include <simgear/structure/exception.hxx>
42 #include <simgear/misc/sg_path.hxx>
43
44 #include <string>
45
46 #include "airport.hxx"
47 #include "runways.hxx"
48 #include "pavement.hxx"
49 #include <Navaids/NavDataCache.hxx>
50 #include <ATC/CommStation.hxx>
51
52 #include <iostream>
53 #include <sstream>              // std::istringstream
54
55 using namespace std;
56
57 typedef SGSharedPtr<FGPavement> FGPavementPtr;
58
59 static FGPositioned::Type fptypeFromRobinType(int aType)
60 {
61   switch (aType) {
62   case 1: return FGPositioned::AIRPORT;
63   case 16: return FGPositioned::SEAPORT;
64   case 17: return FGPositioned::HELIPORT;
65   default:
66     SG_LOG(SG_GENERAL, SG_ALERT, "unsupported type:" << aType);
67     throw sg_range_exception("Unsupported airport type", "fptypeFromRobinType");
68   }
69 }
70
71 // generated by 'wc -l' on the uncompressed file
72 const unsigned int LINES_IN_APT_DAT = 2465648;
73
74 namespace flightgear
75 {
76   
77 class APTLoader
78 {
79 public:
80
81   APTLoader()
82   :  last_apt_id(""),
83      last_apt_elev(0.0),
84      last_apt_info("")
85   {
86     currentAirportID = 0;
87     cache = NavDataCache::instance();
88   }
89
90   void parseAPT(const SGPath &aptdb_file)
91   {
92     std::string apt_dat = aptdb_file.str();
93     sg_gzifstream in(apt_dat);
94
95     if ( !in.is_open() ) {
96       SG_LOG( SG_GENERAL, SG_ALERT, "Cannot open file: " << apt_dat );
97       throw sg_io_exception("cannot open apt.dat file", apt_dat.c_str());
98     }
99
100     string line;
101
102     unsigned int line_id = 0;
103     unsigned int line_num = 0;
104
105     // Read the apt.dat header (two lines)
106     while ( line_num < 2 && std::getline(in, line) ) {
107       // 'line' may end with an \r character (tested on Linux, only \n was
108       // stripped: std::getline() only discards the _native_ line terminator)
109       line_num++;
110
111       if ( line_num == 1 ) {
112         std::string stripped_line = simgear::strutils::strip(line);
113         // First line indicates IBM ("I") or Macintosh ("A") line endings.
114         if ( stripped_line != "I" && stripped_line != "A" ) {
115           std::string pb = "invalid first line (neither 'I' nor 'A')";
116           SG_LOG( SG_GENERAL, SG_ALERT, apt_dat << ": " << pb);
117           throw sg_format_exception("cannot parse apt.dat file: " + pb,
118                                     apt_dat);
119         }
120       } else {     // second line of the file
121         std::istringstream s(line);
122         int apt_dat_format_version;
123         s >> apt_dat_format_version;
124         SG_LOG( SG_GENERAL, SG_INFO,
125                 "apt.dat format version: " << apt_dat_format_version );
126       }
127     } // end of the apt.dat header
128
129     throwExceptionIfStreamError(in, "apt.dat", apt_dat);
130
131     while ( std::getline(in, line) ) {
132       line_num++;
133
134       if ( isBlankOrCommentLine(line) )
135         continue;
136
137         if ((line_num % 100) == 0) {
138             // every 100 lines
139             unsigned int percent = (line_num * 100) / LINES_IN_APT_DAT;
140             cache->setRebuildPhaseProgress(NavDataCache::REBUILD_AIRPORTS, percent);
141         }
142
143       // Extract the first field into 'line_id'
144       line_id = atoi(line.c_str());
145
146       if ( line_id == 1 /* Airport */ ||
147                     line_id == 16 /* Seaplane base */ ||
148                     line_id == 17 /* Heliport */ ) {
149         parseAirportLine(simgear::strutils::split(line));
150       } else if ( line_id == 10 ) { // Runway v810
151         parseRunwayLine810(simgear::strutils::split(line));
152       } else if ( line_id == 100 ) { // Runway v850
153         parseRunwayLine850(simgear::strutils::split(line));
154       } else if ( line_id == 101 ) { // Water Runway v850
155         parseWaterRunwayLine850(simgear::strutils::split(line));
156       } else if ( line_id == 102 ) { // Helipad v850
157         parseHelipadLine850(simgear::strutils::split(line));
158       } else if ( line_id == 18 ) {
159             // beacon entry (ignore)
160       } else if ( line_id == 14 ) {
161         // control tower entry
162         vector<string> token(simgear::strutils::split(line));
163         
164         double lat = atof( token[1].c_str() );
165         double lon = atof( token[2].c_str() );
166         double elev = atof( token[3].c_str() );
167         tower = SGGeod::fromDegFt(lon, lat, elev + last_apt_elev);        
168         cache->insertTower(currentAirportID, tower);
169       } else if ( line_id == 19 ) {
170           // windsock entry (ignore)
171       } else if ( line_id == 20 ) {
172           // Taxiway sign (ignore)
173       } else if ( line_id == 21 ) {
174           // lighting objects (ignore)
175       } else if ( line_id == 15 ) {
176           // custom startup locations (ignore)
177       } else if ( line_id == 0 ) {
178           // ??
179       } else if ( line_id >= 50 && line_id <= 56) {
180         parseCommLine(line_id, simgear::strutils::split(line));
181       } else if ( line_id == 110 ) {
182         pavement = true;
183         parsePavementLine850(simgear::strutils::split(line, 0, 4));
184       } else if ( line_id >= 111 && line_id <= 114 ) {
185         if ( pavement )
186           parsePavementNodeLine850(line_id, simgear::strutils::split(line));
187       } else if ( line_id >= 115 && line_id <= 116 ) {
188           // other pavement nodes (ignore)
189       } else if ( line_id == 120 ) {
190         pavement = false;
191       } else if ( line_id == 130 ) {
192         pavement = false;
193       } else if ( line_id >= 1000 ) {
194           // airport traffic flow (ignore)
195       } else if ( line_id == 99 ) {
196           SG_LOG( SG_GENERAL, SG_DEBUG, "End of file reached" );
197       } else {
198           SG_LOG( SG_GENERAL, SG_ALERT, 
199                   "Unknown line(#" << line_num << ") in apt.dat file: " << line );
200           throw sg_format_exception("malformed line in apt.dat:", line);
201       }
202     }
203
204     throwExceptionIfStreamError(in, "apt.dat", apt_dat);
205     finishAirport();
206   }
207   
208 private:
209   vector<string> token;
210   double rwy_lat_accum;
211   double rwy_lon_accum;
212   double last_rwy_heading;
213   int rwy_count;
214   string last_apt_id;
215   double last_apt_elev;
216   SGGeod tower;
217   string last_apt_info;
218   string pavement_ident;
219   bool pavement;
220   
221   //vector<FGRunwayPtr> runways;
222   //vector<FGTaxiwayPtr> taxiways;
223   vector<FGPavementPtr> pavements;
224   
225   NavDataCache* cache;
226   PositionedID currentAirportID;
227
228   // Tell whether an apt.dat line is blank or a comment line
229   bool isBlankOrCommentLine(const std::string& line)
230   {
231     size_t pos = line.find_first_not_of(" \t");
232     return ( pos == std::string::npos || line.find("##", pos) == pos );
233   }
234
235   void throwExceptionIfStreamError(const sg_gzifstream& input_stream,
236                                    const std::string& short_name,
237                                    const std::string& full_path)
238   {
239     if ( input_stream.bad() ) {
240       // strerror() isn't thread-safe, unfortunately...
241       SG_LOG( SG_GENERAL, SG_ALERT, "error while reading " << full_path );
242       throw sg_io_exception("error while reading " + short_name,
243                             full_path.c_str());
244     }
245   }
246
247   void finishAirport()
248   {
249     if (currentAirportID == 0) {
250       return;
251     }
252     
253     if (!rwy_count) {
254       currentAirportID = 0;
255       SG_LOG(SG_GENERAL, SG_ALERT, "ERROR: No runways for " << last_apt_id
256               << ", skipping." );
257       return;
258     }
259
260     double lat = rwy_lat_accum / (double)rwy_count;
261     double lon = rwy_lon_accum / (double)rwy_count;
262
263     SGGeod pos(SGGeod::fromDegFt(lon, lat, last_apt_elev));
264     cache->updatePosition(currentAirportID, pos);
265     
266     currentAirportID = 0;
267   }
268   
269   void parseAirportLine(const vector<string>& token)
270   {
271     const string& id(token[4]);
272     double elev = atof( token[1].c_str() );
273
274   // finish the previous airport
275     finishAirport();
276             
277     last_apt_elev = elev;
278
279     string name;
280     // build the name
281     for ( unsigned int i = 5; i < token.size() - 1; ++i ) {
282         name += token[i] + " ";
283     }
284     name += token[token.size() - 1];
285
286     // clear runway list for start of next airport
287     rwy_lon_accum = 0.0;
288     rwy_lat_accum = 0.0;
289     rwy_count = 0;
290     
291     int robinType = atoi(token[0].c_str());
292     currentAirportID = cache->insertAirport(fptypeFromRobinType(robinType), id, name);
293   }
294   
295   void parseRunwayLine810(const vector<string>& token)
296   {
297     double lat = atof( token[1].c_str() );
298     double lon = atof( token[2].c_str() );
299     rwy_lat_accum += lat;
300     rwy_lon_accum += lon;
301     rwy_count++;
302
303     const string& rwy_no(token[3]);
304
305     double heading = atof( token[4].c_str() );
306     double length = atoi( token[5].c_str() );
307     double width = atoi( token[8].c_str() );
308     length *= SG_FEET_TO_METER;
309     width *= SG_FEET_TO_METER;
310
311     // adjust lat / lon to the start of the runway/taxiway, not the middle
312     SGGeod pos_1 = SGGeodesy::direct( SGGeod::fromDegFt(lon, lat, last_apt_elev), heading, -length/2 );
313
314     last_rwy_heading = heading;
315
316     int surface_code = atoi( token[10].c_str() );
317
318     if (rwy_no[0] == 'x') {  // Taxiway
319       cache->insertRunway(FGPositioned::TAXIWAY, rwy_no, pos_1, currentAirportID,
320                           heading, length, width, 0.0, 0.0, surface_code);
321     } else if (rwy_no[0] == 'H') {  // Helipad
322       SGGeod pos(SGGeod::fromDegFt(lon, lat, last_apt_elev));
323       cache->insertRunway(FGPositioned::HELIPAD, rwy_no, pos, currentAirportID,
324                           heading, length, width, 0.0, 0.0, surface_code);
325     } else {
326       // (pair of) runways
327       string rwy_displ_threshold = token[6];
328       vector<string> displ
329           = simgear::strutils::split( rwy_displ_threshold, "." );
330       double displ_thresh1 = atof( displ[0].c_str() );
331       double displ_thresh2 = atof( displ[1].c_str() );
332       displ_thresh1 *= SG_FEET_TO_METER;
333       displ_thresh2 *= SG_FEET_TO_METER;
334
335       string rwy_stopway = token[7];
336       vector<string> stop
337           = simgear::strutils::split( rwy_stopway, "." );
338       double stopway1 = atof( stop[0].c_str() );
339       double stopway2 = atof( stop[1].c_str() );
340       stopway1 *= SG_FEET_TO_METER;
341       stopway2 *= SG_FEET_TO_METER;
342
343       SGGeod pos_2 = SGGeodesy::direct( pos_1, heading, length );
344
345       PositionedID rwy = cache->insertRunway(FGPositioned::RUNWAY, rwy_no, pos_1,
346                                              currentAirportID, heading, length,
347                                              width, displ_thresh1, stopway1,
348                                              surface_code);
349       
350       PositionedID reciprocal = cache->insertRunway(FGPositioned::RUNWAY,
351                                              FGRunway::reverseIdent(rwy_no), pos_2,
352                                              currentAirportID,
353                                              SGMiscd::normalizePeriodic(0, 360, heading + 180.0),
354                                              length, width, displ_thresh2, stopway2,
355                                              surface_code);
356
357       cache->setRunwayReciprocal(rwy, reciprocal);
358     }
359   }
360
361   void parseRunwayLine850(const vector<string>& token)
362   {
363     double width = atof( token[1].c_str() );
364     int surface_code = atoi( token[2].c_str() );
365
366     double lat_1 = atof( token[9].c_str() );
367     double lon_1 = atof( token[10].c_str() );
368     SGGeod pos_1(SGGeod::fromDegFt(lon_1, lat_1, 0.0));
369     rwy_lat_accum += lat_1;
370     rwy_lon_accum += lon_1;
371     rwy_count++;
372
373     double lat_2 = atof( token[18].c_str() );
374     double lon_2 = atof( token[19].c_str() );
375     SGGeod pos_2(SGGeod::fromDegFt(lon_2, lat_2, 0.0));
376     rwy_lat_accum += lat_2;
377     rwy_lon_accum += lon_2;
378     rwy_count++;
379
380     double length, heading_1, heading_2;
381     SGGeodesy::inverse( pos_1, pos_2, heading_1, heading_2, length );
382
383     last_rwy_heading = heading_1;
384
385     const string& rwy_no_1(token[8]);
386     const string& rwy_no_2(token[17]);
387     if ( rwy_no_1.empty() || rwy_no_2.empty() )
388         return;
389
390     double displ_thresh1 = atof( token[11].c_str() );
391     double displ_thresh2 = atof( token[20].c_str() );
392
393     double stopway1 = atof( token[12].c_str() );
394     double stopway2 = atof( token[21].c_str() );
395
396     PositionedID rwy = cache->insertRunway(FGPositioned::RUNWAY, rwy_no_1, pos_1,
397                                            currentAirportID, heading_1, length,
398                                            width, displ_thresh1, stopway1,
399                                            surface_code);
400     
401     PositionedID reciprocal = cache->insertRunway(FGPositioned::RUNWAY,
402                                                   rwy_no_2, pos_2,
403                                                   currentAirportID, heading_2, length,
404                                                   width, displ_thresh2, stopway2,
405                                                   surface_code);
406     
407     cache->setRunwayReciprocal(rwy, reciprocal);
408   }
409
410   void parseWaterRunwayLine850(const vector<string>& token)
411   {
412     double width = atof( token[1].c_str() );
413
414     double lat_1 = atof( token[4].c_str() );
415     double lon_1 = atof( token[5].c_str() );
416     SGGeod pos_1(SGGeod::fromDegFt(lon_1, lat_1, 0.0));
417     rwy_lat_accum += lat_1;
418     rwy_lon_accum += lon_1;
419     rwy_count++;
420
421     double lat_2 = atof( token[7].c_str() );
422     double lon_2 = atof( token[8].c_str() );
423     SGGeod pos_2(SGGeod::fromDegFt(lon_2, lat_2, 0.0));
424     rwy_lat_accum += lat_2;
425     rwy_lon_accum += lon_2;
426     rwy_count++;
427
428     double length, heading_1, heading_2;
429     SGGeodesy::inverse( pos_1, pos_2, heading_1, heading_2, length );
430
431     last_rwy_heading = heading_1;
432
433     const string& rwy_no_1(token[3]);
434     const string& rwy_no_2(token[6]);
435
436     PositionedID rwy = cache->insertRunway(FGPositioned::RUNWAY, rwy_no_1, pos_1,
437                                            currentAirportID, heading_1, length,
438                                            width, 0.0, 0.0, 13);
439     
440     PositionedID reciprocal = cache->insertRunway(FGPositioned::RUNWAY,
441                                                   rwy_no_2, pos_2,
442                                                   currentAirportID, heading_2, length,
443                                                   width, 0.0, 0.0, 13);
444     
445     cache->setRunwayReciprocal(rwy, reciprocal);
446   }
447
448   void parseHelipadLine850(const vector<string>& token)
449   {
450     double length = atof( token[5].c_str() );
451     double width = atof( token[6].c_str() );
452
453     double lat = atof( token[2].c_str() );
454     double lon = atof( token[3].c_str() );
455     SGGeod pos(SGGeod::fromDegFt(lon, lat, 0.0));
456     rwy_lat_accum += lat;
457     rwy_lon_accum += lon;
458     rwy_count++;
459
460     double heading = atof( token[4].c_str() );
461
462     last_rwy_heading = heading;
463
464     const string& rwy_no(token[1]);
465     int surface_code = atoi( token[7].c_str() );
466
467     cache->insertRunway(FGPositioned::HELIPAD, rwy_no, pos,
468                         currentAirportID, heading, length,
469                         width, 0.0, 0.0, surface_code);
470   }
471
472   void parsePavementLine850(const vector<string>& token)
473   {
474     if ( token.size() >= 5 ) {
475       pavement_ident = token[4];
476       if ( !pavement_ident.empty() && pavement_ident[pavement_ident.size()-1] == '\r' )
477         pavement_ident.erase( pavement_ident.size()-1 );
478     } else {
479       pavement_ident = "xx";
480     }
481   }
482
483   void parsePavementNodeLine850(int num, const vector<string>& token)
484   {
485     double lat = atof( token[1].c_str() );
486     double lon = atof( token[2].c_str() );
487     SGGeod pos(SGGeod::fromDegFt(lon, lat, 0.0));
488
489     FGPavement* pvt = 0;
490     if ( !pavement_ident.empty() ) {
491       pvt = new FGPavement( 0, pavement_ident, pos );
492       pavements.push_back( pvt );
493       pavement_ident = "";
494     } else {
495       pvt = pavements.back();
496     }
497     if ( num == 112 || num == 114 ) {
498       double lat_b = atof( token[3].c_str() );
499       double lon_b = atof( token[4].c_str() );
500       SGGeod pos_b(SGGeod::fromDegFt(lon_b, lat_b, 0.0));
501       pvt->addBezierNode(pos, pos_b, num == 114);
502     } else {
503       pvt->addNode(pos, num == 113);
504     }
505   }
506
507   void parseCommLine(int lineId, const vector<string>& token) 
508   {
509     if ( rwy_count <= 0 ) {
510       SG_LOG( SG_GENERAL, SG_ALERT, "No runways; skipping comm for " + last_apt_id);
511     }
512  
513     SGGeod pos = SGGeod::fromDegFt(rwy_lon_accum / (double)rwy_count, 
514         rwy_lat_accum / (double)rwy_count, last_apt_elev);
515     
516     // short int representing tens of kHz:
517     int freqKhz = atoi(token[1].c_str()) * 10;
518     int rangeNm = 50;
519     FGPositioned::Type ty;
520     // Make sure we only pass on stations with at least a name
521     if (token.size() >2){
522
523         switch (lineId) {
524             case 50:
525                 ty = FGPositioned::FREQ_AWOS;
526                 for( size_t i = 2; i < token.size(); ++i )
527                 {
528                   if( token[i] == "ATIS" )
529                   {
530                     ty = FGPositioned::FREQ_ATIS;
531                     break;
532                   }
533                 }
534                 break;
535
536             case 51:    ty = FGPositioned::FREQ_UNICOM; break;
537             case 52:    ty = FGPositioned::FREQ_CLEARANCE; break;
538             case 53:    ty = FGPositioned::FREQ_GROUND; break;
539             case 54:    ty = FGPositioned::FREQ_TOWER; break;
540             case 55:
541             case 56:    ty = FGPositioned::FREQ_APP_DEP; break;
542             default:
543                 throw sg_range_exception("unsupported apt.dat comm station type");
544         }
545
546       // Name can contain white spaces. All tokens after the second token are
547       // part of the name.
548       std::string name = token[2];
549       for( size_t i = 3; i < token.size(); ++i )
550         name += ' ' + token[i];
551
552       cache->insertCommStation(ty, name, pos, freqKhz, rangeNm, currentAirportID);
553     }
554     else SG_LOG( SG_GENERAL, SG_DEBUG, "Found unnamed comm. Skipping: " << lineId);
555   }
556
557 };
558   
559 // Load the airport data base from the specified aptdb file.  The
560 // metar file is used to mark the airports as having metar available
561 // or not.
562 bool airportDBLoad( const SGPath &aptdb_file )
563 {
564   APTLoader ld;
565   ld.parseAPT(aptdb_file);
566   return true;
567 }
568   
569 bool metarDataLoad(const SGPath& metar_file)
570 {
571   sg_gzifstream metar_in( metar_file.str() );
572   if ( !metar_in.is_open() ) {
573     SG_LOG( SG_GENERAL, SG_ALERT, "Cannot open file: " << metar_file );
574     return false;
575   }
576   
577   NavDataCache* cache = NavDataCache::instance();
578
579   string ident;
580   while ( metar_in ) {
581     metar_in >> ident;
582     if ( ident == "#" || ident == "//" ) {
583       metar_in >> skipeol;
584     } else {
585       cache->setAirportMetar(ident, true);
586     }
587   }
588   
589   return true;
590 }
591
592 } // of namespace flightgear