diff --git a/src/Airports/CMakeLists.txt b/src/Airports/CMakeLists.txt index d72a6bdb..930a2cbe 100644 --- a/src/Airports/CMakeLists.txt +++ b/src/Airports/CMakeLists.txt @@ -3,4 +3,5 @@ include_directories(${PROJECT_SOURCE_DIR}/src/BuildTiles) if (NEWMAT_FOUND) add_subdirectory(GenAirports) -endif (NEWMAT_FOUND) \ No newline at end of file +add_subdirectory(GenAirports850) +endif (NEWMAT_FOUND) diff --git a/src/Airports/GenAirports850/.gitignore b/src/Airports/GenAirports850/.gitignore new file mode 100644 index 00000000..410f82f3 --- /dev/null +++ b/src/Airports/GenAirports850/.gitignore @@ -0,0 +1 @@ +genapts850 diff --git a/src/Airports/GenAirports850/CMakeLists.txt b/src/Airports/GenAirports850/CMakeLists.txt new file mode 100644 index 00000000..e1ec161e --- /dev/null +++ b/src/Airports/GenAirports850/CMakeLists.txt @@ -0,0 +1,32 @@ + + +add_executable(genapts850 + apt_surface.hxx apt_surface.cxx + build.cxx build.hxx + convex_hull.cxx convex_hull.hxx + elevations.cxx elevations.hxx + global.hxx + lights.hxx lights.cxx + main.cxx + point2d.cxx point2d.hxx + poly_extra.cxx poly_extra.hxx + runway.cxx runway.hxx + rwy_common.cxx rwy_common.hxx + rwy_nonprec.cxx rwy_nonprec.hxx + rwy_gen.cxx rwy_gen.hxx + rwy_simple.cxx rwy_simple.hxx + rwy_visual.cxx rwy_visual.hxx + taxiway.cxx taxiway.hxx + texparams.hxx + ) + +target_link_libraries(genapts850 + Polygon Geometry + Array Optimize Output poly2tri + TriangleJRS + ${SIMGEAR_CORE_LIBRARIES} + ${SIMGEAR_CORE_LIBRARY_DEPENDENCIES} + ${GPC_LIBRARY} + ${NEWMAT_LIBRARY}) + +install(TARGETS genapts850 RUNTIME DESTINATION bin) diff --git a/src/Airports/GenAirports850/apt_surface.cxx b/src/Airports/GenAirports850/apt_surface.cxx new file mode 100644 index 00000000..470aefb2 --- /dev/null +++ b/src/Airports/GenAirports850/apt_surface.cxx @@ -0,0 +1,477 @@ +// apt_surface.cxx -- class to manage airport terrain surface +// approximation and smoothing +// +// Written by Curtis Olson, started March 2003. +// +// Copyright (C) 2003 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: apt_surface.cxx,v 1.31 2005-12-19 16:51:25 curt Exp $ +// + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +// libnewmat includes and defines +#define WANT_STREAM // include.h will get stream fns +#define WANT_MATH // include.h will get math fns + // newmatap.h will get include.h +#include // need matrix applications +#include // need matrix output routines + +#include +#include +#include +#include + +#include + +#include "elevations.hxx" +#include "global.hxx" +#include "apt_surface.hxx" + + +static bool limit_slope( SimpleMatrix *Pts, int i1, int j1, int i2, int j2, + double average_elev_m ) +{ + bool slope_error = false; + + Point3D p1, p2; + p1 = Pts->element(i1,j1); + p2 = Pts->element(i2,j2); + + double az1, az2, dist; + double slope; + + geo_inverse_wgs_84( 0, p1.y(), p1.x(), p2.y(), p2.x(), + &az1, &az2, &dist ); + slope = (p2.z() - p1.z()) / dist; + + if ( fabs(slope) > (slope_max + slope_eps) ) { + // need to throttle slope, let's move the point + // furthest away from average towards the center. + + slope_error = true; + + SG_LOG( SG_GENERAL, SG_DEBUG, " (a) detected slope of " + << slope << " dist = " << dist ); + + double e1 = fabs(average_elev_m - p1.z()); + double e2 = fabs(average_elev_m - p2.z()); + // cout << " z1 = " << p1.z() << " z2 = " << p2.z() << endl; + // cout << " e1 = " << e1 << " e2 = " << e2 << endl; + + if ( e1 > e2 ) { + // cout << " p1 error larger" << endl; + if ( slope > 0 ) { + p1.setz( p2.z() - (dist * slope_max) ); + } else { + p1.setz( p2.z() + (dist * slope_max) ); + } + Pts->set(i1, j1, p1); + } else { + // cout << " p2 error larger" << endl; + if ( slope > 0 ) { + p2.setz( p1.z() + (dist * slope_max) ); + } else { + p2.setz( p1.z() - (dist * slope_max) ); + } + Pts->set(i2, j2, p2); + } + // cout << " z1 = " << p1.z() << " z2 = " << p2.z() << endl; + } + + return slope_error; +} + + +// Constructor, specify min and max coordinates of desired area in +// lon/lat degrees +TGAptSurface::TGAptSurface( const string& path, + const string_list& elev_src, + Point3D _min_deg, Point3D _max_deg, + double _average_elev_m ) +{ + // Calculate desired size of grid + min_deg = _min_deg; + max_deg = _max_deg; + average_elev_m = _average_elev_m; + // cout << "min = " << min_deg << " max = " << max_deg + // << " ave = " << average_elev_m << endl; + + // The following size calculations are for the purpose of + // determining grid divisions so it's not important that they be + // *exact*, just ball park. + double y_deg = max_deg.lat() - min_deg.lat(); + double y_rad = y_deg * SG_DEGREES_TO_RADIANS; + double y_nm = y_rad * SG_RAD_TO_NM; + double y_m = y_nm * SG_NM_TO_METER; + + double xfact = cos( min_deg.lat() * SG_DEGREES_TO_RADIANS ); + double x_deg = max_deg.lon() - min_deg.lon(); + double x_rad = x_deg * SG_DEGREES_TO_RADIANS; + double x_nm = x_rad * SG_RAD_TO_NM * xfact; + double x_m = x_nm * SG_NM_TO_METER; + + SG_LOG( SG_GENERAL, SG_DEBUG, + "Area size = " << y_m << " x " << x_m << " (m)" ); + + int xdivs = (int)(x_m / coarse_grid) + 1; + int ydivs = (int)(y_m / coarse_grid) + 1; + + // set an arbitrary minumum number of divisions to keep things + // interesting + if ( xdivs < 8 ) { xdivs = 8; } + if ( ydivs < 8 ) { ydivs = 8; } + SG_LOG(SG_GENERAL, SG_INFO, " M(" << ydivs << "," << xdivs << ")"); + + double dlon = x_deg / xdivs; + double dlat = y_deg / ydivs; + + double dlon_h = dlon * 0.5; + double dlat_h = dlat * 0.5; + + // Build the extra res input grid (shifted SW by half (dlon,dlat) + // with an added major row column on the NE sides.) + int mult = 10; + SimpleMatrix dPts( (xdivs + 1) * mult + 1, (ydivs + 1) * mult + 1 ); + for ( int j = 0; j < dPts.rows(); ++j ) { + for ( int i = 0; i < dPts.cols(); ++i ) { + dPts.set(i, j, Point3D( min_deg.lon() - dlon_h + + i * (dlon / (double)mult), + min_deg.lat() - dlat_h + + j * (dlat / (double)mult), + -9999 ) + ); + } + } + + // Lookup the elevations of all the grid points + tgCalcElevations( path, elev_src, dPts, 0.0 ); +#ifdef DEBUG + for ( int j = 0; j < dPts.rows(); ++j ) { + for ( int i = 0; i < dPts.cols(); ++i ) { + printf("hr %.5f %.5f %.1f\n", + dPts.element(i,j).x(), dPts.element(i,j).y(), + dPts.element(i,j).z() ); + } + } +#endif + + // Clamp the elevations against the externally provided average + // elevation. + tgClampElevations( dPts, average_elev_m, max_clamp ); + +#ifdef DEBUG + for ( int j = 0; j < dPts.rows(); ++j ) { + for ( int i = 0; i < dPts.cols(); ++i ) { + printf("chr %.5f %.5f %.1f\n", + dPts.element(i,j).x(), dPts.element(i,j).y(), + dPts.element(i,j).z() ); + } + } +#endif + + // Build the normal res input grid from the double res version + Pts = new SimpleMatrix(xdivs + 1, ydivs + 1 ); + double ave_divider = (mult+1) * (mult+1); + for ( int j = 0; j < Pts->rows(); ++j ) { + for ( int i = 0; i < Pts->cols(); ++i ) { + SG_LOG(SG_GENERAL, SG_DEBUG, i << "," << j); + double accum = 0.0; + double lon_accum = 0.0; + double lat_accum = 0.0; + for ( int jj = 0; jj <= mult; ++jj ) { + for ( int ii = 0; ii <= mult; ++ii ) { + double value = dPts.element(mult*i + ii, mult*j + jj).z(); + SG_LOG( SG_GENERAL, SG_DEBUG, "value = " << value ); + accum += value; + lon_accum += dPts.element(mult*i + ii, mult*j + jj).x(); + lat_accum += dPts.element(mult*i + ii, mult*j + jj).y(); + } + } + double val_ave = accum / ave_divider; + double lon_ave = lon_accum / ave_divider; + double lat_ave = lat_accum / ave_divider; + + SG_LOG( SG_GENERAL, SG_DEBUG, " val_ave = " << val_ave ); + Pts->set(i, j, Point3D( min_deg.lon() + i * dlon, + min_deg.lat() + j * dlat, + val_ave ) + ); + SG_LOG( SG_GENERAL, SG_DEBUG, "lon_ave = " << lon_ave + << " lat_ave = " << lat_ave ); + SG_LOG( SG_GENERAL, SG_DEBUG, "lon = " << min_deg.lon() + j * dlon + << " lat = " << min_deg.lat() + i * dlat ); + } + } + +#ifdef DEBUG + for ( int j = 0; j < Pts->rows(); ++j ) { + for ( int i = 0; i < Pts->cols(); ++i ) { + printf("nr %.5f %.5f %.1f\n", + Pts->element(i,j).x(), + Pts->element(i,j).y(), + Pts->element(i,j).z() ); + } + } +#endif + + bool slope_error = true; + while ( slope_error ) { + SG_LOG( SG_GENERAL, SG_DEBUG, "start of slope processing pass" ); + slope_error = false; + // Add some "slope" sanity to the resulting surface grid points + for ( int j = 0; j < Pts->rows() - 1; ++j ) { + for ( int i = 0; i < Pts->cols() - 1; ++i ) { + if ( limit_slope( Pts, i, j, i+1, j, average_elev_m ) ) { + slope_error = true; + } + if ( limit_slope( Pts, i, j, i, j+1, average_elev_m ) ) { + slope_error = true; + } + if ( limit_slope( Pts, i, j, i+1, j+1, average_elev_m ) ) { + slope_error = true; + } + } + } + } + +#ifdef DEBUG + for ( int j = 0; j < Pts->rows(); ++j ) { + for ( int i = 0; i < Pts->cols(); ++i ) { + printf("%.5f %.5f %.1f\n", + Pts->element(i,j).x(), Pts->element(i,j).y(), + Pts->element(i,j).z() ); + } + } +#endif + + // compute an central offset point. + double clon = (min_deg.lon() + max_deg.lon()) / 2.0; + double clat = (min_deg.lat() + max_deg.lat()) / 2.0; + offset = Point3D( clon, clat, average_elev_m ); + SG_LOG(SG_GENERAL, SG_DEBUG, "Central offset point = " << offset); + + // Create the fitted surface + SG_LOG(SG_GENERAL, SG_DEBUG, "ready to create fitted surface"); + fit(); + SG_LOG(SG_GENERAL, SG_DEBUG, " fit process successful."); + +#ifdef DEBUG + // For debugging only: output an array of surface points suitable + // for plotting with gnuplot. This is useful for comparing the + // approximated and smoothed surface to the original rough + // surface. + cout << "DEBUGGING TEST" << endl; + const int divs = 100; + double dx = x_deg / divs; + double dy = y_deg / divs; + for ( int j = 0; j < divs; ++j ) { + for ( int i = 0; i < divs; ++i ) { + double lon = min_deg.lon() + j * dx; + double lat = min_deg.lat() + i * dy; + printf("%.5f %.5f %.1f\n", lon, lat, query(lon, lat) ); + } + } +#endif + +} + + +TGAptSurface::~TGAptSurface() { + delete Pts; +} + + +static ColumnVector qr_method( Real* y, + Real* t1, Real* t2, Real* t3, Real* t4, + Real* t5, Real* t6, Real* t7, Real* t8, + Real* t9, Real* t10, Real* t11, Real* t12, + Real* t13, Real* t14, Real* t15, + int nobs, int npred ) +{ + cout << "QR triangularisation" << endl;; + + // QR triangularisation method + + // load data - 1s into col 1 of matrix + int npred1 = npred+1; + cout << "nobs = " << nobs << " npred1 = " << npred1 << endl; + + Matrix X(nobs,npred1); ColumnVector Y(nobs); + X.column(1) = 1.0; + X.column(2) << t1; + X.column(3) << t2; + X.column(4) << t3; + X.column(5) << t4; + X.column(6) << t5; + X.column(7) << t6; + X.column(8) << t7; + X.column(9) << t8; + X.column(10) << t9; + X.column(11) << t10; + X.column(12) << t11; + X.column(13) << t12; + X.column(14) << t13; + X.column(15) << t14; + X.column(16) << t15; + Y << y; + + // do Householder triangularisation + // no need to deal with constant term separately + Matrix X1 = X; // Want copy of matrix + ColumnVector Y1 = Y; + UpperTriangularMatrix U; ColumnVector M; + QRZ(X1, U); QRZ(X1, Y1, M); // Y1 now contains resids + ColumnVector A = U.i() * M; + ColumnVector Fitted = X * A; + Real ResVar = sum_square(Y1) / (nobs-npred1); + + // get variances of estimates + U = U.i(); DiagonalMatrix D; D << U * U.t(); + + // Get diagonals of Hat matrix + DiagonalMatrix Hat; Hat << X1 * X1.t(); + + cout << "A vector = " << A << endl; + cout << "A rows = " << A.nrows() << endl; + + // print out answers + cout << "\nEstimates and their standard errors\n\n"; + ColumnVector SE(npred1); + for (int i=1; i<=npred1; i++) SE(i) = sqrt(D(i)*ResVar); + cout << setw(11) << setprecision(5) << (A | SE) << endl; + cout << "\nObservations, fitted value, residual value, hat value\n"; + cout << setw(9) << setprecision(3) << + (X.columns(2,4) | Y | Fitted | Y1 | Hat.as_column()); + cout << "\n\n"; + + return A; +} + + +// Use a linear least squares method to fit a 3d polynomial to the +// sampled surface data +void TGAptSurface::fit() { + + // the fit function is: + // f(x,y) = A1*x + A2*x*y + A3*y + + // A4*x*x + A5+x*x*y + A6*x*x*y*y + A7*y*y + A8*x*y*y + + // A9*x*x*x + A10*x*x*x*y + A11*x*x*x*y*y + A12*x*x*x*y*y*y + + // A13*y*y*y + A14*x*y*y*y + A15*x*x*y*y*y + + int nobs = Pts->cols() * Pts->rows(); // number of observations + int npred = 15; // number of predictor values A[n] + + vector tz(nobs); + vector t1(nobs); + vector t2(nobs); + vector t3(nobs); + vector t4(nobs); + vector t5(nobs); + vector t6(nobs); + vector t7(nobs); + vector t8(nobs); + vector t9(nobs); + vector t10(nobs); + vector t11(nobs); + vector t12(nobs); + vector t13(nobs); + vector t14(nobs); + vector t15(nobs); + + // generate the required fit data + for ( int j = 0; j < Pts->rows(); j++ ) { + for ( int i = 0; i < Pts->cols(); i++ ) { + Point3D p = Pts->element( i, j ); + int index = ( j * Pts->cols() ) + i; + Real x = p.x() - offset.x(); + Real y = p.y() - offset.y(); + Real z = p.z() - offset.z(); + //cout << "pt = " << x << "," << y << "," << z << endl; + tz[index] = z; + t1[index] = x; + t2[index] = x*y; + t3[index] = y; + t4[index] = x*x; + t5[index] = x*x*y; + t6[index] = x*x*y*y; + t7[index] = y*y; + t8[index] = x*y*y; + t9[index] = x*x*x; + t10[index] = x*x*x*y; + t11[index] = x*x*x*y*y; + t12[index] = x*x*x*y*y*y; + t13[index] = y*y*y; + t14[index] = x*y*y*y; + t15[index] = x*x*y*y*y; + } + } + + // we want to find the values of a,b,c to give the best + // fit + + Try { + surface_coefficients + = qr_method( &tz[0], + &t1[0], &t2[0], &t3[0], &t4[0], &t5[0], &t6[0], &t7[0], &t8[0], + &t9[0], &t10[0], &t11[0], &t12[0], &t13[0], &t14[0], &t15[0], + nobs, npred + ); + cout << "surface_coefficients size = " << surface_coefficients.nrows() << endl; + } + CatchAll { cout << BaseException::what(); } + +} + + +// Query the elevation of a point, return -9999 if out of range +double TGAptSurface::query( double lon_deg, double lat_deg ) { + // sanity check + if ( lon_deg < min_deg.lon() || lon_deg > max_deg.lon() || + lat_deg < min_deg.lat() || lat_deg > max_deg.lat() ) + { + SG_LOG(SG_GENERAL, SG_WARN, + "Warning: query out of bounds for fitted surface!"); + return -9999.0; + } + + // compute the function with solved coefficients + + // the fit function is: + // f(x,y) = A1*x + A2*x*y + A3*y + + // A4*x*x + A5+x*x*y + A6*x*x*y*y + A7*y*y + A8*x*y*y + + // A9*x*x*x + A10*x*x*x*y + A11*x*x*x*y*y + A12*x*x*x*y*y*y + + // A13*y*y*y + A14*x*y*y*y + A15*x*x*y*y*y + + double x = lon_deg - offset.x(); + double y = lat_deg - offset.y(); + ColumnVector A = surface_coefficients; + + double result = A(1) + A(2)*x + A(3)*x*y + A(4)*y + A(5)*x*x + A(6)*x*x*y + + A(7)*x*x*y*y + A(8)*y*y + A(9)*x*y*y + A(10)*x*x*x + A(11)*x*x*x*y + + A(12)*x*x*x*y*y + A(13)*x*x*x*y*y*y + A(14)*y*y*y + A(15)*x*y*y*y + + A(16)*x*x*y*y*y; + result += offset.z(); + + printf("result = %.6f %.6f %.2f\n", lon_deg, lat_deg, result); + + return result; +} diff --git a/src/Airports/GenAirports850/apt_surface.hxx b/src/Airports/GenAirports850/apt_surface.hxx new file mode 100644 index 00000000..81237e0a --- /dev/null +++ b/src/Airports/GenAirports850/apt_surface.hxx @@ -0,0 +1,148 @@ +// apt_surface.hxx -- class to manage airport terrain surface +// approximation and smoothing +// +// Written by Curtis Olson, started March 2003. +// +// Copyright (C) 2003 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: apt_surface.hxx,v 1.9 2005-09-09 20:47:04 curt Exp $ +// + + +#ifndef _APT_SURFACE_HXX +#define _APT_SURFACE_HXX + + +#include + +// libnewmat includes and defines +#define WANT_STREAM // include.h will get stream fns +#define WANT_MATH // include.h will get math fns + // newmatap.h will get include.h +#include // need matrix applications +#include // need matrix output routines + +#include + + +/*** + * A dirt simple matrix class for our convenience based on top of Point3D + */ + +class SimpleMatrix { + +private: + + int _rows; + int _cols; + point_list m; + +public: + + inline SimpleMatrix( int columns, int rows ) { + _cols = columns; + _rows = rows; + m.resize( _cols * _rows ); + } + + inline Point3D element( int col, int row ) { + int index = ( row * _cols ) + col; + if ( col < 0 || col >= _cols ) { + cout << "column out of bounds on read (" << col << " >= " << _cols << ")" + << endl; + int *p = 0; *p = 1; // force crash + } else if ( row < 0 || row >= _rows ) { + cout << "row out of bounds on read (" << row << " >= " << _rows << ")" + << endl; + int *p = 0; *p = 1; // force crash + } + return m[index]; + } + + inline void set( int col, int row, Point3D p ) { + int index = ( row * _cols ) + col; + if ( col < 0 || col >= _cols ) { + cout << "column out of bounds on set (" << col << " >= " << _cols << ")" + << endl; + int *p = 0; *p = 1; // force crash + } else if ( row < 0 || row >= _rows ) { + cout << "row out of bounds on set (" << row << " >= " << _rows << ")" + << endl; + int *p = 0; *p = 1; // force crash + } + m[index] = p; + } + + inline int cols() const { return _cols; } + inline int rows() const { return _rows; } +}; + + +/*** + * Note of explanation. When a TGAptSurface instance is created, you + * must specify a min and max lon/lat containing the entire airport + * area. The class will divide up that area into a reasonably sized + * regular grid. It will then look up the elevation of each point on + * the grid from the DEM/Array data. Finally it will fit do a linear + * least squares polygonal surface approximation from this grid. Each + * vertex of the actual airport model is drapped over this fitted + * surface rather than over the underlying terrain data. This + * provides a) smoothing of noisy terrain data and b) natural rises + * and dips in the airport surface. + */ + +class TGAptSurface { + +private: + + // The actual nurbs surface approximation for the airport + SimpleMatrix *Pts; + ColumnVector surface_coefficients; + + Point3D min_deg, max_deg; + + // A central point in the airport area + Point3D offset; + + // externally seeded average airport elevation + double average_elev_m; + + +public: + + // Constructor, specify min and max coordinates of desired area in + // lon/lat degrees, also please specify an "average" airport + // elevations in meters. + TGAptSurface( const string &path, const string_list& elev_src, + Point3D _min_deg, Point3D _max_deg, double _average_elev_m ); + + // Destructor + ~TGAptSurface(); + + // Use a linear least squares method to fit a 3d polynomial to the + // sampled surface data + void fit(); + + // Query the elevation of a point, return -9999 if out of range. + // This routine makes a simplistic assumption that X,Y space is + // proportional to u,v space on the nurbs surface which it isn't. + double query( double lon_deg, double lat_deg ); + +}; + + +#endif // _APT_SURFACE_HXX diff --git a/src/Airports/GenAirports850/build.cxx b/src/Airports/GenAirports850/build.cxx new file mode 100644 index 00000000..cdf135d0 --- /dev/null +++ b/src/Airports/GenAirports850/build.cxx @@ -0,0 +1,1414 @@ +// build.cxx -- routines to build polygon model of an airport from the runway +// definition +// +// Written by Curtis Olson, started September 1999. +// +// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: build.cxx,v 1.116 2005-10-31 18:43:02 curt Exp $ +// + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include // for atoi() atof() +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "apt_surface.hxx" +#include "convex_hull.hxx" +#include "elevations.hxx" +#include "lights.hxx" +#include "point2d.hxx" +#include "poly_extra.hxx" +#include "runway.hxx" +#include "rwy_common.hxx" +#include "rwy_nonprec.hxx" +#include "rwy_gen.hxx" +#include "rwy_simple.hxx" +#include "rwy_visual.hxx" +#include "taxiway.hxx" +#include "texparams.hxx" + +#include "build.hxx" + +using std::map; +using std::less; +using std::string; + + +// calculate texture coordinates for runway section using the provided +// texturing parameters. Returns a mirror polygon to the runway, +// except each point is the texture coordinate of the corresponding +// point in the original polygon. +static TGPolygon rwy_section_tex_coords( const TGPolygon& in_poly, + const TGTexParams& tp, + const bool clip_result ) +{ + int i, j; + TGPolygon result; + result.erase(); + // double length = rwy.length * SG_FEET_TO_METER; + // double width = rwy.width * SG_FEET_TO_METER; + + Point3D ref = tp.get_ref(); + double width = tp.get_width(); + double length = tp.get_length(); + double heading = tp.get_heading(); + double minu = tp.get_minu(); + double maxu = tp.get_maxu(); + double minv = tp.get_minv(); + double maxv = tp.get_maxv(); + SG_LOG( SG_GENERAL, SG_DEBUG, "section ref = " << ref ); + SG_LOG( SG_GENERAL, SG_DEBUG, " width = " << width ); + SG_LOG( SG_GENERAL, SG_DEBUG, " length = " << length ); + SG_LOG( SG_GENERAL, SG_DEBUG, " heading = " << heading ); + Point3D p, t; + double x, y, tx, ty; + + for ( i = 0; i < in_poly.contours(); ++i ) { + for ( j = 0; j < in_poly.contour_size( i ); ++j ) { + p = in_poly.get_pt( i, j ); + SG_LOG(SG_GENERAL, SG_DEBUG, "point = " << p); + + // + // 1. Calculate distance and bearing from the center of + // the runway + // + + // given alt, lat1, lon1, lat2, lon2, calculate starting + // and ending az1, az2 and distance (s). Lat, lon, and + // azimuth are in degrees. distance in meters + double az1, az2, dist; + geo_inverse_wgs_84( 0, ref.y(), ref.x(), p.y(), p.x(), + &az1, &az2, &dist ); + SG_LOG(SG_GENERAL, SG_DEBUG, "basic course = " << az2); + + // + // 2. Rotate this back into a coordinate system where Y + // runs the length of the runway and X runs crossways. + // + + double course = az2 - heading; + while ( course < -360 ) { course += 360; } + while ( course > 360 ) { course -= 360; } + SG_LOG( SG_GENERAL, SG_DEBUG, + " course = " << course << " dist = " << dist ); + + // + // 3. Convert from polar to cartesian coordinates + // + + x = sin( course * SGD_DEGREES_TO_RADIANS ) * dist; + y = cos( course * SGD_DEGREES_TO_RADIANS ) * dist; + SG_LOG(SG_GENERAL, SG_DEBUG, " x = " << x << " y = " << y); + + // + // 4. Map x, y point into texture coordinates + // + double tmp; + + tmp = x / width; + tx = tmp * (maxu - minu) + minu; + // tx = ((int)(tx * 100)) / 100.0; + SG_LOG(SG_GENERAL, SG_DEBUG, " (" << tx << ")"); + + if ( clip_result) { + if ( tx < 0.0 ) { tx = 0.0; } + if ( tx > 1.0 ) { tx = 1.0; } + } + + // ty = (y - min.y()) / (max.y() - min.y()); + ty = y / length; + tmp = y / length; + ty = tmp * (maxv - minv) + minv; + // ty = ((int)(ty * 100)) / 100.0; + SG_LOG(SG_GENERAL, SG_DEBUG, " (" << ty << ")"); + + if ( clip_result ) { + if ( ty < 0.0 ) { ty = 0.0; } + if ( ty > 1.0 ) { ty = 1.0; } + } + + t = Point3D( tx, ty, 0 ); + SG_LOG(SG_GENERAL, SG_DEBUG, " (" << tx << ", " << ty << ")"); + + result.add_node( i, t ); + } + } + + return result; +} + + +// Determine node elevations of a point_list based on the provided +// TGAptSurface. Offset is added to the final elevation +static point_list calc_elevations( TGAptSurface &surf, + const point_list& geod_nodes, + double offset ) +{ + point_list result = geod_nodes; + for ( unsigned int i = 0; i < result.size(); ++i ) { + double elev = surf.query( result[i].lon(), result[i].lat() ); + result[i].setelev( elev + offset ); + } + + return result; +} + + +// Determine node elevations of each node of a TGPolygon based on the +// provided TGAptSurface. Offset is added to the final elevation +static TGPolygon calc_elevations( TGAptSurface &surf, + const TGPolygon& poly, + double offset ) +{ + TGPolygon result; + for ( int i = 0; i < poly.contours(); ++i ) { + point_list contour = poly.get_contour( i ); + point_list elevated = calc_elevations( surf, contour, offset ); + + result.add_contour( elevated, poly.get_hole_flag(i) ); + } + + return result; +} + + +#if 0 // DEAD CODE 10/15/2004 CLO +// strip trailing spaces +static void my_chomp( string& str ) { + SG_LOG(SG_GENERAL, SG_DEBUG, "my_chomp()"); + SG_LOG(SG_GENERAL, SG_DEBUG, "'" << str.substr( str.length() - 1, 1 ) << "'"); + while ( str.substr( str.length() - 1, 1 ) == " " ) { + str = str.substr( 0, str.length() - 1 ); + SG_LOG(SG_GENERAL, SG_DEBUG, "'" << str.substr( str.length() - 1, 1 ) << "'"); + } +} +#endif + + +// build a runway +static void build_runway( const TGRunway& rwy_info, + double alt_m, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum, + TGPolygon *apt_base, + TGPolygon *apt_clearing ) +{ + SG_LOG(SG_GENERAL, SG_DEBUG, "surface code = " << rwy_info.surface_code); + int surface_code = rwy_info.surface_code; + SG_LOG(SG_GENERAL, SG_DEBUG, "surface code = " << surface_code); + string lighting_flags = rwy_info.lighting_flags; + SG_LOG(SG_GENERAL, SG_DEBUG, "lighting flags = " << lighting_flags); + + string vasi1 = lighting_flags.substr(0,1); + string rwylt1 = lighting_flags.substr(1,1); + string apprch1 = lighting_flags.substr(2,1); + string vasi2 = lighting_flags.substr(3,1); + string rwylt2 = lighting_flags.substr(4,1); + string apprch2 = lighting_flags.substr(5,1); + + string material; + if ( surface_code == 1 /* Asphalt */ ) { + if ( !rwy_info.really_taxiway ) { + material = "pa_"; + } else { + if ( rwy_info.width <= 150 && rwylt1 == "6" ) { + material = "pa_taxiway"; + } else { + material = "pa_tiedown"; + } + } + } else if ( surface_code == 2 /* Concrete */ ) { + if ( !rwy_info.really_taxiway ) { + material = "pc_"; + } else { + if ( rwy_info.width <= 150 && rwylt1 == "6" ) { + material = "pc_taxiway"; + } else { + material = "pc_tiedown"; + } + } + } else if ( surface_code == 3 /* Turf/Grass */ ) { + material = "grass_rwy"; + } else if ( surface_code == 4 /* Dirt */ + || surface_code == 5 /* Gravel */ ) { + material = "dirt_rwy"; + } else if ( surface_code == 12 /* Dry Lakebed */ ) { + if ( rwy_info.really_taxiway ) { + material = "lakebed_taxiway"; + } else { + material = "dirt_rwy"; + } + } else if ( surface_code == 13 /* Water runway (buoy's?) */ ) { + // water + } else { + SG_LOG(SG_GENERAL, SG_WARN, "surface_code = " << surface_code); + throw sg_exception("unknown runway type!"); + } + + + SG_LOG(SG_GENERAL, SG_DEBUG, "marking code = " << rwy_info.marking_code); + + if ( rwy_info.really_taxiway ) { + gen_taxiway( rwy_info, alt_m, material, + rwy_polys, texparams, accum ); + } else if ( surface_code == 3 /* Turf/Grass */ + || surface_code == 4 /* Dirt */ + || surface_code == 5 /* Gravel */ ) + { + gen_simple_rwy( rwy_info, alt_m, material, + rwy_polys, texparams, accum ); + } else if ( rwy_info.marking_code == 3 /* Precision */ ) { + // precision runway markings + gen_precision_rwy( rwy_info, alt_m, material, + rwy_polys, texparams, accum ); + } else if ( rwy_info.marking_code == 2 /* Non-precision */ ) { + // non-precision runway markings + gen_non_precision_rwy( rwy_info, alt_m, material, + rwy_polys, texparams, accum ); + } else if ( rwy_info.marking_code == 1 /* Visual */ ) { + // visual runway markings + gen_visual_rwy( rwy_info, alt_m, material, + rwy_polys, texparams, accum ); + } else if ( rwy_info.marking_code == 0 /* No known markings, lets assume Visual */ ) { + // visual runway markings + gen_visual_rwy( rwy_info, alt_m, material, + rwy_polys, texparams, accum ); + } else if ( surface_code == 13 /* Water buoys */ ) { + // do nothing for now. + } else { + // unknown runway code ... hehe, I know, let's just die + // right here so the programmer has to fix his code if a + // new code ever gets introduced. :-) + SG_LOG( SG_GENERAL, SG_ALERT, "Unknown runway code = " << + rwy_info.marking_code ); + throw sg_exception("Unknown runway code in build.cxx:build_airport()"); + } + + TGPolygon base, safe_base; + if ( rwy_info.really_taxiway ) { + base = gen_runway_area_w_extend( rwy_info, 0.0, 10.0, 0.0, 0.0, 10.0 ); + // also clear a safe area around the taxiway + safe_base + = gen_runway_area_w_extend( rwy_info, 0.0, 40.0, 0.0, 0.0, 40.0 ); + } else { + base = gen_runway_area_w_extend( rwy_info, 0.0, 20.0, -rwy_info.stopway1* SG_FEET_TO_METER, -rwy_info.stopway2* SG_FEET_TO_METER, 20.0 ); + // also clear a safe area around the runway + safe_base + = gen_runway_area_w_extend( rwy_info, 0.0, 180.0, -rwy_info.stopway1* SG_FEET_TO_METER, -rwy_info.stopway2* SG_FEET_TO_METER, 50.0 ); + } + *apt_clearing = tgPolygonUnion(safe_base, *apt_clearing); + + // add base to apt_base + *apt_base = tgPolygonUnion( base, *apt_base ); +} + + +// build 3d airport +void build_airport( string airport_id, float alt_m, + string_list& runways_raw, + string_list& beacons_raw, + string_list& towers_raw, + string_list& windsocks_raw, + const string& root, + const string_list& elev_src ) +{ + int i, j, k; + + superpoly_list rwy_polys; + texparams_list texparams; + + // poly_list rwy_tris, rwy_txs; + TGPolygon runway, runway_a, runway_b, clipped_a, clipped_b; + TGPolygon split_a, split_b; + TGPolygon apt_base; + TGPolygon apt_clearing; + Point3D p; + + TGPolygon accum; + accum.erase(); + + // parse main airport information + double apt_lon = 0.0, apt_lat = 0.0; + int rwy_count = 0; + + SG_LOG( SG_GENERAL, SG_INFO, "Building " << airport_id ); + + // parse runways/taxiways and generate the vertex list + runway_list runways; runways.clear(); + runway_list taxiways; taxiways.clear(); + + for ( i = 0; i < (int)runways_raw.size(); ++i ) { + ++rwy_count; + + string rwy_str = runways_raw[i]; + vector token = simgear::strutils::split( rwy_str ); + + TGRunway rwy; + + SG_LOG(SG_GENERAL, SG_DEBUG, rwy_str); + rwy.rwy_no = token[8]; + rwy.really_taxiway = (rwy.rwy_no == "xxx"); + rwy.generated = false; + + //first runway end coordinates + double lat_1 = atof( token[9].c_str() ); + double lon_1 = atof( token[10].c_str() ); + SGGeod pos_1(SGGeod::fromDegFt(lon_1, lat_1, 0.0)); + + //opposite end coordinates + double lat_2 = atof( token[18].c_str() ); + double lon_2 = atof( token[19].c_str() ); + SGGeod pos_2(SGGeod::fromDegFt(lon_2, lat_2, 0.0)); + + //calculate runway heading + double rwcourse(SGGeodesy::courseDeg(pos_1, pos_2)); + + double rwlength((SGGeodesy::distanceM(pos_1, pos_2)) * SG_METER_TO_FEET); + + + SG_LOG( SG_GENERAL, SG_INFO, "pos1 is " << lat_1 << lon_1 ); + SG_LOG( SG_GENERAL, SG_INFO, "pos2 is " << lat_2 << lon_2 ); + SG_LOG( SG_GENERAL, SG_INFO, "course is " << rwcourse ); + SG_LOG( SG_GENERAL, SG_INFO, "distance is " << rwlength ); + + rwy.lat = atof( token[9].c_str() ); + apt_lat += rwy.lat; + + rwy.lon = atof( token[10].c_str() ); + apt_lon += rwy.lon; + + rwy.heading = rwcourse; + + rwy.length = rwlength; + rwy.width = (atoi( token[1].c_str() ) * SG_METER_TO_FEET); + + rwy.disp_thresh1 = atoi( token[11].c_str() ); + rwy.disp_thresh2 = atoi( token[20].c_str() ); + + rwy.stopway1 = atoi( token[12].c_str() ); + rwy.stopway2 = atoi( token[21].c_str() ); + + rwy.lighting_flags = token[9]; + rwy.surface_code = atoi( token[2].c_str() ); + rwy.shoulder_code = token[3]; + rwy.marking_code = atoi( token[13].c_str() ); + rwy.smoothness = atof( token[4].c_str() ); + rwy.dist_remaining = (atoi( token[14].c_str() ) == 1 ); + + if (token.size()>15) { + string vasi_angles = token[15]; + vector vasis = simgear::strutils::split( vasi_angles, "." ); + rwy.gs_angle1 = atof( vasis[0].c_str() ) * 0.01; + rwy.gs_angle2 = atof( vasis[1].c_str() ) * 0.01; + } else { + rwy.gs_angle1 = rwy.gs_angle2 = 3.0; + } + + SG_LOG( SG_GENERAL, SG_DEBUG, " no = " << rwy.rwy_no); + SG_LOG( SG_GENERAL, SG_DEBUG, " lat = " << rwy.lat); + SG_LOG( SG_GENERAL, SG_DEBUG, " lon = " << rwy.lon); + SG_LOG( SG_GENERAL, SG_DEBUG, " hdg = " << rwy.heading); + SG_LOG( SG_GENERAL, SG_DEBUG, " len = " << rwy.length); + SG_LOG( SG_GENERAL, SG_DEBUG, " width = " << rwy.width); + SG_LOG( SG_GENERAL, SG_DEBUG, " lighting = " << rwy.lighting_flags); + SG_LOG( SG_GENERAL, SG_DEBUG, " sfc = " << rwy.surface_code); + SG_LOG( SG_GENERAL, SG_DEBUG, " mrkgs = " << rwy.marking_code); + SG_LOG( SG_GENERAL, SG_DEBUG, " dspth1= " << rwy.disp_thresh1); + SG_LOG( SG_GENERAL, SG_DEBUG, " stop1 = " << rwy.stopway1); + SG_LOG( SG_GENERAL, SG_DEBUG, " dspth2= " << rwy.disp_thresh2); + SG_LOG( SG_GENERAL, SG_DEBUG, " stop2 = " << rwy.stopway2); + + if ( rwy.really_taxiway ) { + taxiways.push_back( rwy ); + } else { + runways.push_back( rwy ); + } + } + SG_LOG(SG_GENERAL, SG_INFO, "Runway count = " << runways.size() ); + SG_LOG(SG_GENERAL, SG_INFO, "Taxiway count = " << taxiways.size() ); + + SGBucket b( apt_lon / (double)rwy_count, apt_lat / (double)rwy_count ); + SG_LOG(SG_GENERAL, SG_INFO, b.gen_base_path() << "/" << b.gen_index_str()); + + point_list beacons; beacons.clear(); + for ( i = 0; i < (int)beacons_raw.size(); ++i ) { + string beacon_str = beacons_raw[i]; + vector token = simgear::strutils::split( beacon_str ); + + Point3D beacon; + + SG_LOG(SG_GENERAL, SG_INFO, beacon_str); + + beacon.setlat( atof( token[1].c_str() ) ); + beacon.setlon( atof( token[2].c_str() ) ); + + SG_LOG( SG_GENERAL, SG_DEBUG, " beacon = " << beacon ); + + beacons.push_back( beacon ); + } + + point_list towers; towers.clear(); + for ( i = 0; i < (int)towers_raw.size(); ++i ) { + string tower_str = towers_raw[i]; + vector token = simgear::strutils::split( tower_str ); + + Point3D tower; + + SG_LOG(SG_GENERAL, SG_INFO, tower_str); + + tower.setlat( atof( token[1].c_str() ) ); + tower.setlon( atof( token[2].c_str() ) ); + + if (!atoi(token[4].c_str())) { + // Ralf Gerlich: Skip towers that shall not be drawn + continue; + } + + SG_LOG( SG_GENERAL, SG_DEBUG, " tower = " << tower ); + + towers.push_back( tower ); + } + + point_list windsocks; windsocks.clear(); + int_list windsock_types; windsock_types.clear(); + for ( i = 0; i < (int)windsocks_raw.size(); ++i ) { + string windsock_str = windsocks_raw[i]; + vector token = simgear::strutils::split( windsock_str ); + + Point3D windsock; + + SG_LOG(SG_GENERAL, SG_INFO, windsock_str); + + windsock.setlat( atof( token[1].c_str() ) ); + windsock.setlon( atof( token[2].c_str() ) ); + + string windsock_type = token[3]; + + SG_LOG( SG_GENERAL, SG_DEBUG, " windsock = " << windsock ); + + windsocks.push_back( windsock ); + if ( windsock_type == "0" ) { + windsock_types.push_back( 0 ); + } else { + windsock_types.push_back( 1 ); + } + } + + TGSuperPoly sp; + TGTexParams tp; + + // First pass: generate the precision runways since these have + // precidence + for ( i = 0; i < (int)runways.size(); ++i ) { + if ( runways[i].marking_code == 3 /* Precision */ ) { + build_runway( runways[i], alt_m, + &rwy_polys, &texparams, &accum, + &apt_base, &apt_clearing ); + } + } + + // 2nd pass: generate the non-precision and visual runways + for ( i = 0; i < (int)runways.size(); ++i ) { + if ( runways[i].marking_code == 2 /* Non-precision */ + || runways[i].marking_code == 1 /* Visual */ ) + { + if ( runways[i].surface_code != 13 /* Water */ ) { + // only build non-water runways + build_runway( runways[i], alt_m, + &rwy_polys, &texparams, &accum, + &apt_base, &apt_clearing ); + } + } + } + + // 3rd pass: generate all remaining runways not covered in the first pass + for ( i = 0; i < (int)runways.size(); ++i ) { + if ( runways[i].marking_code != 3 /* Precision */ + && runways[i].marking_code != 2 /* Non-precision */ + && runways[i].marking_code != 1 /* Visual */ ) + { + if ( runways[i].surface_code != 6 /* Asphalt Helipad */ && + runways[i].surface_code != 7 /* Concrete Helipad */ && + runways[i].surface_code != 8 /* Turf Helipad */ && + runways[i].surface_code != 9 /* Dirt Helipad */ && + runways[i].surface_code != 13 /* Water/buoy runway */ ) + { + // only build non-water and non-heliport runways + build_runway( runways[i], alt_m, + &rwy_polys, &texparams, &accum, + &apt_base, &apt_clearing ); + } + } + } + + // 4th pass: generate all taxiways + +#if 0 + // we want to generate in order of largest size first so this will + // look a little weird, but that's all I'm doing, otherwise a + // simple list traversal would work fine. + bool done = false; + while ( !done ) { + // find the largest taxiway + int largest_idx = -1; + double max_size = 0; + for ( i = 0; i < (int)taxiways.size(); ++i ) { + SG_LOG( SG_GENERAL, SG_DEBUG, "taxiway i = " << i ); + double size = taxiways[i].length * taxiways[i].width; + if ( size > max_size && !taxiways[i].generated ) { + SG_LOG( SG_GENERAL, SG_DEBUG, "taxiway max i = " << i ); + max_size = size; + largest_idx = i; + } + } + + if ( largest_idx >= 0 ) { + SG_LOG( SG_GENERAL, SG_DEBUG, "generating " << largest_idx ); + build_runway( taxiways[largest_idx], alt_m, + &rwy_polys, &texparams, &accum, + &apt_base, &apt_clearing ); + taxiways[largest_idx].generated = true; + } else { + SG_LOG( SG_GENERAL, SG_DEBUG, "done with taxiways." ); + done = true; + } + } +#else + /* Ralf Gerlich: Generate Taxiways in specified order from bottom to top */ + for ( i=0; i geodNodes; + for ( j = 0; j < nodes.get_node_list().size(); j++ ) { + Point3D node = nodes.get_node_list()[j]; + geodNodes.push_back( SGGeod::fromDegM( node.x(), node.y(), node.z() ) ); + } + base_txs.clear(); + base_txs = sgCalcTexCoords( b, geodNodes, tri_v ); + + base_tc.clear(); + for ( j = 0; j < (int)base_txs.size(); ++j ) { + SGVec2f tc = base_txs[j]; + // SG_LOG(SG_GENERAL, SG_DEBUG, "base_tc = " << tc); + index = texcoords.simple_add( Point3D( tc.x(), tc.y(), 0 ) ); + base_tc.push_back( index ); + } + tris_tc.push_back( base_tc ); + } + + // on rare occasion, one or more of the divided base points can be + // missed. Make sure they are all in the node list so we can + // build a proper skirt. + + for ( i = 0; i < divided_base.contours(); ++i ) { + for ( j = 0; j < divided_base.contour_size( i ); ++j ) { + nodes.unique_add( divided_base.get_pt(i, j) ); + } + } + + // Now that we have assembled all the airport geometry nodes into + // a list, calculate an "average" airport elevation based on all + // the actual airport node points. This is more useful than + // calculating an average over the entire airport surface because + // it avoids biases introduced from the surrounding area if the + // airport is located in a bowl or on a hill. + + double average = tgAverageElevation( root, elev_src, + nodes.get_node_list() ); + // cout << "average airport elevation = " << average << endl; + + // Now build the fitted airport surface ... + + // calculation min/max coordinates of airport area + Point3D min_deg(9999.0, 9999.0, 0), max_deg(-9999.0, -9999.0, 0); + for ( j = 0; j < (int)nodes.get_node_list().size(); ++j ) { + Point3D p = nodes.get_node_list()[j]; + if ( p.lon() < min_deg.lon() ) { + min_deg.setlon( p.lon() ); + } + if ( p.lon() > max_deg.lon() ) { + max_deg.setlon( p.lon() ); + } + if ( p.lat() < min_deg.lat() ) { + min_deg.setlat( p.lat() ); + } + if ( p.lat() > max_deg.lat() ) { + max_deg.setlat( p.lat() ); + } + } + + // extend the min/max coordinates of airport area to cover all + // lights as well + for ( i = 0; i < (int)rwy_lights.size(); ++i ) { + for ( j = 0; + j < (int)rwy_lights[i].get_poly().get_contour(0).size(); + ++j ) + { + Point3D p = rwy_lights[i].get_poly().get_contour(0)[j]; + if ( p.lon() < min_deg.lon() ) { + min_deg.setlon( p.lon() ); + } + if ( p.lon() > max_deg.lon() ) { + max_deg.setlon( p.lon() ); + } + if ( p.lat() < min_deg.lat() ) { + min_deg.setlat( p.lat() ); + } + if ( p.lat() > max_deg.lat() ) { + max_deg.setlat( p.lat() ); + } + } + } + + // Extend the area a bit so we don't have wierd things on the edges + double dlon = max_deg.lon() - min_deg.lon(); + double dlat = max_deg.lat() - min_deg.lat(); + min_deg.setlon( min_deg.lon() - 0.01 * dlon ); + max_deg.setlon( max_deg.lon() + 0.01 * dlon ); + min_deg.setlat( min_deg.lat() - 0.01 * dlat ); + max_deg.setlat( max_deg.lat() + 0.01 * dlat ); + cout << "min = " << min_deg << " max = " << max_deg << endl; + + TGAptSurface apt_surf( root, elev_src, min_deg, max_deg, average ); + SG_LOG(SG_GENERAL, SG_INFO, "Airport surface created"); + + // calculate node elevations + SG_LOG(SG_GENERAL, SG_INFO, "Computing airport node elevations"); + point_list geod_nodes = calc_elevations( apt_surf, + nodes.get_node_list(), + 0.0 ); + divided_base = calc_elevations( apt_surf, divided_base, 0.0 ); + SG_LOG(SG_GENERAL, SG_DEBUG, "DIVIDED"); + SG_LOG(SG_GENERAL, SG_DEBUG, divided_base); + + SG_LOG(SG_GENERAL, SG_DEBUG, "Done with base calc_elevations()"); + + SG_LOG(SG_GENERAL, SG_INFO, "Computing beacon node elevations"); + point_list beacon_nodes = calc_elevations( apt_surf, beacons, 0.0 ); + SG_LOG(SG_GENERAL, SG_INFO, "Computing tower node elevations"); + point_list tower_nodes = calc_elevations( apt_surf, towers, 0.0 ); + SG_LOG(SG_GENERAL, SG_INFO, "Computing windsock node elevations"); + point_list windsock_nodes = calc_elevations( apt_surf, windsocks, 0.0 ); + + // add base skirt (to hide potential cracks) + // + // this has to happen after we've calculated the node elevations + // but before we convert to wgs84 coordinates + int uindex, lindex; + + for ( i = 0; i < divided_base.contours(); ++i ) { + strip_v.clear(); + strip_n.clear(); + strip_tc.clear(); + + // prime the pump ... + p = divided_base.get_pt( i, 0 ); + uindex = nodes.find( p ); + if ( uindex >= 0 ) { + Point3D lower = geod_nodes[uindex] - Point3D(0, 0, 20); + SG_LOG(SG_GENERAL, SG_DEBUG, geod_nodes[uindex] << " <-> " << lower); + lindex = nodes.simple_add( lower ); + geod_nodes.push_back( lower ); + strip_v.push_back( lindex ); + strip_v.push_back( uindex ); + + // use 'the' normal. We are pushing on two nodes so we + // need to push on two normals. + index = normals.unique_add( vn ); + strip_n.push_back( index ); + strip_n.push_back( index ); + } else { + string message = "Ooops missing node when building skirt (in init)"; + SG_LOG( SG_GENERAL, SG_INFO, message << " " << p ); + throw sg_exception( message ); + } + + // loop through the list + for ( j = 1; j < divided_base.contour_size(i); ++j ) { + p = divided_base.get_pt( i, j ); + uindex = nodes.find( p ); + if ( uindex >= 0 ) { + Point3D lower = geod_nodes[uindex] - Point3D(0, 0, 20); + SG_LOG(SG_GENERAL, SG_DEBUG, geod_nodes[uindex] << " <-> " << lower); + lindex = nodes.simple_add( lower ); + geod_nodes.push_back( lower ); + strip_v.push_back( lindex ); + strip_v.push_back( uindex ); + + index = normals.unique_add( vn ); + strip_n.push_back( index ); + strip_n.push_back( index ); + } else { + string message + = "Ooops missing node when building skirt (in loop)"; + SG_LOG( SG_GENERAL, SG_INFO, message << " " << p ); + throw sg_exception( message ); + } + } + + // close off the loop + p = divided_base.get_pt( i, 0 ); + uindex = nodes.find( p ); + if ( uindex >= 0 ) { + Point3D lower = geod_nodes[uindex] - Point3D(0, 0, 20); + SG_LOG(SG_GENERAL, SG_DEBUG, geod_nodes[uindex] << " <-> " << lower); + lindex = nodes.simple_add( lower ); + geod_nodes.push_back( lower ); + strip_v.push_back( lindex ); + strip_v.push_back( uindex ); + + index = normals.unique_add( vn ); + strip_n.push_back( index ); + strip_n.push_back( index ); + } else { + string message = "Ooops missing node when building skirt (at end)"; + SG_LOG( SG_GENERAL, SG_INFO, message << " " << p ); + throw sg_exception( message ); + } + + strips_v.push_back( strip_v ); + strips_n.push_back( strip_n ); + strip_materials.push_back( "Grass" ); + + std::vector < SGGeod > geodNodes; + for ( j = 0; j < nodes.get_node_list().size(); j++ ) { + Point3D node = nodes.get_node_list()[j]; + geodNodes.push_back( SGGeod::fromDegM( node.x(), node.y(), node.z() ) ); + } + base_txs.clear(); + base_txs = sgCalcTexCoords( b, geodNodes, strip_v ); + + base_tc.clear(); + for ( j = 0; j < (int)base_txs.size(); ++j ) { + SGVec2f tc = base_txs[j]; + // SG_LOG(SG_GENERAL, SG_DEBUG, "base_tc = " << tc); + index = texcoords.simple_add( Point3D( tc.x(), tc.y(), 0 ) ); + base_tc.push_back( index ); + } + strips_tc.push_back( base_tc ); + } + + // add light points + + superpoly_list tmp_light_list; tmp_light_list.clear(); + typedef map < string, double, less > elev_map_type; + typedef elev_map_type::const_iterator const_elev_map_iterator; + elev_map_type elevation_map; + + SG_LOG(SG_GENERAL, SG_INFO, + "Computing runway/approach lighting elevations"); + + // pass one, calculate raw elevations from Array + + for ( i = 0; i < (int)rwy_lights.size(); ++i ) { + TGTriNodes light_nodes; + light_nodes.clear(); + point_list lights_v = rwy_lights[i].get_poly().get_contour(0); + for ( j = 0; j < (int)lights_v.size(); ++j ) { + p = lights_v[j]; + index = light_nodes.simple_add( p ); + } + + // calculate light node elevations + + point_list geod_light_nodes + = calc_elevations( apt_surf, light_nodes.get_node_list(), 0.5 ); + TGPolygon p; + p.add_contour( geod_light_nodes, 0 ); + TGSuperPoly s; + s.set_poly( p ); + tmp_light_list.push_back( s ); + + string flag = rwy_lights[i].get_flag(); + if ( flag != (string)"" ) { + double max = -9999; + const_elev_map_iterator it = elevation_map.find( flag ); + if ( it != elevation_map.end() ) { + max = elevation_map[flag]; + } + for ( j = 0; j < (int)geod_light_nodes.size(); ++j ) { + if ( geod_light_nodes[j].z() > max ) { + max = geod_light_nodes[j].z(); + } + } + elevation_map[flag] = max; + SG_LOG( SG_GENERAL, SG_DEBUG, flag << " max = " << max ); + } + } + + SG_LOG(SG_GENERAL, SG_INFO, "Done with lighting calc_elevations()"); + + // pass two, for each light group check if we need to lift (based + // on flag) and do so, then output next structures. + for ( i = 0; i < (int)rwy_lights.size(); ++i ) { + // tmp_light_list is a parallel structure to rwy_lights + point_list geod_light_nodes + = tmp_light_list[i].get_poly().get_contour(0); + +#if 0 + // This code forces the elevation of all the approach lighting + // components for a particular runway end up to the highest + // max elevation for any of the points. That can cause other + // problem so let's nuke code this for the moment. + string flag = rwy_lights[i].get_flag(); + if ( flag != (string)"" ) { + const_elev_map_iterator it = elevation_map.find( flag ); + if ( it != elevation_map.end() ) { + double force_elev = elevation_map[flag]; + for ( j = 0; j < (int)geod_light_nodes.size(); ++j ) { + geod_light_nodes[j].setz( force_elev ); + } + } + } +#endif + + // this is a little round about, but what we want to calculate the + // light node elevations as ground + an offset so we do them + // seperately, then we add them back into nodes to get the index + // out, but also add them to geod_nodes to maintain consistancy + // between these two lists. + point_list light_normals = rwy_lights[i].get_normals().get_contour(0); + pt_v.clear(); + pt_n.clear(); + for ( j = 0; j < (int)geod_light_nodes.size(); ++j ) { + p = geod_light_nodes[j]; + index = nodes.simple_add( p ); + pt_v.push_back( index ); + geod_nodes.push_back( p ); + + index = normals.unique_add( light_normals[j] ); + pt_n.push_back( index ); + } + pts_v.push_back( pt_v ); + pts_n.push_back( pt_n ); + pt_materials.push_back( rwy_lights[i].get_material() ); + } + + // calculate wgs84 mapping of nodes + std::vector< SGVec3d > wgs84_nodes; + for ( i = 0; i < (int)geod_nodes.size(); ++i ) { + SGGeod geod = SGGeod::fromDegM( geod_nodes[i].x(), geod_nodes[i].y(), geod_nodes[i].z() ); + SG_LOG(SG_GENERAL, SG_DEBUG, "geod pt = " << geod_nodes[i] ); + SGVec3d cart = SGVec3d::fromGeod(geod); + SG_LOG(SG_GENERAL, SG_DEBUG, " cart pt = " << cart ); + wgs84_nodes.push_back( cart ); + } + SGSphered d; + for ( i = 0; i < wgs84_nodes.size(); ++i ) { + d.expandBy(wgs84_nodes[ i ]); + } + + SGVec3d gbs_center = d.getCenter(); + double gbs_radius = d.getRadius(); + cout << "gbs center = " << gbs_center << endl; + SG_LOG(SG_GENERAL, SG_DEBUG, "Done with wgs84 node mapping"); + SG_LOG(SG_GENERAL, SG_DEBUG, " center = " << gbs_center + << " radius = " << gbs_radius ); + + // null structures + group_list fans_v; fans_v.clear(); + group_list fans_n; fans_n.clear(); + group_list fans_tc; fans_tc.clear(); + string_list fan_materials; fan_materials.clear(); + + string objpath = root + "/AirportObj"; + string name = airport_id + ".btg"; + + std::vector< SGVec3f > normals_3f; + for ( i=0; i < normals.get_node_list().size(); i++ ) { + Point3D node = normals.get_node_list()[i]; + normals_3f.push_back( node.toSGVec3f() ); + } + + std::vector< SGVec2f > texcoords_2f; + for ( i=0; i < texcoords.get_node_list().size(); i++ ) { + Point3D node = texcoords.get_node_list()[i]; + texcoords_2f.push_back( node.toSGVec2f() ); + } + + SGBinObject obj; + + obj.set_gbs_center( gbs_center ); + obj.set_gbs_radius( gbs_radius ); + obj.set_wgs84_nodes( wgs84_nodes ); + obj.set_normals( normals_3f ); + obj.set_texcoords( texcoords_2f ); + obj.set_pts_v( pts_v ); + obj.set_pts_n( pts_n ); + obj.set_pt_materials( pt_materials ); + obj.set_tris_v( tris_v ); + obj.set_tris_n( tris_n ); + obj.set_tris_tc( tris_tc ); + obj.set_tri_materials( tri_materials ); + obj.set_strips_v( strips_v ); + obj.set_strips_n( strips_n ); + obj.set_strips_tc( strips_tc ); + obj.set_strip_materials( strip_materials ); + obj.set_fans_v( fans_v ); + obj.set_fans_n( fans_n ); + obj.set_fans_tc( fans_tc ); + obj.set_fan_materials( fan_materials ); + + bool result; + result = obj.write_bin( objpath, name, b ); + if ( !result ) { + throw sg_exception("error writing file. :-("); + } + +#if 0 + // checking result of write, remove the read_bin() before this + // goes into production + string file = objpath + "/" + b.gen_base_path() + "/" + name; + point_list tmp_texcoords = texcoords.get_node_list(); + sgReadBinObj( file, gbs_center, &gbs_radius, + wgs84_nodes, normals, + tmp_texcoords, + tris_v, tris_tc, tri_materials, + strips_v, strips_tc, strip_materials, + fans_v, fans_tc, fan_materials ); +#endif + + // write out airport object reference + write_index( objpath, b, name ); + + // write out beacon references + for ( i = 0; i < (int)beacon_nodes.size(); ++i ) { + write_index_shared( objpath, b, beacon_nodes[i], + "Models/Airport/beacon.xml", + 0.0 ); + } + + // write out tower references + for ( i = 0; i < (int)tower_nodes.size(); ++i ) { + write_index_shared( objpath, b, tower_nodes[i], + "Models/Airport/tower.xml", + 0.0 ); + } + + // write out windsock references + for ( i = 0; i < (int)windsock_nodes.size(); ++i ) { + if ( windsock_types[i] == 0 ) { + write_index_shared( objpath, b, windsock_nodes[i], + "Models/Airport/windsock.xml", + 0.0 ); + } else { + write_index_shared( objpath, b, windsock_nodes[i], + "Models/Airport/windsock_lit.xml", + 0.0 ); + } + } + + string holepath = root + "/AirportArea"; + // long int poly_index = poly_index_next(); + // write_boundary( holepath, b, hull, poly_index ); + tgChopNormalPolygon( holepath, "Hole", divided_base, true ); + tgChopNormalPolygon( holepath, "Airport", apt_clearing, false ); + +} + diff --git a/src/Airports/GenAirports850/build.hxx b/src/Airports/GenAirports850/build.hxx new file mode 100644 index 00000000..d1afcab2 --- /dev/null +++ b/src/Airports/GenAirports850/build.hxx @@ -0,0 +1,49 @@ +// build.hxx -- routines to build polygon model of an airport from the runway +// definition +// +// Written by Curtis Olson, started September 1999. +// +// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: build.hxx,v 1.10 2004-11-19 22:25:49 curt Exp $ +// + + +#ifndef _BUILD_HXX +#define _BUILD_HXX + + +#include + +#include "global.hxx" +#include "point2d.hxx" + + +// build 3d airport +void build_airport( string airport_id, float alt_m, + string_list& runways_raw, + string_list& beacons_raw, + string_list& towers_raw, + string_list& windsocks_raw, + const string& root, + const string_list& elev_src ); + + + +#endif // _BUILD_HXX + + diff --git a/src/Airports/GenAirports850/convex_hull.cxx b/src/Airports/GenAirports850/convex_hull.cxx new file mode 100644 index 00000000..62df7b87 --- /dev/null +++ b/src/Airports/GenAirports850/convex_hull.cxx @@ -0,0 +1,252 @@ +// convex_hull.cxx -- calculate the convex hull of a set of points +// +// Written by Curtis Olson, started September 1998. +// +// Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: convex_hull.cxx,v 1.12 2004-11-19 22:25:49 curt Exp $ +// + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include + +#include +#include +#include + +#include + +#include "convex_hull.hxx" +#include "point2d.hxx" + +using std::less; +using std::map; + + +// stl map typedefs +typedef map < double, double, less > map_container; +typedef map_container::iterator map_iterator; + + +// Calculate theta of angle (a, b, c) +double calc_angle(Point3D a, Point3D b, Point3D c) { + Point3D u, v; + double udist, vdist, uv_dot, tmp; + + // u . v = ||u|| * ||v|| * cos(theta) + + u.setx( b.x() - a.x() ); + u.sety( b.y() - a.y() ); + udist = sqrt( u.x() * u.x() + u.y() * u.y() ); + // printf("udist = %.6f\n", udist); + + v.setx( b.x() - c.x() ); + v.sety( b.y() - c.y() ); + vdist = sqrt( v.x() * v.x() + v.y() * v.y() ); + // printf("vdist = %.6f\n", vdist); + + uv_dot = u.x() * v.x() + u.y() * v.y(); + // printf("uv_dot = %.6f\n", uv_dot); + + tmp = uv_dot / (udist * vdist); + // printf("tmp = %.6f\n", tmp); + + return acos(tmp); +} + + +// Test to see if angle(Pa, Pb, Pc) < 180 degrees +bool test_point(Point3D Pa, Point3D Pb, Point3D Pc) { + double a1, a2; + + Point3D origin( 0.0 ); + + Point3D a( cos(Pa.y()) * Pa.x(), + sin(Pa.y()) * Pa.x(), 0 ); + + Point3D b( cos(Pb.y()) * Pb.x(), + sin(Pb.y()) * Pb.x(), 0 ); + + Point3D c( cos(Pc.y()) * Pc.x(), + sin(Pc.y()) * Pc.x(), 0 ); + + // printf("a is %.6f %.6f\n", a.x, a.y); + // printf("b is %.6f %.6f\n", b.x, b.y); + // printf("c is %.6f %.6f\n", c.x, c.y); + + a1 = calc_angle(a, b, origin); + a2 = calc_angle(origin, b, c); + + // printf("a1 = %.2f a2 = %.2f\n", a1 * SGD_RADIANS_TO_DEGREES, a2 * SGD_RADIANS_TO_DEGREES); + + return ( (a1 + a2) < SGD_PI ); +} + + +// calculate the convex hull of a set of points, return as a list of +// point2d. The algorithm description can be found at: +// http://riot.ieor.berkeley.edu/riot/Applications/ConvexHull/CHDetails.html +TGPolygon convex_hull( const point_list& input_list ) { + int i; + + map_iterator map_current, map_next, map_next_next, map_last; + + // list of translated points + point_list trans_list; + + // points sorted by radian degrees + map_container radians_map; + + // will contain the convex hull + TGPolygon con_hull; + + Point3D p, Pa, Pb, Pc, result; + double sum_x, sum_y; + int in_count, last_size; + + // STEP ONE: Find an average midpoint of the input set of points + in_count = input_list.size(); + sum_x = sum_y = 0.0; + + for ( i = 0; i < in_count; ++i ) { + sum_x += input_list[i].x(); + sum_y += input_list[i].y(); + } + + Point3D average( sum_x / in_count, sum_y / in_count, 0 ); + + // printf("Average center point is %.4f %.4f\n", average.x, average.y); + + // STEP TWO: Translate input points so average is at origin + trans_list.clear(); + + for ( i = 0; i < in_count; ++i ) { + p = Point3D( input_list[i].x() - average.x(), + input_list[i].y() - average.y(), 0 ); + // printf("%.6f %.6f\n", p.x, p.y); + trans_list.push_back( p ); + } + + // STEP THREE: convert to radians and sort by theta + radians_map.clear(); + + for ( i = 0; i < in_count; ++i ) { + p = cart_to_polar_2d( trans_list[i] ); + if ( p.x() > radians_map[p.y()] ) { + radians_map[p.y()] = p.x(); + } + } + + /* + // printf("Sorted list\n"); + map_current = radians_map.begin(); + map_last = radians_map.end(); + for ( ; map_current != map_last ; ++map_current ) { + p.setx( (*map_current).first ); + p.sety( (*map_current).second ); + + printf("p is %.6f %.6f\n", p.x(), p.y()); + } + */ + + // STEP FOUR: traverse the sorted list and eliminate everything + // not on the perimeter. + // printf("Traversing list\n"); + + // double check list size ... this should never fail because a + // single runway will always generate four points. + if ( radians_map.size() < 3 ) { + throw sg_exception("convex hull not possible with < 3 points"); + } + + // ensure that we run the while loop at least once + last_size = radians_map.size() + 1; + + while ( last_size > (int)radians_map.size() ) { + // printf("Running an iteration of the graham scan algorithm\n"); + last_size = radians_map.size(); + + map_current = radians_map.begin(); + while ( map_current != radians_map.end() ) { + // get first element + Pa.sety( (*map_current).first ); + Pa.setx( (*map_current).second ); + + // get second element + map_next = map_current; + ++map_next; + if ( map_next == radians_map.end() ) { + map_next = radians_map.begin(); + } + Pb.sety( (*map_next).first ); + Pb.setx( (*map_next).second ); + + // get third element + map_next_next = map_next; + ++map_next_next; + if ( map_next_next == radians_map.end() ) { + map_next_next = radians_map.begin(); + } + Pc.sety( (*map_next_next).first ); + Pc.setx( (*map_next_next).second ); + + // printf("Pa is %.6f %.6f\n", Pa.y(), Pa.x()); + // printf("Pb is %.6f %.6f\n", Pb.y(), Pb.x()); + // printf("Pc is %.6f %.6f\n", Pc.y(), Pc.x()); + + if ( test_point(Pa, Pb, Pc) ) { + // printf("Accepted a point\n"); + // accept point, advance Pa, Pb, and Pc. + ++map_current; + } else { + // printf("REJECTED A POINT\n"); + // reject point, delete it and advance only Pb and Pc + map_next = map_current; + ++map_next; + if ( map_next == radians_map.end() ) { + map_next = radians_map.begin(); + } + radians_map.erase( map_next ); + } + } + } + + // translate back to correct lon/lat + // printf("Final sorted convex hull\n"); + con_hull.erase(); + map_current = radians_map.begin(); + map_last = radians_map.end(); + for ( ; map_current != map_last ; ++map_current ) { + p.sety( (*map_current).first ); + p.setx( (*map_current).second ); + + result.setx( cos(p.y()) * p.x() + average.x() ); + result.sety( sin(p.y()) * p.x() + average.y() ); + + // printf("%.6f %.6f\n", result.x, result.y); + + con_hull.add_node(0, result); + } + + return con_hull; +} diff --git a/src/Airports/GenAirports850/convex_hull.hxx b/src/Airports/GenAirports850/convex_hull.hxx new file mode 100644 index 00000000..de863c34 --- /dev/null +++ b/src/Airports/GenAirports850/convex_hull.hxx @@ -0,0 +1,47 @@ +// convex_hull.hxx -- calculate the convex hull of a set of points +// +// Written by Curtis Olson, started September 1998. +// +// Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: convex_hull.hxx,v 1.5 2004-11-19 22:25:49 curt Exp $ +// + + +#ifndef _CONVEX_HULL_HXX +#define _CONVEX_HULL_HXX + + +#include + +#include + +#include + +#include "point2d.hxx" + + + +// calculate the convex hull of a set of points, return as a list of +// point2d. The algorithm description can be found at: +// http://riot.ieor.berkeley.edu/riot/Applications/ConvexHull/CHDetails.html +TGPolygon convex_hull( const point_list& input_list ); + + +#endif // _CONVEX_HULL_HXX + + diff --git a/src/Airports/GenAirports850/elevations.cxx b/src/Airports/GenAirports850/elevations.cxx new file mode 100644 index 00000000..39971073 --- /dev/null +++ b/src/Airports/GenAirports850/elevations.cxx @@ -0,0 +1,282 @@ +// elevations.cxx -- routines to help calculate DEM elevations for a +// set of points +// +// Written by Curtis Olson, started April 2004. +// +// Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: elevations.cxx,v 1.8 2005-12-19 16:51:25 curt Exp $ +// + +#ifdef HAVE_CONFIG_H +# include +#endif + +// libnewmat includes and defines +#define WANT_STREAM // include.h will get stream fns +#define WANT_MATH // include.h will get math fns + // newmatap.h will get include.h +#include // need matrix applications +#include // need matrix output routines + +#include +#include +#include +#include + +#include + +#include "global.hxx" +#include "apt_surface.hxx" + + +// lookup node elevations for each point in the point_list. Returns +// average of all points. Doesn't modify the original list. + +double tgAverageElevation( const string &root, const string_list elev_src, + const point_list points_source ) +{ + bool done = false; + unsigned int i; + TGArray array; + + // make a copy so our routine is non-destructive. + point_list points = points_source; + + // just bail if no work to do + if ( points.size() == 0 ) { + return 0.0; + } + + // set all elevations to -9999 + for ( i = 0; i < points.size(); ++i ) { + points[i].setz( -9999.0 ); + } + + while ( !done ) { + // find first node with -9999 elevation + Point3D first(0.0); + bool found_one = false; + for ( i = 0; i < points.size(); ++i ) { + if ( points[i].z() < -9000.0 && !found_one ) { + first = points[i]; + found_one = true; + } + } + + if ( found_one ) { + SGBucket b( first.x(), first.y() ); + string base = b.gen_base_path(); + + // try the various elevation sources + i = 0; + bool found_file = false; + while ( !found_file && i < elev_src.size() ) { + string array_path = root + "/" + elev_src[i] + "/" + base + + "/" + b.gen_index_str(); + if ( array.open(array_path) ) { + found_file = true; + SG_LOG( SG_GENERAL, SG_DEBUG, "Using array_path = " + << array_path ); + } + i++; + } + + // this will fill in a zero structure if no array data + // found/opened + array.parse( b ); + + // this will do a hasty job of removing voids by inserting + // data from the nearest neighbor (sort of) + array.remove_voids(); + + // update all the non-updated elevations that are inside + // this array file + double elev; + done = true; + for ( i = 0; i < points.size(); ++i ) { + if ( points[i].z() < -9000.0 ) { + done = false; + elev = array.altitude_from_grid( points[i].lon() * 3600.0, + points[i].lat() * 3600.0 ); + if ( elev > -9000 ) { + points[i].setz( elev ); + // cout << "interpolating for " << p << endl; + // cout << p.x() << " " << p.y() << " " << p.z() + // << endl; + } + } + } + + array.close(); + } else { + done = true; + } + } + + // now find the average height of the queried points + double total = 0.0; + int count = 0; + for ( i = 0; i < points.size(); ++i ) { + total += points[i].z(); + count++; + } + double average = total / (double) count; + SG_LOG(SG_GENERAL, SG_DEBUG, "Average surface height of point list = " + << average); + + return average; +} + + +// lookup node elevations for each point in the specified simple +// matrix. Returns average of all points. + +void tgCalcElevations( const string &root, const string_list elev_src, + SimpleMatrix &Pts, const double average ) +{ + bool done = false; + int i, j; + TGArray array; + + // just bail if no work to do + if ( Pts.rows() == 0 || Pts.cols() == 0 ) { + return; + } + + // set all elevations to -9999 + for ( j = 0; j < Pts.rows(); ++j ) { + for ( i = 0; i < Pts.cols(); ++i ) { + Point3D p = Pts.element(i, j); + p.setz( -9999.0 ); + Pts.set(i, j, p); + } + } + + while ( !done ) { + // find first node with -9999 elevation + Point3D first(0.0); + bool found_one = false; + for ( j = 0; j < Pts.rows(); ++j ) { + for ( i = 0; i < Pts.cols(); ++i ) { + Point3D p = Pts.element(i,j); + if ( p.z() < -9000.0 && !found_one ) { + first = p; + found_one = true; + } + } + } + + if ( found_one ) { + SGBucket b( first.x(), first.y() ); + string base = b.gen_base_path(); + + // try the various elevation sources + j = 0; + bool found_file = false; + while ( !found_file && j < (int)elev_src.size() ) { + string array_path = root + "/" + elev_src[j] + "/" + base + + "/" + b.gen_index_str(); + if ( array.open(array_path) ) { + found_file = true; + SG_LOG( SG_GENERAL, SG_DEBUG, "Using array_path = " + << array_path ); + } + j++; + } + + // this will fill in a zero structure if no array data + // found/opened + array.parse( b ); + + // this will do a hasty job of removing voids by inserting + // data from the nearest neighbor (sort of) + array.remove_voids(); + + // update all the non-updated elevations that are inside + // this array file + double elev; + done = true; + for ( j = 0; j < Pts.rows(); ++j ) { + for ( i = 0; i < Pts.cols(); ++i ) { + Point3D p = Pts.element(i,j); + if ( p.z() < -9000.0 ) { + done = false; + elev = array.altitude_from_grid( p.x() * 3600.0, + p.y() * 3600.0 ); + if ( elev > -9000 ) { + p.setz( elev ); + Pts.set(i, j, p); + // cout << "interpolating for " << p << endl; + // cout << p.x() << " " << p.y() << " " << p.z() + // << endl; + } + } + } + } + + array.close(); + } else { + done = true; + } + } + + // do some post processing for sanity's sake + + // find the average height of the queried points + double total = 0.0; + int count = 0; + for ( j = 0; j < Pts.rows(); ++j ) { + for ( i = 0; i < Pts.cols(); ++i ) { + Point3D p = Pts.element(i,j); + total += p.z(); + count++; + } + } + double grid_average = total / (double) count; + SG_LOG(SG_GENERAL, SG_DEBUG, "Average surface height of matrix = " + << grid_average); +} + + +// clamp all elevations to the specified range + +void tgClampElevations( SimpleMatrix &Pts, + double center_m, double max_clamp_m ) +{ + int i, j; + + // go through the elevations and clamp all elevations to within + // +/-max_m of the center_m elevation. + for ( j = 0; j < Pts.rows(); ++j ) { + for ( i = 0; i < Pts.cols(); ++i ) { + Point3D p = Pts.element(i,j); + if ( p.z() < center_m - max_clamp_m ) { + SG_LOG(SG_GENERAL, SG_DEBUG, " clamping " << p.z() + << " to " << center_m - max_clamp_m ); + p.setz( center_m - max_clamp_m ); + Pts.set(i, j, p); + } + if ( p.z() > center_m + max_clamp_m ) { + SG_LOG(SG_GENERAL, SG_DEBUG, " clamping " << p.z() + << " to " << center_m + max_clamp_m ); + p.setz( center_m + max_clamp_m ); + Pts.set(i, j, p); + } + } + } +} diff --git a/src/Airports/GenAirports850/elevations.hxx b/src/Airports/GenAirports850/elevations.hxx new file mode 100644 index 00000000..c1cc0754 --- /dev/null +++ b/src/Airports/GenAirports850/elevations.hxx @@ -0,0 +1,56 @@ +// elevations.hxx -- routines to help calculate DEM elevations for a +// set of points +// +// Written by Curtis Olson, started April 2004. +// +// Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: elevations.hxx,v 1.4 2005-09-09 15:05:15 curt Exp $ +// + + +// libnewmat includes and defines +#define WANT_STREAM // include.h will get stream fns +#define WANT_MATH // include.h will get math fns + // newmatap.h will get include.h +#include // need matrix applications +#include // need matrix output routines + +#include +#include +#include +#include + +#include + +#include "global.hxx" +#include "apt_surface.hxx" + + +// lookup node elevations for each point in the point_list. Returns +// average of all points. Doesn't modify the original list. +double tgAverageElevation( const string &root, const string_list elev_src, + const point_list points_source ); + +// lookup node elevations for each point in the specified nurbs++ +// matrix. +void tgCalcElevations( const string &root, const string_list elev_src, + SimpleMatrix &Pts, double average ); + +// clamp all elevations to the specified range +void tgClampElevations( SimpleMatrix &Pts, + double center_m, double max_clamp_m ); diff --git a/src/Airports/GenAirports850/global.hxx b/src/Airports/GenAirports850/global.hxx new file mode 100644 index 00000000..6b303fc9 --- /dev/null +++ b/src/Airports/GenAirports850/global.hxx @@ -0,0 +1,45 @@ +// global.hxx -- kind of dumb but oh well... +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: global.hxx,v 1.9 2005-10-31 18:43:27 curt Exp $ +// + + +#ifndef _GEN_AIRPORT_GLOBAL_HXX +#define _GEN_AIRPORT_GLOBAL_HXX + + +extern int nudge; + +// Final grid size for airport surface (in meters) +const double coarse_grid = 300.0; + +// compared to the average surface elevation, clamp all values within +// this many meters of the average +const double max_clamp = 100.0; + +// maximum slope (rise/run) allowed on an airport surface +extern double slope_max; // = 0.02; +const double slope_eps = 0.00001; + +// nurbs query/search epsilon +const double nurbs_eps = 0.0000001; + +#endif // _GEN_AIRPORT_GLOBAL_HXX diff --git a/src/Airports/GenAirports850/lights.cxx b/src/Airports/GenAirports850/lights.cxx new file mode 100644 index 00000000..b9489d8b --- /dev/null +++ b/src/Airports/GenAirports850/lights.cxx @@ -0,0 +1,2973 @@ +// lights.cxx -- Generate runway lighting +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: lights.cxx,v 1.41 2005-12-19 16:51:25 curt Exp $ +// + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include + +#include +#include + +#include "lights.hxx" + +using std::cout; +using std::endl; +using std::string; + + +// calculate the runway light direction vector. We take the center of +// one runway end - the center of the other end to get the direction +// of the runway. Combine this with an appropriate portion of the +// local 'up' vector based on the provide 'angle' gives the light +// direction vector for the runway. +static Point3D gen_runway_light_vector( const TGRunway& rwy_info, + double angle, bool recip ) { + double length; + + // Generate the 4 corners of the runway + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 0.0, 0.0, 0.0, 0.0 ); + point_list corner; + for ( int i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + Point3D end1, end2; + if ( !recip ) { + end1 = (corner[0] + corner[1]) / 2.0; + end2 = (corner[2] + corner[3]) / 2.0; + } else { + end2 = (corner[0] + corner[1]) / 2.0; + end1 = (corner[2] + corner[3]) / 2.0; + } + Point3D cart1 = sgGeodToCart( end1 * SG_DEGREES_TO_RADIANS ); + Point3D cart2 = sgGeodToCart( end2 * SG_DEGREES_TO_RADIANS ); + SG_LOG(SG_GENERAL, SG_DEBUG, "cart1 = " << cart1 << " cart2 = " << cart2); + + Point3D up = cart1; + length = up.distance3D( Point3D(0.0) ); + up = up / length; + + Point3D rwy_vec = cart2 - cart1; + SG_LOG(SG_GENERAL, SG_DEBUG, "rwy_vec = " << rwy_vec); + + // angle up specified amount + length = rwy_vec.distance3D( Point3D(0.0) ); + double up_length = length * tan(angle * SG_DEGREES_TO_RADIANS); + Point3D light_vec = rwy_vec + (up * up_length); + + length = light_vec.distance3D( Point3D(0.0) ); + light_vec = light_vec / length; + + return light_vec; +} + + +// generate runway edge lighting +// 60 meters spacing or the next number down that divides evenly. +static superpoly_list gen_runway_edge_lights( const TGRunway& rwy_info, + const int kind, bool recip ) +{ + point_list w_lights; w_lights.clear(); + point_list y_lights; y_lights.clear(); + point_list w_normals; w_normals.clear(); + point_list y_normals; y_normals.clear(); + int i; + + double len = rwy_info.length * SG_FEET_TO_METER; + int divs = (int)(len / 60.0) + 1; + + Point3D normal = gen_runway_light_vector( rwy_info, 3.0, recip ); + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 2.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 2.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + Point3D inc1, inc2; + Point3D pt1, pt2; + + if ( recip ) { + inc1 = (corner[3] - corner[0]) / divs; + inc2 = (corner[2] - corner[1]) / divs; + pt1 = corner[0]; + pt2 = corner[1]; + } else { + inc1 = (corner[0] - corner[3]) / divs; + inc2 = (corner[1] - corner[2]) / divs; + pt1 = corner[3]; + pt2 = corner[2]; + } + + double dist = rwy_info.length; + double step = dist / divs; + + w_lights.push_back( pt1 ); + w_normals.push_back( normal ); + w_lights.push_back( pt2 ); + w_normals.push_back( normal ); + dist -= step; + + for ( i = 0; i < divs; ++i ) { + pt1 += inc1; + pt2 += inc2; + if ( dist > 2000.0 ) { + w_lights.push_back( pt1 ); + w_normals.push_back( normal ); + w_lights.push_back( pt2 ); + w_normals.push_back( normal ); + } else { + y_lights.push_back( pt1 ); + y_normals.push_back( normal ); + y_lights.push_back( pt2 ); + y_normals.push_back( normal ); + } + dist -= step; + } + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( w_lights, false ); + normals_poly.add_contour( w_normals, false ); + + TGSuperPoly white; + white.set_poly( lights_poly ); + white.set_normals( normals_poly ); + white.set_material( "RWY_WHITE_LIGHTS" ); + /* other intensities for when the data file supports it + * + * white.set_material( "RWY_WHITE_MEDIUM_LIGHTS" ); + * white.set_material( "RWY_WHITE_LOW_LIGHTS" ); + */ + + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( y_lights, false ); + normals_poly.add_contour( y_normals, false ); + + TGSuperPoly yellow; + yellow.set_poly( lights_poly ); + yellow.set_normals( normals_poly ); + yellow.set_material( "RWY_YELLOW_LIGHTS" ); + /* other intensities for when the data file supports it + * + * yellow.set_material( "RWY_YELLOW_MEDIUM_LIGHTS" ); + * yellow.set_material( "RWY_YELLOW_LOW_LIGHTS" ); + */ + + superpoly_list result; result.clear(); + + result.push_back( white ); + result.push_back( yellow ); + + return result; +} + + +// generate taxiway edge lighting +// 100 meters spacing or the next number down that divides evenly. +static superpoly_list gen_taxiway_edge_lights( const TGRunway& rwy_info, + const int kind, bool recip ) +{ + point_list b_lights; b_lights.clear(); + point_list b_normals; b_normals.clear(); + int i; + + double len = rwy_info.length * SG_FEET_TO_METER; + int divs; + if ( len > 100.0 ) { + // for lengths of 300' or more, max spacing is 200' + divs = (int)(len / 70.0) + 1; + } else { + // for lengths <= 300', max spacing = 100' + divs = (int)(len / 35.0) + 1; + } + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 2.0, 0.0, 0.0, 2.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + Point3D inc1, inc2; + Point3D pt1, pt2; + + if ( recip ) { + inc1 = (corner[3] - corner[0]) / divs; + inc2 = (corner[2] - corner[1]) / divs; + pt1 = corner[0]; + pt2 = corner[1]; + } else { + inc1 = (corner[0] - corner[3]) / divs; + inc2 = (corner[1] - corner[2]) / divs; + pt1 = corner[3]; + pt2 = corner[2]; + } + + double dist = rwy_info.length; + double step = dist / divs; + + Point3D up = sgGeodToCart( corner[0] * SG_DEGREES_TO_RADIANS ); + double length = up.distance3D( Point3D(0.0) ); + up = up / length; + + b_lights.push_back( pt1 ); + b_normals.push_back( up ); + b_lights.push_back( pt2 ); + b_normals.push_back( up ); + dist -= step; + + for ( i = 0; i < divs; ++i ) { + pt1 += inc1; + pt2 += inc2; + b_lights.push_back( pt1 ); + b_normals.push_back( up ); + b_lights.push_back( pt2 ); + b_normals.push_back( up ); + } + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( b_lights, false ); + normals_poly.add_contour( b_normals, false ); + + TGSuperPoly blue; + blue.set_poly( lights_poly ); + blue.set_normals( normals_poly ); + blue.set_material( "RWY_BLUE_TAXIWAY_LIGHTS" ); + + superpoly_list result; result.clear(); + + result.push_back( blue ); + + return result; +} + + +// generate threshold lights for a 3 degree approach +static superpoly_list gen_runway_threshold_lights( const TGRunway& rwy_info, + const int kind, + float alt_m, bool recip ) +{ + point_list g_lights; g_lights.clear(); + point_list g_normals; g_normals.clear(); + point_list r_lights; r_lights.clear(); + point_list r_normals; r_normals.clear(); + int i; + + cout << "gen threshold " << rwy_info.rwy_no << endl; + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 0.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 0.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + // determine the start point. + Point3D ref1, ref2, ref3, ref4; + double length_hdg, left_hdg; + double lon, lat, r; + if ( recip ) { + ref1 = corner[0]; + ref2 = corner[1]; + ref3 = corner[3]; + ref4 = corner[2]; + length_hdg = rwy_info.heading + 180.0; + if ( length_hdg > 360.0 ) { length_hdg -= 360.0; } + } else { + ref1 = corner[2]; + ref2 = corner[3]; + ref3 = corner[1]; + ref4 = corner[0]; + length_hdg = rwy_info.heading; + } + left_hdg = length_hdg - 90.0; + if ( left_hdg < 0 ) { left_hdg += 360.0; } + cout << "length hdg = " << length_hdg + << " left heading = " << left_hdg << endl; + + Point3D normal1 = gen_runway_light_vector( rwy_info, 3.0, recip ); + Point3D normal2 = gen_runway_light_vector( rwy_info, 3.0, !recip ); + + // offset 5' downwind + geo_direct_wgs_84 ( alt_m, ref1.lat(), ref1.lon(), length_hdg, + -5 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref1 = Point3D( lon, lat, 0.0 ); + geo_direct_wgs_84 ( alt_m, ref2.lat(), ref2.lon(), length_hdg, + -5 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref2 = Point3D( lon, lat, 0.0 ); + + // five lights for each side + for ( i = 0; i < 5; ++i ) { + g_lights.push_back( ref1 ); + g_normals.push_back( normal1 ); + + g_lights.push_back( ref2 ); + g_normals.push_back( normal1 ); + + r_lights.push_back( ref1 ); + r_normals.push_back( normal2 ); + + r_lights.push_back( ref2 ); + r_normals.push_back( normal2 ); + + // offset 10' towards center + geo_direct_wgs_84 ( alt_m, ref1.lat(), ref1.lon(), left_hdg, + -10 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref1 = Point3D( lon, lat, 0.0 ); + geo_direct_wgs_84 ( alt_m, ref2.lat(), ref2.lon(), left_hdg, + 10 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref2 = Point3D( lon, lat, 0.0 ); + } + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( g_lights, false ); + normals_poly.add_contour( g_normals, false ); + + TGSuperPoly green; + green.set_poly( lights_poly ); + green.set_normals( normals_poly ); + green.set_material( "RWY_GREEN_LIGHTS" ); + /* other intensities for when the data file supports it + * + * green.set_material( "RWY_GREEN_MEDIUM_LIGHTS" ); + * green.set_material( "RWY_GREEN_LOW_LIGHTS" ); + */ + + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( r_lights, false ); + normals_poly.add_contour( r_normals, false ); + + TGSuperPoly red; + red.set_poly( lights_poly ); + red.set_normals( normals_poly ); + red.set_material( "RWY_RED_LIGHTS" ); + /* other intensities for when the data file supports it + * + * red.set_material( "RWY_RED_MEDIUM_LIGHTS" ); + * red.set_material( "RWY_RED_LOW_LIGHTS" ); + */ + + superpoly_list result; result.clear(); + + result.push_back( green ); + result.push_back( red ); + + return result; +} + + +// generate runway center line lighting, 50' spacing. +static superpoly_list gen_runway_center_line_lights( const TGRunway& rwy_info, + bool recip ) +{ + point_list w_lights; w_lights.clear(); + point_list r_lights; r_lights.clear(); + point_list w_normals; w_normals.clear(); + point_list r_normals; r_normals.clear(); + int i; + + double len = rwy_info.length; + // this should be 25' technically but I'm trying 50' to space things out + int divs = (int)(len / (50.0*SG_FEET_TO_METER)) + 1; + + Point3D normal = gen_runway_light_vector( rwy_info, 3.0, recip ); + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 2.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 2.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + Point3D inc; + Point3D pt1, pt2; + + if ( recip ) { + pt1 = (corner[0] + corner[1] ) / 2.0; + pt2 = (corner[2] + corner[3] ) / 2.0; + } else { + pt1 = (corner[2] + corner[3] ) / 2.0; + pt2 = (corner[0] + corner[1] ) / 2.0; + } + inc = (pt2 - pt1) / divs; + + double dist = len; + double step = len / divs; + pt1 += inc; // move 25' in + dist -= step; + bool use_white = true; + + while ( dist > 0.0 ) { + if ( dist > 3000.0 ) { + w_lights.push_back( pt1 ); + w_normals.push_back( normal ); + } else if ( dist > 1000.0 ) { + if ( use_white ) { + w_lights.push_back( pt1 ); + w_normals.push_back( normal ); + } else { + r_lights.push_back( pt1 ); + r_normals.push_back( normal ); + } + use_white = !use_white; + } else { + r_lights.push_back( pt1 ); + r_normals.push_back( normal ); + } + pt1 += inc; + pt1 += inc; + dist -= step; + dist -= step; + } + + superpoly_list result; result.clear(); + + if ( w_lights.size() > 0 ) { + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( w_lights, false ); + normals_poly.add_contour( w_normals, false ); + + TGSuperPoly white; + white.set_poly( lights_poly ); + white.set_normals( normals_poly ); + white.set_material( "RWY_WHITE_MEDIUM_LIGHTS" ); + + result.push_back( white ); + } + + if ( r_lights.size() > 0 ) { + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( r_lights, false ); + normals_poly.add_contour( r_normals, false ); + + TGSuperPoly red; + red.set_poly( lights_poly ); + red.set_normals( normals_poly ); + red.set_material( "RWY_RED_MEDIUM_LIGHTS" ); + + result.push_back( red ); + } + + return result; +} + + +// generate runway center line lighting, 50' spacing. +static superpoly_list gen_taxiway_center_line_lights( const TGRunway& rwy_info, + bool recip ) +{ + point_list g_lights; g_lights.clear(); + point_list g_normals; g_normals.clear(); + int i; + + double len = rwy_info.length; + // this should be ??' technically but I'm trying 50' to space things out + int divs = (int)(len / 70) + 1; + + Point3D normal = gen_runway_light_vector( rwy_info, 3.0, recip ); + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 2.0, 0.0, 0.0, 2.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + Point3D inc; + Point3D pt1, pt2; + + if ( recip ) { + pt1 = (corner[0] + corner[1] ) / 2.0; + pt2 = (corner[2] + corner[3] ) / 2.0; + } else { + pt1 = (corner[2] + corner[3] ) / 2.0; + pt2 = (corner[0] + corner[1] ) / 2.0; + } + inc = (pt2 - pt1) / divs; + + double dist = len; + double step = len / divs; + pt1 += inc; // move 25' in + dist -= step; + + while ( dist > 0.0 ) { + g_lights.push_back( pt1 ); + g_normals.push_back( normal ); + + pt1 += inc; + pt1 += inc; + dist -= step; + dist -= step; + } + + superpoly_list result; result.clear(); + + if ( g_lights.size() > 0 ) { + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( g_lights, false ); + normals_poly.add_contour( g_normals, false ); + + TGSuperPoly green; + green.set_poly( lights_poly ); + green.set_normals( normals_poly ); + green.set_material( "RWY_GREEN_TAXIWAY_LIGHTS" ); + + result.push_back( green ); + } + + return result; +} + + +// generate touch down zone lights +static TGSuperPoly gen_touchdown_zone_lights( const TGRunway& rwy_info, + float alt_m, bool recip ) +{ + point_list lights; lights.clear(); + point_list normals; normals.clear(); + int i; + + cout << "gen touchdown zone lights " << rwy_info.rwy_no << endl; + + Point3D normal; + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 0.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 0.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + // determine the start point. + Point3D ref; + double length_hdg, left_hdg; + double lon, lat, r; + if ( recip ) { + ref = (corner[0] + corner[1]) / 2; + length_hdg = rwy_info.heading + 180.0; + if ( length_hdg > 360.0 ) { length_hdg -= 360.0; } + } else { + ref = (corner[2] + corner[3]) / 2; + length_hdg = rwy_info.heading; + } + left_hdg = length_hdg - 90.0; + if ( left_hdg < 0 ) { left_hdg += 360.0; } + cout << "length hdg = " << length_hdg + << " left heading = " << left_hdg << endl; + + normal = gen_runway_light_vector( rwy_info, 3.0, recip ); + + for ( i = 0; i < 30; ++i ) { + // offset 100' upwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + 100 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + Point3D pt1 = ref; + + // left side bar + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 36 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + + pt1 = ref; + + // right side bar + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + -36 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + -5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + -5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + } + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( lights, false ); + normals_poly.add_contour( normals, false ); + + TGSuperPoly result; + result.set_poly( lights_poly ); + result.set_normals( normals_poly ); + result.set_material( "RWY_WHITE_LIGHTS" ); + + return result; +} + + +// generate a simple 2 bar VASI +static TGSuperPoly gen_vasi( const TGRunway& rwy_info, float alt_m, + bool recip, TGPolygon *apt_base ) +{ + point_list lights; lights.clear(); + point_list normals; normals.clear(); + int i; + string flag; + double gs_angle = 3.0; + + cout << "gen vasi " << rwy_info.rwy_no << endl; + + Point3D normal; + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 0.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 0.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + // determine the start point. + Point3D ref; + double length_hdg, left_hdg; + double lon, lat, r; + if ( recip ) { + ref = corner[0]; + length_hdg = rwy_info.heading + 180.0; + if ( length_hdg > 360.0 ) { length_hdg -= 360.0; } + flag = rwy_info.rwy_no + "-i"; + gs_angle = rwy_info.gs_angle1; + } else { + ref = corner[2]; + length_hdg = rwy_info.heading; + flag = rwy_info.rwy_no; + gs_angle = rwy_info.gs_angle2; + } + + left_hdg = length_hdg - 90.0; + if ( left_hdg < 0 ) { left_hdg += 360.0; } + + if ( gs_angle < 0.5 ) { + gs_angle = 3.0; + } + + // offset 600' upwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + 600 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + // offset 50' left + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), left_hdg, + 50 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + // downwind bar + normal = gen_runway_light_vector( rwy_info, gs_angle - 0.5, recip ); + + // unit1 + Point3D pt1 = ref; + lights.push_back( pt1 ); + normals.push_back( normal ); + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 1 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + + // unit2 + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 16 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 1 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + + // unit3 + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 16 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 1 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + + // grass base + Point3D base_pt = (ref + pt1) / 2.0; + TGPolygon obj_base = gen_wgs84_area( base_pt, 15.0, 0.0, 0.0, 15.0, + length_hdg, alt_m, false ); + *apt_base = tgPolygonUnion( obj_base, *apt_base ); + + // upwind bar + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + 700 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + normal = gen_runway_light_vector( rwy_info, gs_angle, recip ); + + // unit1 + pt1 = ref; + lights.push_back( pt1 ); + normals.push_back( normal ); + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 1 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + + // unit2 + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 16 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 1 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + + // unit3 + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 16 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 1 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normals.push_back( normal ); + + // grass base + base_pt = (ref + pt1) / 2.0; + obj_base = gen_wgs84_area( base_pt, 15.0, 0.0, 0.0, 15.0, + length_hdg, alt_m, false ); + *apt_base = tgPolygonUnion( obj_base, *apt_base ); + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( lights, false ); + normals_poly.add_contour( normals, false ); + + TGSuperPoly result; + result.set_poly( lights_poly ); + result.set_normals( normals_poly ); + result.set_material( "RWY_VASI_LIGHTS" ); + + result.set_flag( flag ); + + return result; +} + + +// generate a simple PAPI +static TGSuperPoly gen_papi( const TGRunway& rwy_info, float alt_m, + bool recip, TGPolygon *apt_base ) +{ + point_list lights; lights.clear(); + point_list normals; normals.clear(); + int i; + string flag; + double gs_angle = 3.0; + + cout << "gen papi " << rwy_info.rwy_no << endl; + + Point3D normal; + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 0.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 0.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + // determine the start point. + Point3D ref; + double length_hdg, left_hdg; + double lon, lat, r; + if ( recip ) { + ref = corner[0]; + length_hdg = rwy_info.heading + 180.0; + if ( length_hdg > 360.0 ) { length_hdg -= 360.0; } + flag = rwy_info.rwy_no + "-i"; + gs_angle = rwy_info.gs_angle1; + } else { + ref = corner[2]; + length_hdg = rwy_info.heading; + flag = rwy_info.rwy_no; + gs_angle = rwy_info.gs_angle2; + } + + left_hdg = length_hdg - 90.0; + if ( left_hdg < 0 ) { left_hdg += 360.0; } + cout << "length hdg = " << length_hdg + << " left heading = " << left_hdg << endl; + + if ( gs_angle < 0.5 ) { + gs_angle = 3.0; + } + + // offset 950' upwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + 950 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + // offset 50' left + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), left_hdg, + 50 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + // unit1 + Point3D pt1 = ref; + lights.push_back( pt1 ); + normal = gen_runway_light_vector( rwy_info, gs_angle + 0.5, recip ); + normals.push_back( normal ); + + // unit2 + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 30 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normal = gen_runway_light_vector( rwy_info, gs_angle + 0.167, recip ); + normals.push_back( normal ); + + // unit3 + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 30 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normal = gen_runway_light_vector( rwy_info, gs_angle - 0.167, recip ); + normals.push_back( normal ); + + // unit4 + geo_direct_wgs_84 ( alt_m, pt1.lat(), pt1.lon(), left_hdg, + 30 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt1 = Point3D( lon, lat, 0.0 ); + lights.push_back( pt1 ); + normal = gen_runway_light_vector( rwy_info, gs_angle - 0.5, recip ); + normals.push_back( normal ); + + // grass base + Point3D base_pt = (ref + pt1) / 2.0; + TGPolygon obj_base = gen_wgs84_area( base_pt, 15.0, 0.0, 0.0, 30.0, + length_hdg, alt_m, false ); + *apt_base = tgPolygonUnion( obj_base, *apt_base ); + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( lights, false ); + normals_poly.add_contour( normals, false ); + + TGSuperPoly result; + result.set_poly( lights_poly ); + result.set_normals( normals_poly ); + result.set_material( "RWY_VASI_LIGHTS" ); + + result.set_flag( flag ); + + return result; +} + + +// generate REIL lights +static TGSuperPoly gen_reil( const TGRunway& rwy_info, float alt_m, + bool recip ) +{ + point_list lights; lights.clear(); + point_list normals; normals.clear(); + int i; + string flag; + + cout << "gen reil " << rwy_info.rwy_no << endl; + + Point3D normal; + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 0.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 0.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + // determine the start point. + Point3D ref1, ref2; + double length_hdg, left_hdg; + double lon, lat, r; + if ( recip ) { + ref1 = corner[0]; + ref2 = corner[1]; + length_hdg = rwy_info.heading + 180.0; + if ( length_hdg > 360.0 ) { length_hdg -= 360.0; } + flag = rwy_info.rwy_no + "-i"; + } else { + ref1 = corner[2]; + ref2 = corner[3]; + length_hdg = rwy_info.heading; + flag = rwy_info.rwy_no; + } + left_hdg = length_hdg - 90.0; + if ( left_hdg < 0 ) { left_hdg += 360.0; } + cout << "length hdg = " << length_hdg + << " left heading = " << left_hdg << endl; + + // offset 40' downwind + geo_direct_wgs_84 ( alt_m, ref1.lat(), ref1.lon(), length_hdg, + -40 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref1 = Point3D( lon, lat, 0.0 ); + // offset 40' left + geo_direct_wgs_84 ( alt_m, ref1.lat(), ref1.lon(), left_hdg, + 40 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref1 = Point3D( lon, lat, 0.0 ); + + lights.push_back( ref1 ); + normal = gen_runway_light_vector( rwy_info, 10, recip ); + normals.push_back( normal ); + + // offset 40' downwind + geo_direct_wgs_84 ( alt_m, ref2.lat(), ref2.lon(), length_hdg, + -40 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref2 = Point3D( lon, lat, 0.0 ); + // offset 40' left + geo_direct_wgs_84 ( alt_m, ref2.lat(), ref2.lon(), left_hdg, + -40 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref2 = Point3D( lon, lat, 0.0 ); + + lights.push_back( ref2 ); + normal = gen_runway_light_vector( rwy_info, 10, recip ); + normals.push_back( normal ); + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( lights, false ); + normals_poly.add_contour( normals, false ); + + TGSuperPoly result; + result.set_poly( lights_poly ); + result.set_normals( normals_poly ); + result.set_material( "RWY_REIL_LIGHTS" ); + + result.set_flag( flag ); + + return result; +} + + +// generate Calvert-I/II approach lighting schemes +static superpoly_list gen_calvert( const TGRunway& rwy_info, + float alt_m, const string &kind, bool recip ) +{ + point_list g_lights; g_lights.clear(); + point_list w_lights; w_lights.clear(); + point_list r_lights; r_lights.clear(); + point_list s_lights; s_lights.clear(); + point_list g_normals; g_normals.clear(); + point_list w_normals; w_normals.clear(); + point_list r_normals; r_normals.clear(); + point_list s_normals; s_normals.clear(); + int i, j; + string flag; + if ( kind == "1" ) { + cout << "gen Calvert lights " << rwy_info.rwy_no << endl; + } else if ( kind == "2" ) { + cout << "gen Calvert/II lights " << rwy_info.rwy_no << endl; + } else { + cout << "gen unknown Calvert lights " << rwy_info.rwy_no << endl; + } + + Point3D normal1 = gen_runway_light_vector( rwy_info, 3.0, recip ); + Point3D normal2 = gen_runway_light_vector( rwy_info, 3.0, !recip ); + + // Generate the threshold lights + + double len = rwy_info.length * SG_FEET_TO_METER; + int divs = (int)(len / 10.0) + 1; + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 2.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 2.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + Point3D inc; + Point3D pt; + + if ( recip ) { + inc = (corner[0] - corner[1]) / divs; + pt = corner[1]; + flag = rwy_info.rwy_no + "-i"; + } else { + inc = (corner[2] - corner[3]) / divs; + pt = corner[3]; + flag = rwy_info.rwy_no; + } + + double dist = rwy_info.length; + double step = dist / divs; + + g_lights.push_back( pt ); + g_normals.push_back( normal1 ); + r_lights.push_back( pt ); + r_normals.push_back( normal2 ); + dist -= step; + + for ( i = 0; i < divs; ++i ) { + pt += inc; + g_lights.push_back( pt ); + g_normals.push_back( normal1 ); + r_lights.push_back( pt ); + r_normals.push_back( normal2 ); + dist -= step; + } + + // Generate long center bar of lights + + // determine the start point. + Point3D ref_save; + double length_hdg, left_hdg; + double lon, lat, r; + if ( recip ) { + ref_save = (corner[0] + corner[1]) / 2; + length_hdg = rwy_info.heading + 180.0; + if ( length_hdg > 360.0 ) { length_hdg -= 360.0; } + } else { + ref_save = (corner[2] + corner[3]) / 2; + length_hdg = rwy_info.heading; + } + left_hdg = length_hdg - 90.0; + if ( left_hdg < 0 ) { left_hdg += 360.0; } + cout << "length hdg = " << length_hdg + << " left heading = " << left_hdg << endl; + + Point3D ref = ref_save; + // + // Centre row of lights 1xlights out to 300m + // 2 x lights from 300m to 600m + // 3 x lights from 600m to 900m + // light spacing is 30m + // + // calvert2 has reds instead of whites out to 300m +#define CALVERT_HORIZ_SPACING 30 +#define CALVERT_VERT_SPACING 10 +#define CALVERT2_VERT_SPACING 2 + + int count; + //if ( kind == "1" || kind == "2" ) { + // geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + // -100 * SG_FEET_TO_METER, &lat, &lon, &r ); + // ref = Point3D( lon, lat, 0.0 ); + // count = 10; + //} + count=30; + + Point3D saved; + Point3D crossbar[5]; + Point3D pair; + // first set of single lights + for ( i = 0; i < count; ++i ) { + pt = ref; + + // centre lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), length_hdg, + -1 * CALVERT_HORIZ_SPACING, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + + if (kind == "1" ) { + if ( i >= 10 && i < 20 ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + CALVERT_VERT_SPACING/2, &lat, &lon, &r ); + pair = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pair ); + w_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -1 * CALVERT_VERT_SPACING/2, &lat, &lon, + &r ); + pair = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pair ); + w_normals.push_back( normal1 ); + } else if (i >= 20) { + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + CALVERT_VERT_SPACING, &lat, &lon, &r ); + pair = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pair ); + w_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -1 * CALVERT_VERT_SPACING, &lat, &lon, &r ); + pair = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pair ); + w_normals.push_back( normal1 ); + } else { + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + } else { + if ( i < 10 ) { + // cal2 has red centre lights + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + } else { + // cal2 has red centre lights + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + } + + switch ( i ) { + case 4: + crossbar[0] = pt; + break; + case 9: + crossbar[1] = pt; + break; + case 14: + crossbar[2] = pt; + break; + case 19: + crossbar[3] = pt; + break; + case 24: + crossbar[4] = pt; + break; + } + + // add 2 more rows if CAL/II (white) + // + + if ( kind == "2" ) { + saved = pt; + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + CALVERT2_VERT_SPACING, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + // five rows < 300m + if ( i < 10 ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + CALVERT2_VERT_SPACING, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + // outer strip of lights + for (j=0;j<9;j++) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + CALVERT2_VERT_SPACING, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + if ( i == 0 || j > 3 ) { + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + } + } + + pt = saved; + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -1 * CALVERT2_VERT_SPACING, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + // five rows < 300m + if ( i < 10 ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -1 * CALVERT2_VERT_SPACING, &lat, &lon, + &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + // outer strip of lights + for ( j = 0; j < 9; j++ ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -1 * CALVERT2_VERT_SPACING, &lat, &lon, + &r ); + pt = Point3D( lon, lat, 0.0 ); + if ( i == 0 || j > 3 ) { + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + } + } + + pt = saved; + + } + ref = pt; + + } + + ref = ref_save; + + int spacing; + int num_lights = 0; + + // draw nice crossbars + for ( i = 0; i < 5; i++ ) { + if (kind == "1") { + spacing = CALVERT_VERT_SPACING; + } else { + spacing = CALVERT2_VERT_SPACING; + } + switch ( i ) { + case 0: + num_lights = 4; + break; + case 1: + num_lights = 5; + break; + case 2: + num_lights = 6; + break; + case 3: + num_lights = 7; + break; + case 4: + num_lights = 8; + break; + } + + pt = crossbar[i]; + for ( j = 0 ; j < num_lights; j++ ) { + // left side lights + + // space out from centre lights + if ( j == 0 ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + CALVERT_VERT_SPACING * j, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + } + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + spacing, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + + if ( kind == "1" || i >= 2 ) { + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } else { + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + } + } + + pt = crossbar[i]; + for ( j = 0; j < num_lights; j++ ) { + // right side lights + // space out from centre lights + if ( j == 0 ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -1 * CALVERT_VERT_SPACING * j, &lat, &lon, + &r ); + pt = Point3D( lon, lat, 0.0 ); + } + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -1 * spacing, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + + if ( kind == "1" || i >= 2 ) { + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } else { + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + } + } + } + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( g_lights, false ); + normals_poly.add_contour( g_normals, false ); + + TGSuperPoly green; + green.set_poly( lights_poly ); + green.set_normals( normals_poly ); + green.set_material( "RWY_GREEN_LIGHTS" ); + green.set_flag( flag ); + + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( r_lights, false ); + normals_poly.add_contour( r_normals, false ); + + TGSuperPoly red; + red.set_poly( lights_poly ); + red.set_normals( normals_poly ); + red.set_material( "RWY_RED_LIGHTS" ); + red.set_flag( flag ); + + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( w_lights, false ); + normals_poly.add_contour( w_normals, false ); + + TGSuperPoly white; + white.set_poly( lights_poly ); + white.set_normals( normals_poly ); + white.set_material( "RWY_WHITE_LIGHTS" ); + white.set_flag( flag ); + + superpoly_list result; result.clear(); + + result.push_back( green ); + result.push_back( red ); + result.push_back( white ); + + if ( s_lights.size() ) { + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( s_lights, false ); + normals_poly.add_contour( s_normals, false ); + + TGSuperPoly sequenced; + sequenced.set_poly( lights_poly ); + sequenced.set_normals( normals_poly ); + sequenced.set_material( "RWY_SEQUENCED_LIGHTS" ); + sequenced.set_flag( flag ); + + result.push_back( sequenced ); + } + + return result; +} + +// generate ALSF-I/II and SALS/SALSF approach lighting schemes +static superpoly_list gen_alsf( const TGRunway& rwy_info, + float alt_m, const string &kind, bool recip ) +{ + point_list g_lights; g_lights.clear(); + point_list w_lights; w_lights.clear(); + point_list r_lights; r_lights.clear(); + point_list s_lights; s_lights.clear(); + point_list g_normals; g_normals.clear(); + point_list w_normals; w_normals.clear(); + point_list r_normals; r_normals.clear(); + point_list s_normals; s_normals.clear(); + int i, j; + string flag; + + cout << "gen ALSF/SALS lights " << rwy_info.rwy_no << endl; + + Point3D normal1 = gen_runway_light_vector( rwy_info, 3.0, recip ); + Point3D normal2 = gen_runway_light_vector( rwy_info, 3.0, !recip ); + + // Generate the threshold lights + + double len = rwy_info.length * SG_FEET_TO_METER; + int divs = (int)(len / 10.0) + 1; + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 2.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 2.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + Point3D inc; + Point3D pt; + + if ( recip ) { + inc = (corner[0] - corner[1]) / divs; + pt = corner[1]; + flag = rwy_info.rwy_no + "-i"; + } else { + inc = (corner[2] - corner[3]) / divs; + pt = corner[3]; + flag = rwy_info.rwy_no; + } + + double dist = rwy_info.length; + double step = dist / divs; + + g_lights.push_back( pt ); + g_normals.push_back( normal1 ); + r_lights.push_back( pt ); + r_normals.push_back( normal2 ); + dist -= step; + + for ( i = 0; i < divs; ++i ) { + pt += inc; + g_lights.push_back( pt ); + g_normals.push_back( normal1 ); + r_lights.push_back( pt ); + r_normals.push_back( normal2 ); + dist -= step; + } + + // Generate long center bar of lights + + // determine the start point. + Point3D ref_save; + double length_hdg, left_hdg; + double lon, lat, r; + if ( recip ) { + ref_save = (corner[0] + corner[1]) / 2; + length_hdg = rwy_info.heading + 180.0; + if ( length_hdg > 360.0 ) { length_hdg -= 360.0; } + } else { + ref_save = (corner[2] + corner[3]) / 2; + length_hdg = rwy_info.heading; + } + left_hdg = length_hdg - 90.0; + if ( left_hdg < 0 ) { left_hdg += 360.0; } + cout << "length hdg = " << length_hdg + << " left heading = " << left_hdg << endl; + + Point3D ref = ref_save; + + int count; + if ( kind == "1" || kind == "2" ) { + // ALSF-I or ALSF-II + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -100 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + count = 30; + } else { + // SALS/SALSF + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -300 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + count = 13; + } + + for ( i = 0; i < count; ++i ) { + pt = ref; + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + // left 2 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + pt = ref; + + // right 2 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -100 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + } + + ref = ref_save; + + if ( kind == "1" || kind == "O" || kind == "P" ) { + // Terminating bar + + // offset 200' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -200 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + pt = ref; + + // left 3 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 15 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + pt = ref; + + // right 3 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -15 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + } else if ( kind == "2" ) { + // Generate red side row lights + + for ( i = 0; i < 9; ++i ) { + // offset 100' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -100 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + pt = ref; + + // left 3 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 36 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + pt = ref; + + // right 3 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -36 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + } + } + + if ( kind == "1" || kind == "O" || kind == "P" ) { + // Generate pre-threshold bar + + ref = ref_save; + + // offset 100' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -100 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + pt = ref; + + // left 5 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 75 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + for ( j = 0; j < 4; ++j ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + } + + pt = ref; + + // rioght 5 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -75 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + for ( j = 0; j < 4; ++j ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + } + } else if ( kind == "2" ) { + // Generate -500 extra horizontal row of lights + + ref = ref_save; + + // offset 500' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -500 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + pt = ref; + + // left 4 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 11.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + for ( j = 0; j < 3; ++j ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + + pt = ref; + + // right 4 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -11.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + for ( j = 0; j < 3; ++j ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + } + + ref = ref_save; + + if ( kind == "O" || kind == "P" ) { + // generate SALS secondary threshold + + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -200 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + count = 30; + + pt = ref; + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + // left 2 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + pt = ref; + + // right 2 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + r_lights.push_back( pt ); + r_normals.push_back( normal1 ); + } + + // Generate -1000' extra horizontal row of lights + + ref = ref_save; + + // offset 1000' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -1000 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + pt = ref; + + // left 8 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 15 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + for ( j = 0; j < 7; ++j ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + + pt = ref; + + // right 8 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -15 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + for ( j = 0; j < 7; ++j ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + + ref = ref_save; + + if ( kind == "1" || kind == "2" ) { + // generate rabbit lights + + // start 1000' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -1000 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + for ( i = 0; i < 21; ++i ) { + s_lights.push_back( ref ); + s_normals.push_back( normal1 ); + + // offset 100' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -100 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + } + } else if ( kind == "P" ) { + // generate 3 sequenced lights aligned with last 3 light bars + + // start 1300' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -1300 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + for ( i = 0; i < 3; ++i ) { + s_lights.push_back( ref ); + s_normals.push_back( normal1 ); + + // offset 100' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -100 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + } + } + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( g_lights, false ); + normals_poly.add_contour( g_normals, false ); + + TGSuperPoly green; + green.set_poly( lights_poly ); + green.set_normals( normals_poly ); + green.set_material( "RWY_GREEN_LIGHTS" ); + green.set_flag( flag ); + + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( r_lights, false ); + normals_poly.add_contour( r_normals, false ); + + TGSuperPoly red; + red.set_poly( lights_poly ); + red.set_normals( normals_poly ); + red.set_material( "RWY_RED_LIGHTS" ); + red.set_flag( flag ); + + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( w_lights, false ); + normals_poly.add_contour( w_normals, false ); + + TGSuperPoly white; + white.set_poly( lights_poly ); + white.set_normals( normals_poly ); + white.set_material( "RWY_WHITE_LIGHTS" ); + white.set_flag( flag ); + + superpoly_list result; result.clear(); + + result.push_back( green ); + result.push_back( red ); + result.push_back( white ); + + if ( s_lights.size() ) { + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( s_lights, false ); + normals_poly.add_contour( s_normals, false ); + + TGSuperPoly sequenced; + sequenced.set_poly( lights_poly ); + sequenced.set_normals( normals_poly ); + sequenced.set_material( "RWY_SEQUENCED_LIGHTS" ); + sequenced.set_flag( flag ); + + result.push_back( sequenced ); + } + + return result; +} + + +// generate ODALS lights +static TGSuperPoly gen_odals( const TGRunway& rwy_info, float alt_m, + bool recip ) +{ + point_list lights; lights.clear(); + point_list normals; normals.clear(); + int i; + string flag; + + cout << "gen odals " << rwy_info.rwy_no << endl; + + // ODALS lighting is omni-directional, but we generate a normal as + // a placeholder to keep everything happy. + Point3D normal( 0.0, 0.0, 0.0 ); + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 0.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 0.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + // determine the start point. + Point3D ref1, ref2; + double length_hdg, left_hdg; + double lon, lat, r; + if ( recip ) { + ref1 = corner[0]; + ref2 = corner[1]; + length_hdg = rwy_info.heading + 180.0; + if ( length_hdg > 360.0 ) { length_hdg -= 360.0; } + flag = rwy_info.rwy_no + "-i"; + } else { + ref1 = corner[2]; + ref2 = corner[3]; + length_hdg = rwy_info.heading; + flag = rwy_info.rwy_no; + } + left_hdg = length_hdg - 90.0; + if ( left_hdg < 0 ) { left_hdg += 360.0; } + cout << "length hdg = " << length_hdg + << " left heading = " << left_hdg << endl; + + Point3D ref = ( ref1 + ref2 ) / 2.0; + + // offset 40' downwind + geo_direct_wgs_84 ( alt_m, ref1.lat(), ref1.lon(), length_hdg, + -40 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref1 = Point3D( lon, lat, 0.0 ); + // offset 40' left + geo_direct_wgs_84 ( alt_m, ref1.lat(), ref1.lon(), left_hdg, + 40 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref1 = Point3D( lon, lat, 0.0 ); + + lights.push_back( ref1 ); + normals.push_back( normal ); + + // offset 40' downwind + geo_direct_wgs_84 ( alt_m, ref2.lat(), ref2.lon(), length_hdg, + -40 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref2 = Point3D( lon, lat, 0.0 ); + // offset 40' left + geo_direct_wgs_84 ( alt_m, ref2.lat(), ref2.lon(), left_hdg, + -40 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref2 = Point3D( lon, lat, 0.0 ); + + lights.push_back( ref2 ); + normals.push_back( normal ); + + for ( i = 0; i < 5; ++i ) { + // offset 100m downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -100, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + lights.push_back( ref ); + normals.push_back( normal ); + } + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( lights, false ); + normals_poly.add_contour( normals, false ); + + TGSuperPoly result; + result.set_poly( lights_poly ); + result.set_normals( normals_poly ); + result.set_material( "RWY_ODALS_LIGHTS" ); + + result.set_flag( flag ); + + return result; +} + + +// generate SSALS, SSALF, and SSALR approach lighting scheme (kind = +// S, F, or R) +static superpoly_list gen_ssalx( const TGRunway& rwy_info, + float alt_m, const string& kind, bool recip ) +{ + point_list g_lights; g_lights.clear(); + point_list w_lights; w_lights.clear(); + point_list r_lights; r_lights.clear(); + point_list s_lights; s_lights.clear(); + point_list g_normals; g_normals.clear(); + point_list w_normals; w_normals.clear(); + point_list r_normals; r_normals.clear(); + point_list s_normals; s_normals.clear(); + int i, j; + string flag; + + cout << "gen SSALx lights " << rwy_info.rwy_no << endl; + + Point3D normal1 = gen_runway_light_vector( rwy_info, 3.0, recip ); + Point3D normal2 = gen_runway_light_vector( rwy_info, 3.0, !recip ); + + // Generate the threshold lights + + double len = rwy_info.length * SG_FEET_TO_METER; + int divs = (int)(len / 10.0) + 1; + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 2.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 2.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + Point3D inc; + Point3D pt; + + if ( recip ) { + inc = (corner[0] - corner[1]) / divs; + pt = corner[1]; + flag = rwy_info.rwy_no + "-i"; + } else { + inc = (corner[2] - corner[3]) / divs; + pt = corner[3]; + flag = rwy_info.rwy_no; + } + + double dist = rwy_info.length; + double step = dist / divs; + + g_lights.push_back( pt ); + g_normals.push_back( normal1 ); + r_lights.push_back( pt ); + r_normals.push_back( normal2 ); + dist -= step; + + for ( i = 0; i < divs; ++i ) { + pt += inc; + g_lights.push_back( pt ); + g_normals.push_back( normal1 ); + r_lights.push_back( pt ); + r_normals.push_back( normal2 ); + dist -= step; + } + + // Generate long center bar of lights (every 200') + + // determine the start point. + Point3D ref_save; + double length_hdg, left_hdg; + double lon, lat, r; + if ( recip ) { + ref_save = (corner[0] + corner[1]) / 2; + length_hdg = rwy_info.heading + 180.0; + if ( length_hdg > 360.0 ) { length_hdg -= 360.0; } + } else { + ref_save = (corner[2] + corner[3]) / 2; + length_hdg = rwy_info.heading; + } + left_hdg = length_hdg - 90.0; + if ( left_hdg < 0 ) { left_hdg += 360.0; } + cout << "length hdg = " << length_hdg + << " left heading = " << left_hdg << endl; + + Point3D ref = ref_save; + + for ( i = 0; i < 7; ++i ) { + // offset 200' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -200 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + pt = ref; + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + // left 2 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + pt = ref; + + // right 2 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -3.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + + // Generate -1000' extra horizontal row of lights + + ref = ref_save; + + // offset 1000' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -1000 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + pt = ref; + + // left 5 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 15 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + for ( j = 0; j < 4; ++j ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + + pt = ref; + + // right 5 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -15 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + for ( j = 0; j < 4; ++j ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + + if ( kind == "R" ) { + // generate 8 rabbit lights + + ref = ref_save; + + // start 1600' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -1600 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + for ( i = 0; i < 8; ++i ) { + s_lights.push_back( ref ); + s_normals.push_back( normal1 ); + + // offset 200' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -200 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + } + } else if ( kind == "F" ) { + // generate 3 sequenced lights aligned with last 3 light bars + ref = ref_save; + + // start 1000' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -1000 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + for ( i = 0; i < 3; ++i ) { + s_lights.push_back( ref ); + s_normals.push_back( normal1 ); + + // offset 200' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -200 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + } + } + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( g_lights, false ); + normals_poly.add_contour( g_normals, false ); + + TGSuperPoly green; + green.set_poly( lights_poly ); + green.set_normals( normals_poly ); + green.set_material( "RWY_GREEN_LIGHTS" ); + green.set_flag( flag ); + + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( r_lights, false ); + normals_poly.add_contour( r_normals, false ); + + TGSuperPoly red; + red.set_poly( lights_poly ); + red.set_normals( normals_poly ); + red.set_material( "RWY_RED_LIGHTS" ); + red.set_flag( flag ); + + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( w_lights, false ); + normals_poly.add_contour( w_normals, false ); + + TGSuperPoly white; + white.set_poly( lights_poly ); + white.set_normals( normals_poly ); + white.set_material( "RWY_WHITE_LIGHTS" ); + white.set_flag( flag ); + + superpoly_list result; result.clear(); + + result.push_back( green ); + result.push_back( red ); + result.push_back( white ); + + if ( s_lights.size() > 0 ) { + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( s_lights, false ); + normals_poly.add_contour( s_normals, false ); + + TGSuperPoly sequenced; + sequenced.set_poly( lights_poly ); + sequenced.set_normals( normals_poly ); + sequenced.set_material( "RWY_SEQUENCED_LIGHTS" ); + sequenced.set_flag( flag ); + + result.push_back( sequenced ); + } + + return result; +} + + +// generate MALS, MALSF, and MALSR approach lighting scheme (kind = +// ' ', F, or R) +static superpoly_list gen_malsx( const TGRunway& rwy_info, + float alt_m, const string& kind, bool recip ) +{ + point_list g_lights; g_lights.clear(); + point_list w_lights; w_lights.clear(); + point_list r_lights; r_lights.clear(); + point_list s_lights; s_lights.clear(); + point_list g_normals; g_normals.clear(); + point_list w_normals; w_normals.clear(); + point_list r_normals; r_normals.clear(); + point_list s_normals; s_normals.clear(); + int i, j; + string flag; + + cout << "gen SSALx lights " << rwy_info.rwy_no << endl; + + Point3D normal1 = gen_runway_light_vector( rwy_info, 3.0, recip ); + Point3D normal2 = gen_runway_light_vector( rwy_info, 3.0, !recip ); + + // Generate the threshold lights + + double len = rwy_info.length * SG_FEET_TO_METER; + int divs = (int)(len / 10.0) + 1; + + // using TGPolygon is a bit innefficient, but that's what the + // routine returns. + TGPolygon poly_corners + = gen_runway_area_w_extend( rwy_info, 0.0, 2.0, + rwy_info.disp_thresh1 * SG_FEET_TO_METER, + rwy_info.disp_thresh2 * SG_FEET_TO_METER, + 2.0 ); + + point_list corner; + for ( i = 0; i < poly_corners.contour_size( 0 ); ++i ) { + corner.push_back( poly_corners.get_pt( 0, i ) ); + } + + Point3D inc; + Point3D pt; + + if ( recip ) { + inc = (corner[0] - corner[1]) / divs; + pt = corner[1]; + flag = rwy_info.rwy_no + "-i"; + } else { + inc = (corner[2] - corner[3]) / divs; + pt = corner[3]; + flag = rwy_info.rwy_no; + } + + double dist = rwy_info.length; + double step = dist / divs; + + g_lights.push_back( pt ); + g_normals.push_back( normal1 ); + r_lights.push_back( pt ); + r_normals.push_back( normal2 ); + dist -= step; + + for ( i = 0; i < divs; ++i ) { + pt += inc; + g_lights.push_back( pt ); + g_normals.push_back( normal1 ); + r_lights.push_back( pt ); + r_normals.push_back( normal2 ); + dist -= step; + } + + // Generate long center bar of lights (every 200') + + // determine the start point. + Point3D ref_save; + double length_hdg, left_hdg; + double lon, lat, r; + if ( recip ) { + ref_save = (corner[0] + corner[1]) / 2; + length_hdg = rwy_info.heading + 180.0; + if ( length_hdg > 360.0 ) { length_hdg -= 360.0; } + } else { + ref_save = (corner[2] + corner[3]) / 2; + length_hdg = rwy_info.heading; + } + left_hdg = length_hdg - 90.0; + if ( left_hdg < 0 ) { left_hdg += 360.0; } + cout << "length hdg = " << length_hdg + << " left heading = " << left_hdg << endl; + + Point3D ref = ref_save; + + for ( i = 0; i < 7; ++i ) { + // offset 200' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -200 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + pt = ref; + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + // left 2 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 2.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 2.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + pt = ref; + + // right 2 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -2.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -2.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + + // Generate -1000' extra horizontal row of lights + + ref = ref_save; + + // offset 1000' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -1000 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + pt = ref; + + // left 5 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 23 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + for ( j = 0; j < 4; ++j ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + 2.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + + pt = ref; + + // right 5 side lights + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -23 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + + for ( j = 0; j < 4; ++j ) { + geo_direct_wgs_84 ( alt_m, pt.lat(), pt.lon(), left_hdg, + -2.5 * SG_FEET_TO_METER, &lat, &lon, &r ); + pt = Point3D( lon, lat, 0.0 ); + w_lights.push_back( pt ); + w_normals.push_back( normal1 ); + } + + if ( kind == "R" ) { + // generate 5 rabbit lights + + ref = ref_save; + + // start 1600' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -1600 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + for ( i = 0; i < 8; ++i ) { + s_lights.push_back( ref ); + s_normals.push_back( normal1 ); + + // offset 200' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -200 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + } + } else if ( kind == "F" ) { + // generate 3 sequenced lights aligned with last 3 light bars + ref = ref_save; + + // start 1000' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -1000 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + for ( i = 0; i < 3; ++i ) { + s_lights.push_back( ref ); + s_normals.push_back( normal1 ); + + // offset 200' downwind + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + -200 * SG_FEET_TO_METER, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + } + } + + TGPolygon lights_poly; lights_poly.erase(); + TGPolygon normals_poly; normals_poly.erase(); + lights_poly.add_contour( g_lights, false ); + normals_poly.add_contour( g_normals, false ); + + TGSuperPoly green; + green.set_poly( lights_poly ); + green.set_normals( normals_poly ); + green.set_material( "RWY_GREEN_LIGHTS" ); + green.set_flag( flag ); + + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( r_lights, false ); + normals_poly.add_contour( r_normals, false ); + + TGSuperPoly red; + red.set_poly( lights_poly ); + red.set_normals( normals_poly ); + red.set_material( "RWY_RED_LIGHTS" ); + red.set_flag( flag ); + + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( w_lights, false ); + normals_poly.add_contour( w_normals, false ); + + TGSuperPoly white; + white.set_poly( lights_poly ); + white.set_normals( normals_poly ); + white.set_material( "RWY_WHITE_LIGHTS" ); + white.set_flag( flag ); + + superpoly_list result; result.clear(); + + result.push_back( green ); + result.push_back( red ); + result.push_back( white ); + + if ( s_lights.size() > 0 ) { + lights_poly.erase(); + normals_poly.erase(); + lights_poly.add_contour( s_lights, false ); + normals_poly.add_contour( s_normals, false ); + + TGSuperPoly sequenced; + sequenced.set_poly( lights_poly ); + sequenced.set_normals( normals_poly ); + sequenced.set_material( "RWY_SEQUENCED_LIGHTS" ); + sequenced.set_flag( flag ); + + result.push_back( sequenced ); + } + + return result; +} + + +// top level runway light generator +void gen_runway_lights( const TGRunway& rwy_info, float alt_m, + superpoly_list &lights, TGPolygon *apt_base ) { + + string lighting_flags = rwy_info.lighting_flags; + SG_LOG( SG_GENERAL, SG_DEBUG, "gen runway lights " << rwy_info.rwy_no << " " + << rwy_info.lighting_flags ); + + int vasi1 = atoi( lighting_flags.substr(0,1).c_str() ); + int rwylt1 = atoi( lighting_flags.substr(1,1).c_str() ); + int app1 = atoi( lighting_flags.substr(2,1).c_str() ); + int vasi2 = atoi( lighting_flags.substr(3,1).c_str() ); + int rwylt2 = atoi( lighting_flags.substr(4,1).c_str() ); + int app2 = atoi( lighting_flags.substr(5,1).c_str() ); + + unsigned int i; + + // Make edge lighting + if ( rwylt1 >= 2 /* Has edge lighting */ ) { + // forward direction + superpoly_list s = gen_runway_edge_lights( rwy_info, rwylt1, false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( rwylt2 >= 2 /* Has edge lighting */ ) { + // reverse direction + superpoly_list s = gen_runway_edge_lights( rwy_info, rwylt2, true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + // Centerline lighting + if ( rwylt1 >= 4 /* Has centerline lighting */ ) { + // forward direction + superpoly_list s = gen_runway_center_line_lights( rwy_info, false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( rwylt2 >= 4 /* Has centerline lighting */ ) { + // reverse direction + superpoly_list s = gen_runway_center_line_lights( rwy_info, true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + // Touchdown zone lighting + if ( rwylt1 >= 5 /* Has touchdown zone lighting */ ) { + TGSuperPoly s = gen_touchdown_zone_lights( rwy_info, alt_m, false ); + lights.push_back( s ); + } + if ( rwylt2 >= 5 /* Has touchdown zone lighting */ ) { + TGSuperPoly s = gen_touchdown_zone_lights( rwy_info, alt_m, true ); + lights.push_back( s ); + } + + // REIL lighting + if ( rwylt1 >= 3 /* Has REIL lighting */ ) { + TGSuperPoly s = gen_reil( rwy_info, alt_m, false ); + lights.push_back( s ); + } + if ( rwylt2 >= 3 /* Has REIL lighting */ ) { + TGSuperPoly s = gen_reil( rwy_info, alt_m, true ); + lights.push_back( s ); + } + + // VASI/PAPI lighting + if ( vasi1 == 2 /* Has VASI */ ) { + TGSuperPoly s = gen_vasi( rwy_info, alt_m, false, apt_base ); + lights.push_back( s ); + } else if ( vasi1 == 3 /* Has PAPI */ ) { + TGSuperPoly s = gen_papi( rwy_info, alt_m, false, apt_base ); + lights.push_back( s ); + } + if ( vasi2 == 2 /* Has VASI */ ) { + TGSuperPoly s = gen_vasi( rwy_info, alt_m, true, apt_base ); + lights.push_back( s ); + } else if ( vasi2 == 3 /* Has PAPI */ ) { + TGSuperPoly s = gen_papi( rwy_info, alt_m, true, apt_base ); + lights.push_back( s ); + } + + // Approach lighting + + //////////////////////////////////////////////////////////// + // NOT IMPLIMENTED: + // + // code "A" == ALS Approach light system (assumed white lights) + // + // Please send me documentation for this configuration + //////////////////////////////////////////////////////////// + + if ( app1 == 4 /* ALSF-I */ ) { + superpoly_list s = gen_alsf( rwy_info, alt_m, "1", false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( app2 == 4 /* ALSF-I */ ) { + superpoly_list s = gen_alsf( rwy_info, alt_m, "1", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + if ( app1 == 5 /* ALSF-II */ ) { + superpoly_list s = gen_alsf( rwy_info, alt_m, "2", false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( app2 == 5 /* ALSF-II */ ) { + superpoly_list s = gen_alsf( rwy_info, alt_m, "2", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + //////////////////////////////////////////////////////////// + // NOT IMPLIMENTED: + // + // code: "D" CAL Calvert (British) + // + // code: "E" CAL-II Calvert (British) - Cat II and II + // + // Please send me documentation for this configuration + //////////////////////////////////////////////////////////// + + if ( app1 == 7 || app1 == 8 /* Calvert 1, 2, and 3 */ ) { + superpoly_list s = gen_calvert( rwy_info, alt_m, "1", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( app2 == 7 || app2 == 8 /* Calvert 1, 2, and 3 */ ) { + superpoly_list s = gen_calvert( rwy_info, alt_m, "2", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + //////////////////////////////////////////////////////////// + // NOT IMPLIMENTED: + // + // code: "F" LDIN + // + // This configuration is airport specific and no additional placement + // data is provided in our database + //////////////////////////////////////////////////////////// + + if ( app1 == -1 /* MALS not supported by data base */ ) { + superpoly_list s = gen_malsx( rwy_info, alt_m, "x", false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( app2 == -1 /* MALS not supported by data base */ ) { + superpoly_list s = gen_malsx( rwy_info, alt_m, "x", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + if ( app1 == -1 /* MALSF not supported by data base */ ) { + superpoly_list s = gen_malsx( rwy_info, alt_m, "F", false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( app2 == -1 /* MALSF not supported by data base */ ) { + superpoly_list s = gen_malsx( rwy_info, alt_m, "F", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + //////////////////////////////////////////////////////////// + // NOT IMPLIMENTED: + // + // code: "I" NSTD Non standard + // + // This is also likely airport specific + //////////////////////////////////////////////////////////// + + if ( app1 == -1 /* MALSR not supported by data base */ ) { + superpoly_list s = gen_malsx( rwy_info, alt_m, "R", false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( app2 == -1 /* MALSR not supported by data base */ ) { + superpoly_list s = gen_malsx( rwy_info, alt_m, "R", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + //////////////////////////////////////////////////////////// + // NOT IMPLIMENTED: + // + // code: "K" MIL OVRN Something military + // + // No clue ... + //////////////////////////////////////////////////////////// + + if ( app1 == 6 /* ODALS Omni-directional approach light system */ ) { + TGSuperPoly s = gen_odals( rwy_info, alt_m, false ); + lights.push_back( s ); + } + if ( app2 == 6 /* ODALS Omni-directional approach light system */ ) { + TGSuperPoly s = gen_odals( rwy_info, alt_m, true ); + lights.push_back( s ); + } + + //////////////////////////////////////////////////////////// + // NOT IMPLIMENTED: + // + // code: "M" RAIL Runway alignment indicator lights (icw other systems) + // + //////////////////////////////////////////////////////////// + + // SALS (Essentially ALSF-1 without the lead in rabbit lights, and + // a shorter center bar) + if ( app1 == -1 /* SALS not supported by database */ ) { + superpoly_list s = gen_alsf( rwy_info, alt_m, "O", false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( app2 == -1 /* SALS not supported by database */ ) { + superpoly_list s = gen_alsf( rwy_info, alt_m, "O", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + if ( app1 == 3 /* SALSF */ ) { + superpoly_list s = gen_alsf( rwy_info, alt_m, "P", false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( app2 == 3 /* SALSF */ ) { + superpoly_list s = gen_alsf( rwy_info, alt_m, "P", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + if ( app1 == -1 /* SSALF not supported by database */ ) { + superpoly_list s = gen_ssalx( rwy_info, alt_m, "F", false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( app2 == -1 /* SSALF not supported by database */ ) { + superpoly_list s = gen_ssalx( rwy_info, alt_m, "F", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + if ( app1 == -1 /* SSALR not supported by database */ ) { + superpoly_list s = gen_ssalx( rwy_info, alt_m, "R", false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( app2 == -1 /* SSALR not supported by database */ ) { + superpoly_list s = gen_ssalx( rwy_info, alt_m, "R", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + if ( app1 == 2 /* SSALS */ ) { + superpoly_list s = gen_ssalx( rwy_info, alt_m, "S", false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + if ( app2 == 2 /* SSALS */ ) { + superpoly_list s = gen_ssalx( rwy_info, alt_m, "S", true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + // Many aproach lighting systems define the threshold lighting + // needed, but for those that don't (i.e. REIL, ODALS, or Edge + // lights defined but no approach lights) + // make threshold lighting + cout << "rwylt1 = " << rwylt1 << " app1 = " << app1 << endl; + if ( rwylt1 >= 3 /* Has REIL lighting */ + || app1 == 6 /* ODALS Omni-directional approach light system */ + || ( rwylt1 >= 2 && app1 <= 1 ) /* Has edge lighting, but no + approach lighting */ ) + { + // forward direction + cout << "threshold lights for forward direction" << endl; + superpoly_list s = gen_runway_threshold_lights( rwy_info, rwylt1, + alt_m, false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + cout << "rwylt2 = " << rwylt2 << " app2 = " << app2 << endl; + if ( rwylt2 >= 3 /* Has REIL lighting */ + || app2 == 6 /* ODALS Omni-directional approach light system */ + || ( rwylt2 >= 2 && app2 <= 1 ) /* Has edge lighting, but no + approach lighting */ ) + { + // reverse direction + cout << "threshold lights for reverse direction" << endl; + superpoly_list s = gen_runway_threshold_lights( rwy_info, rwylt1, + alt_m, true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } +} + + +// top level taxiway light generator +void gen_taxiway_lights( const TGRunway& taxiway_info, float alt_m, + superpoly_list &lights ) +{ + SG_LOG( SG_GENERAL, SG_DEBUG, "gen taxiway lights " + << taxiway_info.rwy_no << " " + << taxiway_info.lighting_flags ); + + string lighting_flags = taxiway_info.lighting_flags; + int rwylt1 = atoi( lighting_flags.substr(1,1).c_str() ); + int rwylt2 = atoi( lighting_flags.substr(4,1).c_str() ); + + unsigned int i; + + // Centerline lighting + if ( rwylt1 == 6 /* taxiway lit, assume centerline too */ ) { + // forward direction + superpoly_list s + = gen_taxiway_center_line_lights( taxiway_info, false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + if ( rwylt2 == 6 /* taxiway lit, assume centerline too */ ) { + // reverse direction + superpoly_list s + = gen_taxiway_center_line_lights( taxiway_info, true ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + + // Make edge lighting + if ( rwylt1 == 6 /* taxiway blue lit */ ) { + // forward direction (blue lights are omni-directional so we + // don't need to generate the reverse direction + superpoly_list s; + s = gen_taxiway_edge_lights( taxiway_info, rwylt1, false ); + for ( i = 0; i < s.size(); ++i ) { + lights.push_back( s[i] ); + } + } + +} diff --git a/src/Airports/GenAirports850/lights.hxx b/src/Airports/GenAirports850/lights.hxx new file mode 100644 index 00000000..09575900 --- /dev/null +++ b/src/Airports/GenAirports850/lights.hxx @@ -0,0 +1,44 @@ +// lights.hxx -- Generate runway lighting +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: lights.hxx,v 1.8 2004-11-19 22:25:49 curt Exp $ +// + + +#ifndef _RWY_LIGHTS_HXX +#define _RWY_LIGHTS_HXX + + +#include +#include + +#include "runway.hxx" +#include "texparams.hxx" + + +// generate runway lighting +void gen_runway_lights( const TGRunway& rwy_info, float alt_m, + superpoly_list &lights, TGPolygon *apt_base ); + +// generate taxiway lighting +void gen_taxiway_lights( const TGRunway& taxiway_info, float alt_m, + superpoly_list &lights ); + +#endif // _RWY_LIGHTS_HXX diff --git a/src/Airports/GenAirports850/main.cxx b/src/Airports/GenAirports850/main.cxx new file mode 100644 index 00000000..fd92db53 --- /dev/null +++ b/src/Airports/GenAirports850/main.cxx @@ -0,0 +1,491 @@ +// main.cxx -- main loop +// +// Written by Curtis Olson, started March 1998. +// +// Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: main.cxx,v 1.37 2005/12/19 15:53:21 curt Exp $ +// + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#ifdef HAVE_STDLIB_H +#include +#endif + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "build.hxx" +#include "convex_hull.hxx" + +using std::vector; +using std::cout; +using std::endl; + + +int nudge = 10; +double slope_max = 0.2; + +static int is_in_range( string_list & runway_list, float min_lat, float max_lat, float min_lon, float max_lon ); + +// Display usage +static void usage( int argc, char **argv ) { + SG_LOG(SG_GENERAL, SG_ALERT, + "Usage " << argv[0] << " --input= " + << "--work= [ --start-id=abcd ] [ --nudge=n ] " + << "[--min-lon=] [--max-lon=] [--min-lat=] [--max-lat=] " + << "[--clear-dem-path] [--dem-path=] [--max-slope=] " + << "[ --airport=abcd ] [--tile=] [--chunk=] [--verbose] [--help]"); +} + +void setup_default_elevation_sources(string_list& elev_src) { + elev_src.push_back( "SRTM2-Africa-3" ); + elev_src.push_back( "SRTM2-Australia-3" ); + elev_src.push_back( "SRTM2-Eurasia-3" ); + elev_src.push_back( "SRTM2-Islands-3" ); + elev_src.push_back( "SRTM2-North_America-3" ); + elev_src.push_back( "SRTM2-South_America-3" ); + elev_src.push_back( "DEM-USGS-3" ); + elev_src.push_back( "SRTM-1" ); + elev_src.push_back( "SRTM-3" ); + elev_src.push_back( "SRTM-30" ); +} + +// Display help and usage +static void help( int argc, char **argv, const string_list& elev_src ) { + cout << "genapts generates airports for use in generating scenery for the FlightGear flight simulator. "; + cout << "Airport, runway, and taxiway vector data and attributes are input, and generated 3D airports "; + cout << "are output for further processing by the TerraGear scenery creation tools. "; + cout << "\n\n"; + cout << "The standard input file is runways.dat.gz which is found in $FG_ROOT/Airports. "; + cout << "This file is periodically generated for the FlightGear project by Robin Peel, who "; + cout << "maintains an airport database for both the X-Plane and FlightGear simulators. "; + cout << "The format of this file is documented on the FlightGear web site. "; + cout << "Any other input file corresponding to this format may be used as input to genapts. "; + cout << "Input files may be gzipped or left as plain text as required. "; + cout << "\n\n"; + cout << "Processing all the world's airports takes a *long* time. To cut down processing time "; + cout << "when only some airports are required, you may refine the input selection either by airport "; + cout << "or by area. By airport, either one airport can be specified using --airport=abcd, where abcd is "; + cout << "a valid airport code eg. --airport-id=KORD, or a starting airport can be specified using --start-id=abcd "; + cout << "where once again abcd is a valid airport code. In this case, all airports in the file subsequent to the "; + cout << "start-id are done. This is convienient when re-starting after a previous error. "; + cout << "\nAn input area may be specified by lat and lon extent using min and max lat and lon. "; + cout << "Alternatively, you may specify a chunk (10 x 10 degrees) or tile (1 x 1 degree) using a string "; + cout << "such as eg. w080n40, e000s27. "; + cout << "\nAn input file containing only a subset of the world's "; + cout << "airports may of course be used."; + cout << "\n\n"; + cout << "It is necessary to generate the elevation data for the area of interest PRIOR TO GENERATING THE AIRPORTS. "; + cout << "Failure to do this will result in airports being generated with an elevation of zero. "; + cout << "The following subdirectories of the work-dir will be searched for elevation files:\n\n"; + + string_list::const_iterator elev_src_it; + for (elev_src_it = elev_src.begin(); elev_src_it != elev_src.end(); elev_src_it++) { + cout << *elev_src_it << "\n"; + } + cout << "\n"; + usage( argc, argv ); +} + + +// reads the apt_full file and extracts and processes the individual +// airport records +int main( int argc, char **argv ) { + float min_lon = -180; + float max_lon = 180; + float min_lat = -90; + float max_lat = 90; + bool ready_to_go = true; + + string_list elev_src; + elev_src.clear(); + setup_default_elevation_sources(elev_src); + + sglog().setLogLevels( SG_GENERAL, SG_INFO ); + + // parse arguments + string work_dir = ""; + string input_file = ""; + string start_id = ""; + string airport_id = ""; + int arg_pos; + for (arg_pos = 1; arg_pos < argc; arg_pos++) { + string arg = argv[arg_pos]; + if ( arg.find("--work=") == 0 ) { + work_dir = arg.substr(7); + } else if ( arg.find("--input=") == 0 ) { + input_file = arg.substr(8); + } else if ( arg.find("--terrain=") == 0 ) { + elev_src.push_back( arg.substr(10) ); + } else if ( arg.find("--start-id=") == 0 ) { + start_id = arg.substr(11); + ready_to_go = false; + } else if ( arg.find("--nudge=") == 0 ) { + nudge = atoi( arg.substr(8).c_str() ); + } else if ( arg.find("--min-lon=") == 0 ) { + min_lon = atof( arg.substr(10).c_str() ); + } else if ( arg.find("--max-lon=") == 0 ) { + max_lon = atof( arg.substr(10).c_str() ); + } else if ( arg.find("--min-lat=") == 0 ) { + min_lat = atof( arg.substr(10).c_str() ); + } else if ( arg.find("--max-lat=") == 0 ) { + max_lat = atof( arg.substr(10).c_str() ); + } else if ( arg.find("--chunk=") == 0 ) { + tg::Rectangle rectangle = tg::parseChunk(arg.substr(8).c_str(), + 10.0); + min_lon = rectangle.getMin().x(); + min_lat = rectangle.getMin().y(); + max_lon = rectangle.getMax().x(); + max_lat = rectangle.getMax().y(); + } else if ( arg.find("--tile=") == 0 ) { + tg::Rectangle rectangle = tg::parseTile(arg.substr(7).c_str()); + min_lon = rectangle.getMin().x(); + min_lat = rectangle.getMin().y(); + max_lon = rectangle.getMax().x(); + max_lat = rectangle.getMax().y(); + } else if ( arg.find("--airport=") == 0 ) { + airport_id = arg.substr(10).c_str(); + ready_to_go = false; + } else if ( arg == "--clear-dem-path" ) { + elev_src.clear(); + } else if ( arg.find("--dem-path=") == 0 ) { + elev_src.push_back( arg.substr(11) ); + } else if ( (arg.find("--verbose") == 0) || (arg.find("-v") == 0) ) { + sglog().setLogLevels( SG_GENERAL, SG_BULK ); + } else if ( (arg.find("--max-slope=") == 0) ) { + slope_max = atof( arg.substr(12).c_str() ); + } else if ( (arg.find("--help") == 0) || (arg.find("-h") == 0) ) { + help( argc, argv, elev_src ); + exit(-1); + } else { + usage( argc, argv ); + exit(-1); + } + } + + SG_LOG(SG_GENERAL, SG_INFO, "Input file = " << input_file); + SG_LOG(SG_GENERAL, SG_INFO, "Terrain sources = "); + for ( unsigned int i = 0; i < elev_src.size(); ++i ) { + SG_LOG(SG_GENERAL, SG_INFO, " " << work_dir << "/" << elev_src[i] ); + } + SG_LOG(SG_GENERAL, SG_INFO, "Work directory = " << work_dir); + SG_LOG(SG_GENERAL, SG_INFO, "Nudge = " << nudge); + SG_LOG(SG_GENERAL, SG_INFO, "Longitude = " << min_lon << ':' << max_lon); + SG_LOG(SG_GENERAL, SG_INFO, "Latitude = " << min_lat << ':' << max_lat); + + if (max_lon < min_lon || max_lat < min_lat || + min_lat < -90 || max_lat > 90 || + min_lon < -180 || max_lon > 180) { + SG_LOG(SG_GENERAL, SG_ALERT, "Bad longitude or latitude"); + exit(1); + } + + if ( work_dir == "" ) { + SG_LOG( SG_GENERAL, SG_ALERT, + "Error: no work directory specified." ); + usage( argc, argv ); + exit(-1); + } + + if ( input_file == "" ) { + SG_LOG( SG_GENERAL, SG_ALERT, + "Error: no input file." ); + exit(-1); + } + + // make work directory + string airportareadir=work_dir+"/AirportArea"; + SGPath sgp( airportareadir ); + sgp.append( "dummy" ); + sgp.create_dir( 0755 ); + + string lastaptfile = work_dir+"/last_apt"; + + // initialize persistant polygon counter + string counter_file = airportareadir+"/poly_counter"; + poly_index_init( counter_file ); + + sg_gzifstream in( input_file ); + if ( !in.is_open() ) { + SG_LOG( SG_GENERAL, SG_ALERT, "Cannot open file: " << input_file ); + exit(-1); + } + + string_list runways_list; + string_list beacon_list; + string_list tower_list; + string_list windsock_list; + + vector token; + string last_apt_id = ""; + string last_apt_info = ""; + string last_apt_type = ""; + string line; + char tmp[2048]; + + while ( ! in.eof() ) { + in.getline(tmp, 2048); + line = tmp; + SG_LOG( SG_GENERAL, SG_DEBUG, "-> '" << line << "'" ); + if ( line.length() ) { + token = simgear::strutils::split( line ); + if ( token.size() ) { + SG_LOG( SG_GENERAL, SG_DEBUG, "token[0] " << token[0] ); + } + } else { + token.clear(); + } + + if ( !line.length() || !token.size() ) { + // empty line, skip + } else if ( (token[0] == "#") || (token[0] == "//") ) { + // comment, skip + } else if ( token[0] == "I" ) { + // First line, indicates IBM (i.e. DOS line endings I + // believe.) + + // move past this line and read and discard the next line + // which is the version and copyright information + in.getline(tmp, 2048); + vector vers_token = simgear::strutils::split( tmp ); + SG_LOG( SG_GENERAL, SG_INFO, "Data version = " << vers_token[0] ); + } else if ( token[0] == "1" /* Airport */ || + token[0] == "16" /* Seaplane base */ || + token[0] == "17" /* Heliport */ ) { + + // extract some airport runway info + string rwy; + float lat, lon; + + string id = token[4]; + int elev = atoi( token[1].c_str() ); + SG_LOG( SG_GENERAL, SG_INFO, "Next airport = " << id << " " << elev ); + + if ( !last_apt_id.empty()) { + if ( runways_list.size() ) { + vector rwy_token + = simgear::strutils::split( runways_list[0] ); + rwy = token[8]; + lat = atof( token[9].c_str() ); + lon = atof( token[10].c_str() ); + + if ( airport_id.length() && airport_id == last_apt_id ) { + ready_to_go = true; + } else if ( start_id.length() && start_id == last_apt_id ) { + ready_to_go = true; + } + + if ( ready_to_go ) { + // check point our location + char command[256]; + sprintf( command, + "echo before building %s >> %s", + last_apt_id.c_str(), + lastaptfile.c_str() ); + system( command ); + + // process previous record + // process_airport(last_apt_id, runways_list, argv[2]); + try { + if ( last_apt_type == "16" /* Seaplane base */ || + last_apt_type == "17" /* Heliport */ ) { + // skip building heliports and + // seaplane bases + } else { + if( is_in_range( runways_list, min_lat, max_lat, min_lon, max_lon ) ) { + build_airport( last_apt_id, + elev * SG_FEET_TO_METER, + runways_list, + beacon_list, + tower_list, + windsock_list, + work_dir, elev_src ); + } + } + } catch (sg_exception &e) { + SG_LOG( SG_GENERAL, SG_ALERT, + "Failed to build airport = " + << last_apt_id ); + SG_LOG( SG_GENERAL, SG_ALERT, "Exception: " + << e.getMessage() ); + exit(-1); + } + if ( airport_id.length() ) { + ready_to_go = false; + } + } + } else { + if(!airport_id.length()) { + SG_LOG(SG_GENERAL, SG_INFO, + "ERRO: No runways, skipping = " << id); + } + } + } + + last_apt_id = id; + last_apt_info = line; + last_apt_type = token[0]; + + // clear runway list for start of next airport + runways_list.clear(); + beacon_list.clear(); + tower_list.clear(); + windsock_list.clear(); + } else if ( token[0] == "100" ) { + // runway entry + runways_list.push_back(line); + } else if ( token[0] == "18" ) { + // beacon entry + beacon_list.push_back(line); + } else if ( token[0] == "14" ) { + // control tower entry + tower_list.push_back(line); + } else if ( token[0] == "19" ) { + // windsock entry + windsock_list.push_back(line); + } else if ( token[0] == "21" ) { + // light object + } else if ( token[0] == "15" ) { + // ignore custom startup locations + } else if ( token[0] == "50" || token[0] == "51" || token[0] == "52" + || token[0] == "53" || token[0] == "54" || token[0] == "55" + || token[0] == "56" ) + { + // ignore frequency entries + } else if ( token[0] == "99" ) { + SG_LOG( SG_GENERAL, SG_ALERT, "End of file reached" ); + } else if ( token[0] == "00" ) { + // ?? + } else { + SG_LOG( SG_GENERAL, SG_ALERT, + "Unknown line in file: " << line ); + exit(-1); + } + } + + cout << "last_apt_id.length() = " << last_apt_id.length() << endl; + + if ( !last_apt_id.empty()) { + char ctmp, tmpid[32], rwy[32]; + string id; + float lat, lon; + int elev = 0; + + if ( runways_list.size() ) { + sscanf( runways_list[0].c_str(), "%c %s %s %f %f", + &ctmp, tmpid, rwy, &lat, &lon ); + } + + if ( start_id.length() && start_id == last_apt_id ) { + ready_to_go = true; + } + + if ( ready_to_go ) { + // check point our location + char command[256]; + sprintf( command, + "echo before building %s >> %s", + last_apt_id.c_str(), + lastaptfile.c_str() ); + system( command ); + + // process previous record + // process_airport(last_apt_id, runways_list, argv[2]); + try { + if ( last_apt_type == "16" /* Seaplane base */ || + last_apt_type == "17" /* Heliport */ ) { + // skip building heliports and + // seaplane bases + } else { + if( is_in_range( runways_list, min_lat, max_lat, min_lon, max_lon ) ) { + build_airport( last_apt_id, elev * SG_FEET_TO_METER, + runways_list, + beacon_list, + tower_list, + windsock_list, + work_dir, elev_src ); + } + } + } catch (sg_exception &e) { + SG_LOG( SG_GENERAL, SG_ALERT, + "Failed to build airport = " + << last_apt_id ); + SG_LOG( SG_GENERAL, SG_ALERT, "Exception: " + << e.getMessage() ); + exit(-1); + } + } else { + SG_LOG(SG_GENERAL, SG_INFO, "Skipping airport " << id); + } + } + + SG_LOG(SG_GENERAL, SG_INFO, "[FINISHED CORRECTLY]"); + + return 0; +} + +static int is_in_range( string_list & runways_raw, float min_lat, float max_lat, float min_lon, float max_lon ) +{ + int i; + int rwy_count = 0; + double apt_lon = 0.0, apt_lat = 0.0; + + for ( i = 0; i < (int)runways_raw.size(); ++i ) { + ++rwy_count; + + string rwy_str = runways_raw[i]; + vector token = simgear::strutils::split( rwy_str ); + + apt_lat += atof( token[1].c_str() ); + apt_lon += atof( token[2].c_str() ); + } + + if( rwy_count > 0 ) { + apt_lat /= rwy_count; + apt_lon /= rwy_count; + } + + if( apt_lat >= min_lat && apt_lat <= max_lat && + apt_lon >= min_lon && apt_lon <= max_lon ) { + return 1; + } + + return 0; +} diff --git a/src/Airports/GenAirports850/point2d.cxx b/src/Airports/GenAirports850/point2d.cxx new file mode 100644 index 00000000..5118e60e --- /dev/null +++ b/src/Airports/GenAirports850/point2d.cxx @@ -0,0 +1,38 @@ +// point2d.cxx -- 2d coordinate routines +// +// Written by Curtis Olson, started September 1998. +// +// Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: point2d.cxx,v 1.2 2004-11-19 22:25:49 curt Exp $ +// + + +#include + +#include "point2d.hxx" + + +// convert a point from cartesian to polar coordinates +Point3D cart_to_polar_2d(const Point3D& in) { + Point3D result( sqrt(in.x() * in.x() + in.y() * in.y()), + atan2(in.y(), in.x()), + 0 ); + return result; +} + + diff --git a/src/Airports/GenAirports850/point2d.hxx b/src/Airports/GenAirports850/point2d.hxx new file mode 100644 index 00000000..ebd36274 --- /dev/null +++ b/src/Airports/GenAirports850/point2d.hxx @@ -0,0 +1,39 @@ +// point2d.hxx -- define a 2d point class +// +// Written by Curtis Olson, started February 1998. +// +// Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: point2d.hxx,v 1.4 2004-11-19 22:25:49 curt Exp $ +// + + +#ifndef _POINT2D_HXX +#define _POINT2D_HXX + + +#include +#include + + +// convert a point from cartesian to polar coordinates +Point3D cart_to_polar_2d(const Point3D& in); + + +#endif // _POINT2D_HXX + + diff --git a/src/Airports/GenAirports850/poly_extra.cxx b/src/Airports/GenAirports850/poly_extra.cxx new file mode 100644 index 00000000..dbe8ba9b --- /dev/null +++ b/src/Airports/GenAirports850/poly_extra.cxx @@ -0,0 +1,117 @@ +// poly_extra.cxx -- Extra polygon manipulation routines +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: poly_extra.cxx,v 1.9 2004-11-19 22:25:49 curt Exp $ +// + +#include + +#include +#include + +#include + +#include "poly_extra.hxx" + + +// Divide segment if there are other existing points on it, return the +// new polygon +void add_intermediate_nodes( int contour, const Point3D& start, + const Point3D& end, const TGTriNodes& tmp_nodes, + TGPolygon *result ) +{ + point_list nodes = tmp_nodes.get_node_list(); + + // SG_LOG(SG_GENERAL, SG_DEBUG, " add_intermediate_nodes()"); + char buf[200]; + snprintf(buf, 199, " %.7f %.7f %.7f <=> %.7f %.7f %.7f\n", + start.x(), start.y(), start.z(), end.x(), end.y(), end.z() ); + SG_LOG(SG_GENERAL, SG_BULK, buf); + + + Point3D new_pt; + bool found_extra = find_intermediate_node( start, end, nodes, &new_pt ); + + if ( found_extra ) { + // recurse with two sub segments + // SG_LOG(SG_GENERAL, SG_DEBUG, "dividing " << p0 << " " << nodes[extra_index] + // << " " << p1); + add_intermediate_nodes( contour, start, new_pt, tmp_nodes, + result ); + + result->add_node( contour, new_pt ); + SG_LOG(SG_GENERAL, SG_BULK, " adding = " << new_pt); + + add_intermediate_nodes( contour, new_pt, end, tmp_nodes, + result ); + } else { + // this segment does not need to be divided + } +} + + +// Search each segment for additional vertex points that may have been +// created elsewhere that lie on the segment and split it there to +// avoid "T" intersections. + +TGPolygon add_nodes_to_poly( const TGPolygon& poly, + const TGTriNodes& tmp_nodes ) { + int i, j; + TGPolygon result; result.erase(); + Point3D p0, p1; + + // SG_LOG(SG_GENERAL, SG_DEBUG, "add_nodes_to_poly"); + + for ( i = 0; i < poly.contours(); ++i ) { + // SG_LOG(SG_GENERAL, SG_DEBUG, "contour = " << i); + for ( j = 0; j < poly.contour_size(i) - 1; ++j ) { + p0 = poly.get_pt( i, j ); + p1 = poly.get_pt( i, j + 1 ); + + // add start of segment + result.add_node( i, p0 ); + + // add intermediate points + add_intermediate_nodes( i, p0, p1, tmp_nodes, &result ); + + // end of segment is beginning of next segment + } + p0 = poly.get_pt( i, poly.contour_size(i) - 1 ); + p1 = poly.get_pt( i, 0 ); + + // add start of segment + result.add_node( i, p0 ); + + // add intermediate points + add_intermediate_nodes( i, p0, p1, tmp_nodes, &result ); + + // end of segment is beginning of next segment + // 5/9/2000 CLO - this results in duplicating the last point + // of a contour so I have removed this line. + // result.add_node( i, p1 ); + + // maintain original hole flag setting + result.set_hole_flag( i, poly.get_hole_flag( i ) ); + } + + return result; +} + + diff --git a/src/Airports/GenAirports850/poly_extra.hxx b/src/Airports/GenAirports850/poly_extra.hxx new file mode 100644 index 00000000..a56a2eaf --- /dev/null +++ b/src/Airports/GenAirports850/poly_extra.hxx @@ -0,0 +1,50 @@ +// poly_extra.hxx -- Extra polygon manipulation routines +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: poly_extra.hxx,v 1.5 2004-11-19 22:25:49 curt Exp $ +// + + +#ifndef _POLY_EXTRA_HXX +#define _POLY_EXTRA_HXX + + +#include + +#include +#include + + +// Divide segment if there are other existing points on it, return the +// new polygon +void add_intermediate_nodes( int contour, const Point3D& start, + const Point3D& end, const TGTriNodes& tmp_nodes, + TGPolygon *result ); + + +// Search each segment for additional vertex points that may have been +// created elsewhere that lie on the segment and split it there to +// avoid "T" intersections. + +TGPolygon add_nodes_to_poly( const TGPolygon& poly, + const TGTriNodes& tmp_nodes ); + + +#endif // _POLY_EXTRA_HXX diff --git a/src/Airports/GenAirports850/process.pl b/src/Airports/GenAirports850/process.pl new file mode 100755 index 00000000..f26f5b7e --- /dev/null +++ b/src/Airports/GenAirports850/process.pl @@ -0,0 +1,73 @@ +#!/usr/bin/perl + +# this is a sad testament to building your software off of other +# libraries that have bugs (or perhaps less than disired features.) +# Unfortunatley I don't know enough about the functionality they +# provide to go fix them. Maybe someday. + +# At any rate. This script is a wrapper around the main airport +# generation utility. It run it until it finishes or crashes. If it +# finishes without saying, yup I'm all done, a crash is assumed. We +# re-run the airport generate from the crash point with a different +# nudge factor in a sorry attempt to work around the bug. + +# yes, I know this is a really ugly hack, I apologize in advance to +# those who might have trouble running these tools on non-unix +# platforms, but I'm not sure what else to do at this point. If +# someone can fix the polygon clipping library so it doesn't leave +# tiny shards of polygons or cracks, I'd be very grateful. + +# So here we go, children under 13 years of age should probably have +# parental supervision if playing with something this ugly. + + +# Edit the following values to set up your preferences: + +$workdir = "/stage/fgfs04/curt/Work"; +$inputfile = "./default.apt"; +$binary = "./genapts"; +$startid = ""; + +# end of user configurable section + + +$done = 0; +$nudge = 0; + +while ( ! $done ) { + + # update the nudge value + $nudge += 5; + if ( $nudge > 40 ) { + $nudge = 5; + } + + # launch the airport generator + + $command = "$binary --input=$inputfile --work=$workdir --nudge=$nudge"; + + if ( $startid ne "" ) { + $command .= " --start-id=$startid"; + } + + print "Executing $command\n"; + open( PIPE, "$command |" ) || die "Cannot run $command\n"; + + while ( ) { + if ( m/Id portion/ ) { + # print $_; + } + + if ( m/\[FINISHED CORRECTLY\]/ ) { + $done = 1; + print "FINISHED!\n"; + } + } + + close ( PIPE ); + + if ( ! $done ) { + $startid = `cat last_apt`; chop( $startid ); + print "Restarting at $startid.\n"; + } +} diff --git a/src/Airports/GenAirports850/process.sh b/src/Airports/GenAirports850/process.sh new file mode 100755 index 00000000..14021753 --- /dev/null +++ b/src/Airports/GenAirports850/process.sh @@ -0,0 +1,13 @@ +#! /bin/bash + +# Previously Skipped: EBPP +# Manually removed: NZSP (led to a runway around the world) +# Fixed: YSAR (was marked as land airport, but was a heliport) + +WORKDIR=$HOME/workdirs/world_scenery +#APTDAT="/home/martin/GIT/fgdata/Airports/apt.dat.gz" +#APTDAT="/home/rgerlich/rawdata/apt.dat.gz" +APTDAT="/home/rgerlich/rawdata/apt.helidat.gz" +SPAT="--nudge=20" + +exec /usr/bin/time genapts --input=$APTDAT --work=$WORKDIR $SPAT > genapts.log 2>&1 diff --git a/src/Airports/GenAirports850/runway.cxx b/src/Airports/GenAirports850/runway.cxx new file mode 100644 index 00000000..2239d2d8 --- /dev/null +++ b/src/Airports/GenAirports850/runway.cxx @@ -0,0 +1,191 @@ +// area.c -- routines to assist with inserting "areas" into FG terrain +// +// Written by Curtis Olson, started March 1998. +// +// Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: runway.cxx,v 1.18 2004-11-19 22:25:49 curt Exp $ +// + + +#include +#include + +#include +#include +#include +#include + +#include "runway.hxx" +#include "point2d.hxx" + + +// given a runway center point, length, width, and heading, and +// altitude (meters) generate the lon and lat 4 corners using wgs84 +// math. +TGPolygon gen_wgs84_area( Point3D origin, + double length_m, + double displ1, double displ2, + double width_m, + double heading_deg, + double alt_m, + bool add_mid ) +{ + TGPolygon result_list; + double length_hdg = heading_deg; + double left_hdg = length_hdg - 90.0; + if ( left_hdg < 0 ) { left_hdg += 360.0; } + + // move to the +l end/center of the runway + Point3D ref = origin; + double lon, lat, r; + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + length_m / 2.0 - displ2, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + // move to the l,-w corner (then we add points in a clockwise direction) + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), left_hdg, + -width_m / 2.0, &lat, &lon, &r ); + Point3D p = Point3D( lon, lat, 0.0 ); + result_list.add_node( 0, p ); + + // move to the l,w corner + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), left_hdg, + width_m / 2.0, &lat, &lon, &r ); + p = Point3D( lon, lat, 0.0 ); + result_list.add_node( 0, p ); + + if ( add_mid ) { + // move to the 0,w point (then we add points in a clockwise direction) + + ref = origin; + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), left_hdg, + width_m / 2.0, &lat, &lon, &r ); + p = Point3D( lon, lat, 0.0 ); + result_list.add_node( 0, p ); + } + + // move to the -l end/center of the runway + ref = origin; + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), length_hdg, + displ1 - length_m/2.0, &lat, &lon, &r ); + ref = Point3D( lon, lat, 0.0 ); + + // move to the -l,w corner (then we add points in a clockwise direction) + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), left_hdg, + width_m / 2.0, &lat, &lon, &r ); + p = Point3D( lon, lat, 0.0 ); + result_list.add_node( 0, p ); + + // move to the -l,-w corner + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), left_hdg, + -width_m / 2.0, &lat, &lon, &r ); + p = Point3D( lon, lat, 0.0 ); + result_list.add_node( 0, p ); + + if ( add_mid ) { + // move to the 0,-w point (then we add points in a clockwise direction) + + ref = origin; + geo_direct_wgs_84 ( alt_m, ref.lat(), ref.lon(), left_hdg, + -width_m / 2.0, &lat, &lon, &r ); + p = Point3D( lon, lat, 0.0 ); + result_list.add_node( 0, p ); + } + + return result_list; +} + + +// generate an area for a runway with expantion specified as a scale +// factor (return result points in degrees) +TGPolygon gen_runway_area_w_scale( const TGRunway& runway, + double alt_m, + double length_scale, + double width_scale ) { + + TGPolygon result_list; + Point3D origin(runway.lon, runway.lat, 0); + + result_list = gen_wgs84_area( origin, + runway.length*length_scale * SG_FEET_TO_METER, + 0.0, 0.0, + runway.width*width_scale * SG_FEET_TO_METER, + runway.heading, alt_m, false ); + + // display points + SG_LOG(SG_GENERAL, SG_DEBUG, "Results w/ scale (new way)"); + for ( int i = 0; i < result_list.contour_size( 0 ); ++i ) { + SG_LOG(SG_GENERAL, SG_DEBUG, " " << result_list.get_pt(0, i)); + } + + return result_list; +} + + +// generate an area for a runway with expansion specified in meters +// (return result points in degrees) +TGPolygon gen_runway_area_w_extend( const TGRunway& runway, + double alt_m, + double length_extend, + double displ1, double displ2, + double width_extend ) { + + TGPolygon result_list; + Point3D origin(runway.lon, runway.lat, 0); + + result_list + = gen_wgs84_area( origin, + runway.length*SG_FEET_TO_METER + 2.0*length_extend, + displ1, displ2, + runway.width*SG_FEET_TO_METER + 2.0*width_extend, + runway.heading, alt_m, false ); + + // display points + SG_LOG(SG_GENERAL, SG_DEBUG, "Results w/ extend (new way)"); + for ( int i = 0; i < result_list.contour_size( 0 ); ++i ) { + SG_LOG(SG_GENERAL, SG_DEBUG, " " << result_list.get_pt(0, i)); + } + + return result_list; +} + + +// generate an area for a runway and include midpoints +TGPolygon gen_runway_w_mid( const TGRunway& runway, + double alt_m, + double length_extend_m, + double width_extend_m ) { + TGPolygon result_list; + Point3D origin(runway.lon, runway.lat, 0); + + result_list = gen_wgs84_area( origin, + runway.length * SG_FEET_TO_METER + + 2.0*length_extend_m, + 0.0, 0.0, + runway.width * SG_FEET_TO_METER + + 2.0 * width_extend_m, + runway.heading, alt_m, true ); + + // display points + SG_LOG(SG_GENERAL, SG_DEBUG, "Results w/ mid (new way)"); + for ( int i = 0; i < result_list.contour_size( 0 ); ++i ) { + SG_LOG(SG_GENERAL, SG_DEBUG, " " << result_list.get_pt(0, i)); + } + + return result_list; +} diff --git a/src/Airports/GenAirports850/runway.hxx b/src/Airports/GenAirports850/runway.hxx new file mode 100644 index 00000000..2d6e6aaf --- /dev/null +++ b/src/Airports/GenAirports850/runway.hxx @@ -0,0 +1,111 @@ +// runway.hxx -- class to store runway info +// +// Written by Curtis Olson, started November 1999. +// +// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: runway.hxx,v 1.16 2005-04-20 18:20:15 curt Exp $ +// + + +#ifndef _RUNWAY_HXX +#define _RUNWAY_HXX + + +#include +#include + +#include + +#include + + +struct TGRunway { + std::string rwy_no; + + double lon; + double lat; + double heading; + double length; + double width; + double disp_thresh1; + double disp_thresh2; + double stopway1; + double stopway2; + + std::string lighting_flags; + int surface_code; + std::string shoulder_code; + int marking_code; + double smoothness; + bool dist_remaining; + + double gs_angle1; + double gs_angle2; + + TGPolygon threshold; + TGPolygon tens, tens_margin, ones, ones_margin; + TGPolygon letter, letter_margin_left, letter_margin_right; + TGPolygon pre_td_zone; + TGPolygon td3_zone, td2_zone, td1a_zone, td1b_zone; + TGPolygon aim_point; + + bool really_taxiway; + bool generated; +}; + + +typedef std::vector < TGRunway > runway_list; +typedef runway_list::iterator runway_list_iterator; +typedef runway_list::const_iterator const_runway_list_iterator; + + +// given a runway center point, length, width, and heading, and +// altitude (meters) generate the lon and lat 4 corners using wgs84 +// math. +TGPolygon gen_wgs84_area( Point3D origin, + double length_m, + double displ1, double displ2, + double width_m, + double heading_deg, + double alt_m, + bool add_mid ); + +// generate an area for a runway with expantion specified as a scale +// factor (return result points in degrees) +TGPolygon gen_runway_area_w_scale( const TGRunway& runway, + double alt_m, + double length_scale = 1.0, + double width_scale = 1.0 ); + +// generate an area for a runway with expansion specified in meters +// (return result points in degrees) +TGPolygon gen_runway_area_w_extend( const TGRunway& runway, + double alt_m, + double length_extend, + double displ1, double displ2, + double width_extend ); + + +// generate an area for half a runway +TGPolygon gen_runway_w_mid( const TGRunway& runway, + double alt_m, + double length_extend_m, + double width_extend_m ); + + +#endif // _RUNWAY_HXX diff --git a/src/Airports/GenAirports850/rwy_common.cxx b/src/Airports/GenAirports850/rwy_common.cxx new file mode 100644 index 00000000..557b296b --- /dev/null +++ b/src/Airports/GenAirports850/rwy_common.cxx @@ -0,0 +1,327 @@ +// rwy_common.cxx -- Common runway generation routines +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: rwy_common.cxx,v 1.12 2004-11-19 22:25:49 curt Exp $ +// + +#include +#include +#include + +#include "global.hxx" +#include "poly_extra.hxx" +#include "rwy_common.hxx" + +#include + +using std::string; + + +void gen_number_block( const TGRunway& rwy_info, + const string& material, + TGPolygon poly, double heading, int num, + double start_pct, double end_pct, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ) +{ + char tex1[32]; tex1[0] = '\0'; + char tex2[32]; tex2[0] = '\0'; + + SG_LOG(SG_GENERAL, SG_DEBUG, "Runway num = " << num); + + if ( num == 0 ) { + SG_LOG( SG_GENERAL, SG_ALERT, + "Ack! Someone passed in a runway number of '0'" ); + exit(-1); + } + + if ( num == 11 ) { + sprintf( tex1, "11" ); + } else if ( num < 10 ) { + sprintf( tex1, "%dc", num ); + } else { + sprintf( tex1, "%dl", num / 10 ); + sprintf( tex2, "%dr", num - (num / 10 * 10)); + } + + // printf("tex1 = '%s' tex2 = '%s'\n", tex1, tex2); + + if ( num < 10 ) { + gen_runway_section( rwy_info, poly, + start_pct, end_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + heading, + material, tex1, + rwy_polys, texparams, accum ); + } else if ( num == 11 ) { + gen_runway_section( rwy_info, poly, + start_pct, end_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + heading, + material, tex1, + rwy_polys, texparams, accum ); + } else { + gen_runway_section( rwy_info, poly, + start_pct, end_pct, + 0.0, 0.5, + 0.0, 1.0, 0.0, 1.0, + heading, + material, tex1, + rwy_polys, texparams, accum ); + gen_runway_section( rwy_info, poly, + start_pct, end_pct, + 0.5, 1.0, + 0.0, 1.0, 0.0, 1.0, + heading, + material, tex2, + rwy_polys, texparams, accum ); + } +} + +// generate the runway stopway +void gen_runway_stopway( const TGRunway& rwy_info, + const TGPolygon& runway_a, + const TGPolygon& runway_b, + const string& prefix, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon* accum ) { + const float length = rwy_info.length / 2.0 + 2.0; + double start1_pct = 0.0; + double start2_pct = 0.0; + double end1_pct = 0.0; + double end2_pct = 0.0; + double part_len = 0.0; + + int count=0; + int i=0; + + if (rwy_info.stopway1 > 0.0) { + /* Generate approach end stopway */ + count = (int) (rwy_info.stopway1 * 2.0/ rwy_info.width); + if(count < 1) count = 1; + part_len = rwy_info.stopway1 / (double) count; + for(i=0;i 0.0) { + /* Generate reciprocal end stopway */ + count = (int) (rwy_info.stopway2 * 2.0 / rwy_info.width); + if(count < 1) count = 1; + part_len = rwy_info.stopway2 / (double) count; + for(i=0;i 0.0 ) { + startl_pct -= nudge * SG_EPSILON; + } + if ( endl_pct < 1.0 ) { + endl_pct += nudge * SG_EPSILON; + } + + if ( endl_pct > 1.0 ) { + endl_pct = 1.0; + } + + // partial "w" percentages could introduce "T" intersections which + // we compensate for later, but could still cause problems now + // with our polygon clipping code. This attempts to compensate + // for that by nudging the areas a bit bigger so we don't end up + // with polygon slivers. + if ( startw_pct > 0.0 || endw_pct < 1.0 ) { + if ( startw_pct > 0.0 ) { + startw_pct -= nudge * SG_EPSILON; + } + if ( endw_pct < 1.0 ) { + endw_pct += nudge * SG_EPSILON; + } + } + + SG_LOG(SG_GENERAL, SG_DEBUG, "start len % = " << startl_pct + << " end len % = " << endl_pct); + + double dlx, dly; + + dlx = a1.x() - a0.x(); + dly = a1.y() - a0.y(); + + Point3D t0 = Point3D( a0.x() + dlx * startl_pct, + a0.y() + dly * startl_pct, 0); + Point3D t1 = Point3D( a0.x() + dlx * endl_pct, + a0.y() + dly * endl_pct, 0); + + dlx = a3.x() - a2.x(); + dly = a3.y() - a2.y(); + + Point3D t2 = Point3D( a2.x() + dlx * startl_pct, + a2.y() + dly * startl_pct, 0); + + Point3D t3 = Point3D( a2.x() + dlx * endl_pct, + a2.y() + dly * endl_pct, 0); + + SG_LOG(SG_GENERAL, SG_DEBUG, "start wid % = " << startw_pct + << " end wid % = " << endw_pct); + + double dwx, dwy; + + dwx = t0.x() - t2.x(); + dwy = t0.y() - t2.y(); + + Point3D p0 = Point3D( t2.x() + dwx * startw_pct, + t2.y() + dwy * startw_pct, 0); + + Point3D p1 = Point3D( t2.x() + dwx * endw_pct, + t2.y() + dwy * endw_pct, 0); + + dwx = t1.x() - t3.x(); + dwy = t1.y() - t3.y(); + + Point3D p2 = Point3D( t3.x() + dwx * startw_pct, + t3.y() + dwy * startw_pct, 0); + + Point3D p3 = Point3D( t3.x() + dwx * endw_pct, + t3.y() + dwy * endw_pct, 0); + + TGPolygon section; + section.erase(); + + section.add_node( 0, p2 ); + section.add_node( 0, p0 ); + section.add_node( 0, p1 ); + section.add_node( 0, p3 ); + + // print runway points + SG_LOG(SG_GENERAL, SG_DEBUG, "pre clipped runway pts " << prefix << material); + for ( j = 0; j < section.contours(); ++j ) { + for ( k = 0; k < section.contour_size( j ); ++k ) { + Point3D p = section.get_pt(j, k); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + } + + // Clip the new polygon against what ever has already been created. + TGPolygon clipped = tgPolygonDiff( section, *accum ); + + // Split long edges to create an object that can better flow with + // the surface terrain + TGPolygon split = tgPolygonSplitLongEdges( clipped, 400.0 ); + + // Create the final output and push on to the runway super_polygon + // list + TGSuperPoly sp; + sp.erase(); + sp.set_poly( split ); + sp.set_material( prefix + material ); + rwy_polys->push_back( sp ); + SG_LOG(SG_GENERAL, SG_DEBUG, "section = " << clipped.contours()); + *accum = tgPolygonUnion( section, *accum ); + + // Store away what we need to know for texture coordinate + // calculation. (CLO 10/20/02: why can't we calculate texture + // coordinates here? Oh, becuase later we need to massage the + // polygons to avoid "T" intersections and clean up other + // potential artifacts and we may add or remove points and need to + // do new texture coordinate calcs later. + + // we add 2' to the length for texture overlap. This puts the + // lines on the texture back to the edge of the runway where they + // belong. + double len = rwy_info.length / 2.0 + 2; + double sect_len = len * ( endl_pct - startl_pct ); + + // we add 2' to both sides of the runway (4' total) for texture + // overlap. This puts the lines on the texture back to the edge + // of the runway where they belong. + double wid = rwy_info.width + 4; + double sect_wid = wid * ( endw_pct - startw_pct ); + + TGTexParams tp; + tp = TGTexParams( p0, + sect_wid * SG_FEET_TO_METER, + sect_len * SG_FEET_TO_METER, + heading ); + tp.set_minu( minu ); + tp.set_maxu( maxu ); + tp.set_minv( minv ); + tp.set_maxv( maxv ); + texparams->push_back( tp ); + + // print runway points + SG_LOG(SG_GENERAL, SG_DEBUG, "clipped runway pts " << prefix + material); + for ( j = 0; j < clipped.contours(); ++j ) { + for ( k = 0; k < clipped.contour_size( j ); ++k ) { + Point3D p = clipped.get_pt(j, k); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + } +} diff --git a/src/Airports/GenAirports850/rwy_common.hxx b/src/Airports/GenAirports850/rwy_common.hxx new file mode 100644 index 00000000..7521fc84 --- /dev/null +++ b/src/Airports/GenAirports850/rwy_common.hxx @@ -0,0 +1,67 @@ +// rwy_common.hxx -- Common runway generation routines +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: rwy_common.hxx,v 1.5 2004-11-19 22:25:49 curt Exp $ +// + + +#ifndef _RWY_COMMON_HXX +#define _RWY_COMMON_HXX + + +#include +#include + +#include "runway.hxx" +#include "texparams.hxx" + + +void gen_number_block( const TGRunway& rwy_info, + const std::string& material, + TGPolygon poly, double heading, int num, + double start_pct, double end_pct, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ); + +// generate the runway stopway +void gen_runway_stopway( const TGRunway& rwy_info, + const TGPolygon& runway_a, + const TGPolygon& runway_b, + const std::string& prefix, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon* accum ); + +// generate a section of runway +void gen_runway_section( const TGRunway& rwy_info, + const TGPolygon& runway, + double startl_pct, double endl_pct, + double startw_pct, double endw_pct, + double minu, double maxu, double minv, double maxv, + double heading, + const std::string& prefix, + const std::string& material, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ); + + +#endif // _RWY_COMMON_HXX diff --git a/src/Airports/GenAirports850/rwy_gen.cxx b/src/Airports/GenAirports850/rwy_gen.cxx new file mode 100644 index 00000000..d11f94ba --- /dev/null +++ b/src/Airports/GenAirports850/rwy_gen.cxx @@ -0,0 +1,619 @@ +// rwy_prec.cxx -- Build a precision runway +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: rwy_prec.cxx,v 1.18 2004-11-19 22:25:49 curt Exp $ +// + +#include +#include +#include + +#include "rwy_common.hxx" +#include "rwy_nonprec.hxx" + +#include + +using std::string; + + +// generate a precision approach runway. The routine modifies +// rwy_polys, texparams, and accum. For specific details and +// dimensions of precision runway markings, please refer to FAA +// document AC 150/5340-1H + +void gen_precision_rwy( const TGRunway& rwy_info, + double alt_m, + const string& material, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ) +{ + SG_LOG( SG_GENERAL, SG_INFO, "Building runway = " << rwy_info.rwy_no ); + + // + // Generate the basic runway outlines + // + + int i; + + TGPolygon runway = gen_runway_w_mid( rwy_info, alt_m, + 2 * SG_FEET_TO_METER, + 2 * SG_FEET_TO_METER ); + + // runway half "a" (actually the reverse half) + TGPolygon runway_a; + runway_a.erase(); + runway_a.add_node( 0, runway.get_pt(0, 0) ); + runway_a.add_node( 0, runway.get_pt(0, 1) ); + runway_a.add_node( 0, runway.get_pt(0, 2) ); + runway_a.add_node( 0, runway.get_pt(0, 5) ); + + + // runway half "b" (actually the forward half) + TGPolygon runway_b; + runway_b.erase(); + runway_b.add_node( 0, runway.get_pt(0, 3) ); + runway_b.add_node( 0, runway.get_pt(0, 4) ); + runway_b.add_node( 0, runway.get_pt(0, 5) ); + runway_b.add_node( 0, runway.get_pt(0, 2) ); + + Point3D p; + SG_LOG(SG_GENERAL, SG_DEBUG, "raw runway pts (a half)"); + for ( i = 0; i < runway_a.contour_size( 0 ); ++i ) { + p = runway_a.get_pt(0, i); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + SG_LOG(SG_GENERAL, SG_DEBUG, "raw runway pts (b half)"); + for ( i = 0; i < runway_b.contour_size( 0 ); ++i ) { + p = runway_b.get_pt(0, i); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + + // + // Setup some variables and values to help us chop up the runway + // into its various sections + // + + TGSuperPoly sp; + TGTexParams tp; + + // we add 2' to the length for texture overlap. This puts the + // lines on the texture back to the edge of the runway where they + // belong. + double length = rwy_info.length / 2.0 + 2.0; + if ( length < 3075 ) { + SG_LOG( SG_GENERAL, SG_ALERT, + "Runway " << rwy_info.rwy_no << " is not long enough (" + << rwy_info.length << ") for precision markings!"); + } + + double start1_pct = 0.0; + double start2_pct = 0.0; + double end1_pct = 0.0; + double end2_pct = 0.0; + + // + // Displaced threshold if it exists + // + + if ( rwy_info.disp_thresh1 > 0.0 ) { + SG_LOG( SG_GENERAL, SG_INFO, "Forward displaced threshold = " + << rwy_info.disp_thresh1 ); + + // reserve 90' for final arrows + double thresh = rwy_info.disp_thresh1 - 90.0; + + // number of full center arrows + int count = (int)(thresh / 200.0); + + // length of starting partial arrow + double part_len = thresh - ( count * 200.0 ); + double tex_pct = (200.0 - part_len) / 200.0; + + // starting (possibly partial chunk) + start2_pct = end2_pct; + end2_pct = start2_pct + ( part_len / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, tex_pct, 1.0, + rwy_info.heading + 180.0, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + + // main chunks + for ( i = 0; i < count; ++i ) { + start2_pct = end2_pct; + end2_pct = start2_pct + ( 200.0 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + } + + // final arrows + start2_pct = end2_pct; + end2_pct = start2_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "dspl_arrows", + rwy_polys, texparams, accum ); + } + + if ( rwy_info.disp_thresh2 > 0.0 ) { + SG_LOG( SG_GENERAL, SG_INFO, "Reverse displaced threshold = " + << rwy_info.disp_thresh2 ); + + // reserve 90' for final arrows + double thresh = rwy_info.disp_thresh2 - 90.0; + + // number of full center arrows + int count = (int)(thresh / 200.0); + + // length of starting partial arrow + double part_len = thresh - ( count * 200.0 ); + double tex_pct = (200.0 - part_len) / 200.0; + + // starting (possibly partial chunk) + start1_pct = end1_pct; + end1_pct = start1_pct + ( part_len / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, tex_pct, 1.0, + rwy_info.heading, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + + // main chunks + for ( i = 0; i < count; ++i ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 200.0 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + } + + // final arrows + start1_pct = end1_pct; + end1_pct = start1_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "dspl_arrows", + rwy_polys, texparams, accum ); + } + + // + // Threshold + // + + start1_pct = end1_pct; + end1_pct = start1_pct + ( 202.0 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "threshold", + rwy_polys, texparams, accum ); + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 202.0 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "threshold", + rwy_polys, texparams, accum ); + + // + // Runway designation letter + // + + int len = rwy_info.rwy_no.length(); + string letter = ""; + string rev_letter = ""; + for ( i = 0; i < len; ++i ) { + string tmp = rwy_info.rwy_no.substr(i, 1); + if ( tmp == "L" ) { + letter = "L"; + rev_letter = "R"; + } else if ( tmp == "R" ) { + letter = "R"; + rev_letter = "L"; + } else if ( tmp == "C" ) { + letter = "C"; + rev_letter = "C"; + } + } + + SG_LOG(SG_GENERAL, SG_DEBUG, "Runway designation = " << rwy_info.rwy_no); + SG_LOG(SG_GENERAL, SG_DEBUG, "Runway designation letter = " << letter); + + if ( !letter.empty() ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, rev_letter, + rwy_polys, texparams, accum ); + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, letter, + rwy_polys, texparams, accum ); + } + + // + // Runway designation number(s) + // + + len = rwy_info.rwy_no.length(); + string snum = rwy_info.rwy_no; + for ( i = 0; i < len; ++i ) { + string tmp = rwy_info.rwy_no.substr(i, 1); + if ( tmp == "L" || tmp == "R" || tmp == "C" || tmp == " " ) { + snum = rwy_info.rwy_no.substr(0, i); + } + } + SG_LOG(SG_GENERAL, SG_INFO, "Runway num = '" << snum << "'"); + int num = atoi( snum.c_str() ); + while ( num <= 0 ) { + num += 36; + } + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 80.0 / length ); + gen_number_block( rwy_info, material, runway_b, rwy_info.heading + 180.0, + num, start2_pct, end2_pct, rwy_polys, texparams, accum ); + + num += 18; + while ( num > 36 ) { + num -= 36; + } + + start1_pct = end1_pct; + end1_pct = start1_pct + ( 80.0 / length ); + gen_number_block( rwy_info, material, runway_a, rwy_info.heading, + num, start1_pct, end1_pct, rwy_polys, texparams, accum ); + + // + // Touch down zone x3 + // + + start1_pct = end1_pct; + end1_pct = start1_pct + ( 380 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "tz_three", + rwy_polys, texparams, accum ); + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 380 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "tz_three", + rwy_polys, texparams, accum ); + + // add a section of center stripe + start1_pct = end1_pct; + end1_pct = start1_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "rest", + rwy_polys, texparams, accum ); + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "rest", + rwy_polys, texparams, accum ); + + // + // Aiming point + // + + start1_pct = end1_pct; + end1_pct = start1_pct + ( 400 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "aim", + rwy_polys, texparams, accum ); + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 400 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "aim", + rwy_polys, texparams, accum ); + + // + // Touch down zone x2 (first) + // + + if ( end1_pct < 1.0 ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 400 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "tz_two_a", + rwy_polys, texparams, accum ); + } + + if ( end2_pct < 1.0 ) { + start2_pct = end2_pct; + end2_pct = start2_pct + ( 400 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "tz_two_a", + rwy_polys, texparams, accum ); + } + + // add a section of center stripe + if ( end1_pct < 1.0 ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "rest", + rwy_polys, texparams, accum ); + } + + if ( end2_pct < 1.0 ) { + start2_pct = end2_pct; + end2_pct = start2_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "rest", + rwy_polys, texparams, accum ); + } + + // + // Touch down zone x2 (second) + // + + if ( end1_pct < 1.0 ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "tz_two_b", + rwy_polys, texparams, accum ); + } + + if ( end2_pct < 1.0 ) { + start2_pct = end2_pct; + end2_pct = start2_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "tz_two_b", + rwy_polys, texparams, accum ); + } + + // add a section of center stripe + if ( end1_pct < 1.0 ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "rest", + rwy_polys, texparams, accum ); + } + + if ( end2_pct < 1.0 ) { + start2_pct = end2_pct; + end2_pct = start2_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "rest", + rwy_polys, texparams, accum ); + } + + // + // Touch down zone x1 (first) + // + + if ( end1_pct < 1.0 ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 400 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "tz_one_a", + rwy_polys, texparams, accum ); + } + + if ( end2_pct < 1.0 ) { + start2_pct = end2_pct; + end2_pct = start2_pct + ( 400 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "tz_one_a", + rwy_polys, texparams, accum ); + } + + // add a section of center stripe + if ( end1_pct < 1.0 ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "rest", + rwy_polys, texparams, accum ); + } + + if ( end2_pct < 1.0 ) { + start2_pct = end2_pct; + end2_pct = start2_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "rest", + rwy_polys, texparams, accum ); + } + + // + // Touch down zone x1 (second) + // + + if ( end1_pct < 1.0 ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "tz_one_b", + rwy_polys, texparams, accum ); + } + + if ( end2_pct < 1.0 ) { + start2_pct = end2_pct; + end2_pct = start2_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "tz_one_b", + rwy_polys, texparams, accum ); + } + + // + // The rest ... + // + + // fit the 'rest' texture in as many times as will go evenly into + // the remaining distance so we don't end up with a super short + // section at the end. + double ideal_rest_inc = ( 200.0 / length ); + int divs = (int)((1.0 - end1_pct) / ideal_rest_inc) + 1; + double rest1_inc = (1.0 - end1_pct) / divs; + + while ( end1_pct < 1.0 ) { + start1_pct = end1_pct; + end1_pct = start1_pct + rest1_inc; + + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "rest", + rwy_polys, texparams, accum ); + } + + ideal_rest_inc = ( 200.0 / length ); + divs = (int)((1.0 - end2_pct) / ideal_rest_inc) + 1; + double rest2_inc = (1.0 - end2_pct) / divs; + + while ( end2_pct < 1.0 ) { + start2_pct = end2_pct; + end2_pct = start2_pct + rest2_inc; + + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "rest", + rwy_polys, texparams, accum ); + } + + gen_runway_stopway( rwy_info, runway_a, runway_b, + material, + rwy_polys, texparams, accum ); +} diff --git a/src/Airports/GenAirports850/rwy_gen.hxx b/src/Airports/GenAirports850/rwy_gen.hxx new file mode 100644 index 00000000..313b7ae5 --- /dev/null +++ b/src/Airports/GenAirports850/rwy_gen.hxx @@ -0,0 +1,49 @@ +// rwy_prec.hxx -- Build a precision runway +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: rwy_prec.hxx,v 1.5 2004-11-19 22:25:49 curt Exp $ +// + + +#ifndef _RWY_PREC_HXX +#define _RWY_PREC_HXX + + +#include +#include + +#include "runway.hxx" +#include "texparams.hxx" + + +// generate a precision approach runway. The routine modifies +// rwy_polys, texparams, and accum. For specific details and +// dimensions of precision runway markings, please refer to FAA +// document AC 150/5340-1H + +void gen_precision_rwy( const TGRunway& rwy_info, + double alt_m, + const std::string& material, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ); + + +#endif // _RWY_PREC_HXX diff --git a/src/Airports/GenAirports850/rwy_nonprec.cxx b/src/Airports/GenAirports850/rwy_nonprec.cxx new file mode 100644 index 00000000..085c19a0 --- /dev/null +++ b/src/Airports/GenAirports850/rwy_nonprec.cxx @@ -0,0 +1,417 @@ +// rwy_nonprec.cxx -- Build a non-precision runway +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: rwy_nonprec.cxx,v 1.16 2004-11-19 22:25:49 curt Exp $ +// + +#include +#include +#include + +#include "rwy_common.hxx" +#include "rwy_nonprec.hxx" + +#include + +using std::string; + + +// generate a non-precision approach runway. The routine modifies +// rwy_polys, texparams, and accum. For specific details and +// dimensions of precision runway markings, please refer to FAA +// document AC 150/5340-1H + +void gen_non_precision_rwy( const TGRunway& rwy_info, + double alt_m, + const string& material, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ) +{ + int i, j; + + // + // Generate the basic runway outlines + // + + TGPolygon runway = gen_runway_w_mid( rwy_info, alt_m, + 2 * SG_FEET_TO_METER, + 2 * SG_FEET_TO_METER ); + + // runway half "a" + TGPolygon runway_a; + runway_a.erase(); + runway_a.add_node( 0, runway.get_pt(0, 0) ); + runway_a.add_node( 0, runway.get_pt(0, 1) ); + runway_a.add_node( 0, runway.get_pt(0, 2) ); + runway_a.add_node( 0, runway.get_pt(0, 5) ); + + // runway half "b" + TGPolygon runway_b; + runway_b.erase(); + runway_b.add_node( 0, runway.get_pt(0, 3) ); + runway_b.add_node( 0, runway.get_pt(0, 4) ); + runway_b.add_node( 0, runway.get_pt(0, 5) ); + runway_b.add_node( 0, runway.get_pt(0, 2) ); + + Point3D p; + SG_LOG(SG_GENERAL, SG_DEBUG, "raw runway pts (a half)"); + for ( j = 0; j < runway_a.contour_size( 0 ); ++j ) { + p = runway_a.get_pt(0, j); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + SG_LOG(SG_GENERAL, SG_DEBUG, "raw runway pts (b half)"); + for ( j = 0; j < runway_b.contour_size( 0 ); ++j ) { + p = runway_b.get_pt(0, j); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + + // + // Setup some variables and values to help us chop up the runway + // into its various sections + // + + TGSuperPoly sp; + TGTexParams tp; + + // we add 2' to the length for texture overlap. This puts the + // lines on the texture back to the edge of the runway where they + // belong. + double length = rwy_info.length / 2.0 + 2.0; + if ( length < 1150 ) { + SG_LOG(SG_GENERAL, SG_ALERT, + "This runway is not long enough for non-precision markings!"); + } + + double start1_pct = 0.0; + double start2_pct = 0.0; + double end1_pct = 0.0; + double end2_pct = 0.0; + + // + // Displaced threshold if it exists + // + + if ( rwy_info.disp_thresh1 > 0.0 ) { + SG_LOG( SG_GENERAL, SG_INFO, "Forward displaced threshold = " + << rwy_info.disp_thresh1 ); + + // reserve 90' for final arrows + double thresh = rwy_info.disp_thresh1 - 90.0; + + // number of full center arrows + int count = (int)(thresh / 200.0); + + // length of starting partial arrow + double part_len = thresh - ( count * 200.0 ); + double tex_pct = (200.0 - part_len) / 200.0; + + // starting (possibly partial chunk) + start2_pct = end2_pct; + end2_pct = start2_pct + ( part_len / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, tex_pct, 1.0, + rwy_info.heading + 180.0, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + + // main chunks + for ( i = 0; i < count; ++i ) { + start2_pct = end2_pct; + end2_pct = start2_pct + ( 200.0 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + } + + // final arrows + start2_pct = end2_pct; + end2_pct = start2_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "dspl_arrows", + rwy_polys, texparams, accum ); + } + + if ( rwy_info.disp_thresh2 > 0.0 ) { + SG_LOG( SG_GENERAL, SG_INFO, "Reverse displaced threshold = " + << rwy_info.disp_thresh2 ); + + // reserve 90' for final arrows + double thresh = rwy_info.disp_thresh2 - 90.0; + + // number of full center arrows + int count = (int)(thresh / 200.0); + + // length of starting partial arrow + double part_len = thresh - ( count * 200.0 ); + double tex_pct = (200.0 - part_len) / 200.0; + + // starting (possibly partial chunk) + start1_pct = end1_pct; + end1_pct = start1_pct + ( part_len / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, tex_pct, 1.0, + rwy_info.heading, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + + // main chunks + for ( i = 0; i < count; ++i ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 200.0 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + } + + // final arrows + start1_pct = end1_pct; + end1_pct = start1_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "dspl_arrows", + rwy_polys, texparams, accum ); + } + + // + // Threshold + // + + start1_pct = end1_pct; + end1_pct = start1_pct + ( 202.0 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "threshold", + rwy_polys, texparams, accum ); + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 202.0 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "threshold", + rwy_polys, texparams, accum ); + + // + // Runway designation letter + // + + int len = rwy_info.rwy_no.length(); + string letter = ""; + string rev_letter = ""; + for ( i = 0; i < len; ++i ) { + string tmp = rwy_info.rwy_no.substr(i, 1); + if ( tmp == "L" ) { + letter = "L"; + rev_letter = "R"; + } else if ( tmp == "R" ) { + letter = "R"; + rev_letter = "L"; + } else if ( tmp == "C" ) { + letter = "C"; + rev_letter = "C"; + } + } + + SG_LOG(SG_GENERAL, SG_DEBUG, "Runway designation = " << rwy_info.rwy_no); + SG_LOG(SG_GENERAL, SG_DEBUG, "Runway designation letter = " << letter); + + if ( !letter.empty() ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, rev_letter, + rwy_polys, texparams, accum ); + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, letter, + rwy_polys, texparams, accum ); + } + + // + // Runway designation number(s) + // + + len = rwy_info.rwy_no.length(); + string snum = rwy_info.rwy_no; + for ( i = 0; i < len; ++i ) { + string tmp = rwy_info.rwy_no.substr(i, 1); + if ( tmp == "L" || tmp == "R" || tmp == "C" || tmp == " " ) { + snum = rwy_info.rwy_no.substr(0, i); + } + } + SG_LOG(SG_GENERAL, SG_INFO, "Runway num = '" << snum << "'"); + int num = atoi( snum.c_str() ); + while ( num <= 0 ) { + num += 36; + } + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 80.0 / length ); + gen_number_block( rwy_info, material, runway_b, rwy_info.heading + 180.0, + num, start2_pct, end2_pct, rwy_polys, texparams, accum ); + + num += 18; + while ( num > 36 ) { + num -= 36; + } + + start1_pct = end1_pct; + end1_pct = start1_pct + ( 80.0 / length ); + gen_number_block( rwy_info, material, runway_a, rwy_info.heading, + num, start1_pct, end1_pct, rwy_polys, texparams, accum ); + + if ( true ) { + // + // Intermediate area before aiming point ... + // + + for ( i = 0; i < 3; ++i ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "centerline", + rwy_polys, texparams, accum ); + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "centerline", + rwy_polys, texparams, accum ); + } + + // + // Aiming point + // + + if ( end1_pct >= 1.0 ) { + return; + } + start1_pct = end1_pct; + end1_pct = start1_pct + ( 400 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "aim", + rwy_polys, texparams, accum ); + + if ( end2_pct >= 1.0 ) { + return; + } + start2_pct = end2_pct; + end2_pct = start2_pct + ( 400 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "aim", + rwy_polys, texparams, accum ); + } + + // + // The rest ... + // + + // fit the 'rest' texture in as many times as will go evenly into + // the remaining distance so we don't end up with a super short + // section at the end. + double ideal_rest_inc = ( 200.0 / length ); + int divs = (int)((1.0 - end1_pct) / ideal_rest_inc) + 1; + double rest1_inc = (1.0 - end1_pct) / divs; + + while ( end1_pct < 1.0 ) { + start1_pct = end1_pct; + end1_pct = start1_pct + rest1_inc; + + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "rest", + rwy_polys, texparams, accum ); + } + + ideal_rest_inc = ( 200.0 / length ); + divs = (int)((1.0 - end2_pct) / ideal_rest_inc) + 1; + double rest2_inc = (1.0 - end2_pct) / divs; + + while ( end2_pct < 1.0 ) { + start2_pct = end2_pct; + end2_pct = start2_pct + rest2_inc; + + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "rest", + rwy_polys, texparams, accum ); + } + + gen_runway_stopway( rwy_info, runway_a, runway_b, + material, + rwy_polys, texparams, accum ); +} diff --git a/src/Airports/GenAirports850/rwy_nonprec.hxx b/src/Airports/GenAirports850/rwy_nonprec.hxx new file mode 100644 index 00000000..b7c70790 --- /dev/null +++ b/src/Airports/GenAirports850/rwy_nonprec.hxx @@ -0,0 +1,49 @@ +// rwy_nonprec.hxx -- Build a non-precision runway +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: rwy_nonprec.hxx,v 1.5 2004-11-19 22:25:49 curt Exp $ +// + + +#ifndef _RWY_NONPREC_HXX +#define _RWY_NONPREC_HXX + + +#include +#include + +#include "runway.hxx" +#include "texparams.hxx" + + +// generate a non-precision approach runway. The routine modifies +// rwy_polys, texparams, and accum. For specific details and +// dimensions of precision runway markings, please refer to FAA +// document AC 150/5340-1H + +void gen_non_precision_rwy( const TGRunway& rwy_info, + double alt_m, + const std::string& material, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ); + + +#endif // _RWY_NONPREC_HXX diff --git a/src/Airports/GenAirports850/rwy_simple.cxx b/src/Airports/GenAirports850/rwy_simple.cxx new file mode 100644 index 00000000..d88f42d4 --- /dev/null +++ b/src/Airports/GenAirports850/rwy_simple.cxx @@ -0,0 +1,139 @@ +// rwy_simple.cxx -- Build a simple (non-marked) runway +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: rwy_simple.cxx,v 1.12 2004-11-19 22:25:49 curt Exp $ +// + + +#include +#include +#include + +#include "poly_extra.hxx" +#include "rwy_common.hxx" +#include "texparams.hxx" +#include "rwy_nonprec.hxx" + +using std::string; + + +// generate a simple runway. The routine modifies rwy_polys, +// texparams, and accum +void gen_simple_rwy( const TGRunway& rwy_info, + double alt_m, + const string& material, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ) +{ + int j, k; + + TGPolygon runway = gen_runway_w_mid( rwy_info, alt_m, 0.0, 0.0 ); + + // runway half "a" + TGPolygon runway_a; + runway_a.erase(); + runway_a.add_node( 0, runway.get_pt(0, 0) ); + runway_a.add_node( 0, runway.get_pt(0, 1) ); + runway_a.add_node( 0, runway.get_pt(0, 2) ); + runway_a.add_node( 0, runway.get_pt(0, 5) ); + + // runway half "b" + TGPolygon runway_b; + runway_b.erase(); + runway_b.add_node( 0, runway.get_pt(0, 5) ); + runway_b.add_node( 0, runway.get_pt(0, 2) ); + runway_b.add_node( 0, runway.get_pt(0, 3) ); + runway_b.add_node( 0, runway.get_pt(0, 4) ); + + Point3D p; + SG_LOG(SG_GENERAL, SG_DEBUG, "raw runway pts (a half)"); + for ( j = 0; j < runway_a.contour_size( 0 ); ++j ) { + p = runway_a.get_pt(0, j); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + SG_LOG(SG_GENERAL, SG_DEBUG, "raw runway pts (b half)"); + for ( j = 0; j < runway_b.contour_size( 0 ); ++j ) { + p = runway_b.get_pt(0, j); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + + TGSuperPoly sp; + TGTexParams tp; + + TGPolygon clipped_a = tgPolygonDiff( runway_a, *accum ); + TGPolygon split_a = tgPolygonSplitLongEdges( clipped_a, 400.0 ); + sp.erase(); + sp.set_poly( split_a ); + sp.set_material( material ); + rwy_polys->push_back( sp ); + SG_LOG(SG_GENERAL, SG_DEBUG, "clipped_a = " << clipped_a.contours()); + *accum = tgPolygonUnion( runway_a, *accum ); + tp = TGTexParams( runway_a.get_pt(0,0), + rwy_info.width * SG_FEET_TO_METER, + rwy_info.length * SG_FEET_TO_METER / 2.0, + rwy_info.heading ); + texparams->push_back( tp ); + + TGPolygon clipped_b = tgPolygonDiff( runway_b, *accum ); + TGPolygon split_b = tgPolygonSplitLongEdges( clipped_b, 400.0 ); + sp.erase(); + sp.set_poly( split_b ); + sp.set_material( material ); + rwy_polys->push_back( sp ); + SG_LOG(SG_GENERAL, SG_DEBUG, "clipped_b = " << clipped_b.contours()); + *accum = tgPolygonUnion( runway_b, *accum ); + tp = TGTexParams( runway_b.get_pt(0,2), + rwy_info.width * SG_FEET_TO_METER, + rwy_info.length * SG_FEET_TO_METER / 2.0, + rwy_info.heading + 180.0 ); + texparams->push_back( tp ); + +#if 0 + // after clip, but before removing T intersections + char tmpa[256], tmpb[256]; + sprintf( tmpa, "a%d", i ); + sprintf( tmpb, "b%d", i ); + write_polygon( clipped_a, tmpa ); + write_polygon( clipped_b, tmpb ); +#endif + + // print runway points + SG_LOG(SG_GENERAL, SG_DEBUG, "clipped runway pts (a)"); + for ( j = 0; j < clipped_a.contours(); ++j ) { + for ( k = 0; k < clipped_a.contour_size( j ); ++k ) { + p = clipped_a.get_pt(j, k); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + } + + // print runway points + SG_LOG(SG_GENERAL, SG_DEBUG, "clipped runway pts (b)"); + for ( j = 0; j < clipped_b.contours(); ++j ) { + for ( k = 0; k < clipped_b.contour_size( j ); ++k ) { + p = clipped_b.get_pt(j, k); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + } + + gen_runway_stopway( rwy_info, runway_a, runway_b, + material, + rwy_polys, texparams, accum ); +} diff --git a/src/Airports/GenAirports850/rwy_simple.hxx b/src/Airports/GenAirports850/rwy_simple.hxx new file mode 100644 index 00000000..6b3183ab --- /dev/null +++ b/src/Airports/GenAirports850/rwy_simple.hxx @@ -0,0 +1,46 @@ +// rwy_simple.hxx -- Build a simple (non-marked) runway +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: rwy_simple.hxx,v 1.5 2004-11-19 22:25:49 curt Exp $ +// + + +#ifndef _RWY_SIMPLE_HXX +#define _RWY_SIMPLE_HXX + + +#include +#include + +#include "runway.hxx" +#include "texparams.hxx" + + +// generate a simple runway. The routine modifies rwy_polys, +// texparams, and accum +void gen_simple_rwy( const TGRunway& rwy_info, + double alt_m, + const std::string& material, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ); + + +#endif // _RWY_SIMPLE_HXX diff --git a/src/Airports/GenAirports850/rwy_visual.cxx b/src/Airports/GenAirports850/rwy_visual.cxx new file mode 100644 index 00000000..fe524586 --- /dev/null +++ b/src/Airports/GenAirports850/rwy_visual.cxx @@ -0,0 +1,426 @@ +// rwy_visual.cxx -- Build a visual approach runway +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: rwy_visual.cxx,v 1.18 2004-11-19 22:25:49 curt Exp $ +// + + +#include +#include +#include + +#include "rwy_common.hxx" +#include "rwy_visual.hxx" + +#include + +using std::string; + + +// generate a visual approach runway. The routine modifies rwy_polys, +// texparams, and accum. For specific details and dimensions of +// precision runway markings, please refer to FAA document AC +// 150/5340-1H + +void gen_visual_rwy( const TGRunway& rwy_info, + double alt_m, + const string& material, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ) +{ + int i, j; + + // + // Generate the basic runway outlines + // + + TGPolygon runway = gen_runway_w_mid( rwy_info, alt_m, + 2 * SG_FEET_TO_METER, + 2 * SG_FEET_TO_METER ); + + // runway half "a" + TGPolygon runway_a; + runway_a.erase(); + runway_a.add_node( 0, runway.get_pt(0, 0) ); + runway_a.add_node( 0, runway.get_pt(0, 1) ); + runway_a.add_node( 0, runway.get_pt(0, 2) ); + runway_a.add_node( 0, runway.get_pt(0, 5) ); + + // runway half "b" + TGPolygon runway_b; + runway_b.erase(); + runway_b.add_node( 0, runway.get_pt(0, 3) ); + runway_b.add_node( 0, runway.get_pt(0, 4) ); + runway_b.add_node( 0, runway.get_pt(0, 5) ); + runway_b.add_node( 0, runway.get_pt(0, 2) ); + + Point3D p; + SG_LOG(SG_GENERAL, SG_DEBUG, "raw runway pts (a half)"); + for ( j = 0; j < runway_a.contour_size( 0 ); ++j ) { + p = runway_a.get_pt(0, j); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + SG_LOG(SG_GENERAL, SG_DEBUG, "raw runway pts (b half)"); + for ( j = 0; j < runway_b.contour_size( 0 ); ++j ) { + p = runway_b.get_pt(0, j); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + + // + // Setup some variables and values to help us chop up the runway + // into its various sections + // + + TGSuperPoly sp; + TGTexParams tp; + + // we add 2' to the length for texture overlap. This puts the + // lines on the texture back to the edge of the runway where they + // belong. + double length = rwy_info.length / 2.0 + 2.0; + if ( length < 1150 ) { + SG_LOG(SG_GENERAL, SG_ALERT, + "This runway is not long enough for visual markings = " + << rwy_info.length ); + } + + double start1_pct = 0.0; + double start2_pct = 0.0; + double end1_pct = 0.0; + double end2_pct = 0.0; + + // + // Displaced threshold if it exists + // + + if ( rwy_info.disp_thresh1 > 0.0 ) { + SG_LOG( SG_GENERAL, SG_INFO, "Forward displaced threshold = " + << rwy_info.disp_thresh1 ); + + // reserve 90' for final arrows + double thresh = rwy_info.disp_thresh1 - 90.0; + + // number of full center arrows + int count = (int)(thresh / 200.0); + + // length of starting partial arrow + double part_len = thresh - ( count * 200.0 ); + double tex_pct = (200.0 - part_len) / 200.0; + + // starting (possibly partial chunk) + start2_pct = end2_pct; + end2_pct = start2_pct + ( part_len / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, tex_pct, 1.0, + rwy_info.heading + 180.0, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + + // main chunks + for ( i = 0; i < count; ++i ) { + start2_pct = end2_pct; + end2_pct = start2_pct + ( 200.0 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + } + + // final arrows + start2_pct = end2_pct; + end2_pct = start2_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "dspl_arrows", + rwy_polys, texparams, accum ); + } + + if ( rwy_info.disp_thresh2 > 0.0 ) { + SG_LOG( SG_GENERAL, SG_INFO, "Reverse displaced threshold = " + << rwy_info.disp_thresh2 ); + + // reserve 90' for final arrows + double thresh = rwy_info.disp_thresh2 - 90.0; + + // number of full center arrows + int count = (int)(thresh / 200.0); + + // length of starting partial arrow + double part_len = thresh - ( count * 200.0 ); + double tex_pct = (200.0 - part_len) / 200.0; + + // starting (possibly partial chunk) + start1_pct = end1_pct; + end1_pct = start1_pct + ( part_len / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, tex_pct, 1.0, + rwy_info.heading, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + + // main chunks + for ( i = 0; i < count; ++i ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 200.0 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "dspl_thresh", + rwy_polys, texparams, accum ); + } + + // final arrows + start1_pct = end1_pct; + end1_pct = start1_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "dspl_arrows", + rwy_polys, texparams, accum ); + } + + // + // Threshold + // + + // mini threshold + + // starting (possibly partial chunk) + start1_pct = end1_pct; + end1_pct = start1_pct + ( 14 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 0.07, + rwy_info.heading, + material, "threshold", + rwy_polys, texparams, accum ); + + // starting (possibly partial chunk) + start2_pct = end2_pct; + end2_pct = start2_pct + ( 14 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 0.07, + rwy_info.heading + 180.0, + material, "threshold", + rwy_polys, texparams, accum ); + + // + // Runway designation letter + // + + int len = rwy_info.rwy_no.length(); + string letter = ""; + string rev_letter = ""; + for ( i = 0; i < len; ++i ) { + string tmp = rwy_info.rwy_no.substr(i, 1); + if ( tmp == "L" ) { + letter = "L"; + rev_letter = "R"; + } else if ( tmp == "R" ) { + letter = "R"; + rev_letter = "L"; + } else if ( tmp == "C" ) { + letter = "C"; + rev_letter = "C"; + } + } + + SG_LOG(SG_GENERAL, SG_DEBUG, "Runway designation = " << rwy_info.rwy_no); + SG_LOG(SG_GENERAL, SG_DEBUG, "Runway designation letter = " << letter); + + if ( !letter.empty() ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, rev_letter, + rwy_polys, texparams, accum ); + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 90.0 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, letter, + rwy_polys, texparams, accum ); + } + + // + // Runway designation number(s) + // + + len = rwy_info.rwy_no.length(); + string snum = rwy_info.rwy_no; + for ( i = 0; i < len; ++i ) { + string tmp = rwy_info.rwy_no.substr(i, 1); + if ( tmp == "L" || tmp == "R" || tmp == "C" || tmp == " " ) { + snum = rwy_info.rwy_no.substr(0, i); + } + } + SG_LOG(SG_GENERAL, SG_DEBUG, "Runway num = '" << snum << "'"); + int num = atoi( snum.c_str() ); + while ( num <= 0 ) { + num += 36; + } + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 80.0 / length ); + gen_number_block( rwy_info, material, runway_b, rwy_info.heading + 180.0, + num, start2_pct, end2_pct, rwy_polys, texparams, accum ); + + num += 18; + while ( num > 36 ) { + num -= 36; + } + + start1_pct = end1_pct; + end1_pct = start1_pct + ( 80.0 / length ); + gen_number_block( rwy_info, material, runway_a, rwy_info.heading, + num, start1_pct, end1_pct, rwy_polys, texparams, accum ); + + if ( false ) { + // + // Intermediate area before aiming point ... + // + + for ( i = 0; i < 3; ++i ) { + start1_pct = end1_pct; + end1_pct = start1_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "centerline", + rwy_polys, texparams, accum ); + + start2_pct = end2_pct; + end2_pct = start2_pct + ( 200 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "centerline", + rwy_polys, texparams, accum ); + } + + // + // Aiming point + // + + if ( end1_pct >= 1.0 ) { + return; + } + start1_pct = end1_pct; + end1_pct = start1_pct + ( 400 / length ); + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "aim", + rwy_polys, texparams, accum ); + + if ( end2_pct >= 1.0 ) { + return; + } + start2_pct = end2_pct; + end2_pct = start2_pct + ( 400 / length ); + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "aim", + rwy_polys, texparams, accum ); + } + + // + // The rest ... + // + + // fit the 'rest' texture in as many times as will go evenly into + // the remaining distance so we don't end up with a super short + // section at the end. + double ideal_rest_inc = ( 200.0 / length ); + int divs = (int)((1.0 - end1_pct) / ideal_rest_inc) + 1; + double rest1_inc = (1.0 - end1_pct) / divs; + + while ( end1_pct < 1.0 ) { + start1_pct = end1_pct; + end1_pct = start1_pct + rest1_inc; + SG_LOG(SG_GENERAL, SG_DEBUG, "start1 = " << start1_pct << " end1 = " << end1_pct); + + gen_runway_section( rwy_info, runway_a, + start1_pct, end1_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading, + material, "rest", + rwy_polys, texparams, accum ); + } + + ideal_rest_inc = ( 200.0 / length ); + divs = (int)((1.0 - end2_pct) / ideal_rest_inc) + 1; + double rest2_inc = (1.0 - end2_pct) / divs; + + while ( end2_pct < 1.0 ) { + start2_pct = end2_pct; + end2_pct = start2_pct + rest2_inc; + + gen_runway_section( rwy_info, runway_b, + start2_pct, end2_pct, + 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + rwy_info.heading + 180.0, + material, "rest", + rwy_polys, texparams, accum ); + } + + gen_runway_stopway( rwy_info, runway_a, runway_b, + material, + rwy_polys, texparams, accum ); +} + + diff --git a/src/Airports/GenAirports850/rwy_visual.hxx b/src/Airports/GenAirports850/rwy_visual.hxx new file mode 100644 index 00000000..897aae69 --- /dev/null +++ b/src/Airports/GenAirports850/rwy_visual.hxx @@ -0,0 +1,49 @@ +// rwy_visual.hxx -- Build a visual approach runway +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: rwy_visual.hxx,v 1.5 2004-11-19 22:25:49 curt Exp $ +// + + +#ifndef _RWY_VISUAL_HXX +#define _RWY_VISUAL_HXX + + +#include +#include + +#include "runway.hxx" +#include "texparams.hxx" + + +// generate a visual approach runway. The routine modifies rwy_polys, +// texparams, and accum. For specific details and dimensions of +// precision runway markings, please refer to FAA document AC +// 150/5340-1H + +void gen_visual_rwy( const TGRunway& rwy_info, + double alt_m, + const std::string& material, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ); + + +#endif // _RWY_VISUAL_HXX diff --git a/src/Airports/GenAirports850/taxiway.cxx b/src/Airports/GenAirports850/taxiway.cxx new file mode 100644 index 00000000..d62f1c95 --- /dev/null +++ b/src/Airports/GenAirports850/taxiway.cxx @@ -0,0 +1,151 @@ +// taxiway.cxx -- Build a taxiway +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: taxiway.cxx,v 1.13 2004-11-19 22:25:49 curt Exp $ +// + + +#include +#include +#include + +#include "poly_extra.hxx" +#include "rwy_common.hxx" +#include "texparams.hxx" +#include "taxiway.hxx" + +using std::string; + + +// generate a taxiway. The routine modifies rwy_polys, texparams, and +// accum +void gen_taxiway( const TGRunway& rwy_info, + double alt_m, + const string& material, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ) +{ + int j, k; + + TGPolygon runway = gen_runway_w_mid( rwy_info, alt_m, 0.0, 0.0 ); + + // runway half "a" + TGPolygon runway_a; + runway_a.erase(); + runway_a.add_node( 0, runway.get_pt(0, 0) ); + runway_a.add_node( 0, runway.get_pt(0, 1) ); + runway_a.add_node( 0, runway.get_pt(0, 2) ); + runway_a.add_node( 0, runway.get_pt(0, 5) ); + + // runway half "b" + TGPolygon runway_b; + runway_b.erase(); + runway_b.add_node( 0, runway.get_pt(0, 5) ); + runway_b.add_node( 0, runway.get_pt(0, 2) ); + runway_b.add_node( 0, runway.get_pt(0, 3) ); + runway_b.add_node( 0, runway.get_pt(0, 4) ); + + Point3D p; + SG_LOG(SG_GENERAL, SG_DEBUG, "raw runway pts (a half)"); + for ( j = 0; j < runway_a.contour_size( 0 ); ++j ) { + p = runway_a.get_pt(0, j); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + SG_LOG(SG_GENERAL, SG_DEBUG, "raw runway pts (b half)"); + for ( j = 0; j < runway_b.contour_size( 0 ); ++j ) { + p = runway_b.get_pt(0, j); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + + TGSuperPoly sp; + TGTexParams tp; + + SG_LOG(SG_GENERAL, SG_DEBUG, "len = " << rwy_info.length); + SG_LOG(SG_GENERAL, SG_DEBUG, "width = " << rwy_info.width); + + double twid; + if ( rwy_info.width <= 150 ) { + // narrower taxiways are more likely directional + twid = rwy_info.width; + } else { + // wider taxiways are more likely large / non-directional + // concrete areas + twid = 250.0; + } + + TGPolygon clipped_a = tgPolygonDiff( runway_a, *accum ); + TGPolygon split_a = tgPolygonSplitLongEdges( clipped_a, 400.0 ); + sp.erase(); + + sp.set_poly( split_a ); + sp.set_material( material ); + sp.set_flag( "taxi" ); // mark as a taxiway + rwy_polys->push_back( sp ); + SG_LOG(SG_GENERAL, SG_DEBUG, "clipped_a = " << clipped_a.contours()); + *accum = tgPolygonUnion( runway_a, *accum ); + tp = TGTexParams( runway_a.get_pt(0,0), + twid * SG_FEET_TO_METER, + 250 * SG_FEET_TO_METER, + rwy_info.heading ); + texparams->push_back( tp ); + + TGPolygon clipped_b = tgPolygonDiff( runway_b, *accum ); + TGPolygon split_b = tgPolygonSplitLongEdges( clipped_b, 400.0 ); + sp.erase(); + sp.set_poly( split_b ); + sp.set_material( material ); + rwy_polys->push_back( sp ); + SG_LOG(SG_GENERAL, SG_DEBUG, "clipped_b = " << clipped_b.contours()); + *accum = tgPolygonUnion( runway_b, *accum ); + tp = TGTexParams( runway_b.get_pt(0,0), + twid * SG_FEET_TO_METER, + 250 * SG_FEET_TO_METER, + rwy_info.heading + 180.0 ); + texparams->push_back( tp ); + +#if 0 + // after clip, but before removing T intersections + char tmpa[256], tmpb[256]; + sprintf( tmpa, "a%d", i ); + sprintf( tmpb, "b%d", i ); + write_polygon( clipped_a, tmpa ); + write_polygon( clipped_b, tmpb ); +#endif + + // print runway points + SG_LOG(SG_GENERAL, SG_DEBUG, "clipped runway pts (a)"); + for ( j = 0; j < clipped_a.contours(); ++j ) { + for ( k = 0; k < clipped_a.contour_size( j ); ++k ) { + p = clipped_a.get_pt(j, k); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + } + + // print runway points + SG_LOG(SG_GENERAL, SG_DEBUG, "clipped runway pts (b)"); + for ( j = 0; j < clipped_b.contours(); ++j ) { + for ( k = 0; k < clipped_b.contour_size( j ); ++k ) { + p = clipped_b.get_pt(j, k); + SG_LOG(SG_GENERAL, SG_DEBUG, " point = " << p); + } + } + +} diff --git a/src/Airports/GenAirports850/taxiway.hxx b/src/Airports/GenAirports850/taxiway.hxx new file mode 100644 index 00000000..b6ed7dcf --- /dev/null +++ b/src/Airports/GenAirports850/taxiway.hxx @@ -0,0 +1,46 @@ +// taxiway.hxx -- Build a taxiway +// +// Written by Curtis Olson, started February 2002. +// +// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: taxiway.hxx,v 1.5 2004-11-19 22:25:49 curt Exp $ +// + + +#ifndef _TAXIWAY_HXX +#define _TAXIWAY_HXX + + +#include +#include + +#include "runway.hxx" +#include "texparams.hxx" + + +// generate a taxiway. The routine modifies rwy_polys, texparams, and +// accum +void gen_taxiway( const TGRunway& rwy_info, + double alt_m, + const std::string& material, + superpoly_list *rwy_polys, + texparams_list *texparams, + TGPolygon *accum ); + + +#endif // _TAXIWAY_HXX diff --git a/src/Airports/GenAirports850/texparams.hxx b/src/Airports/GenAirports850/texparams.hxx new file mode 100644 index 00000000..dec7dadc --- /dev/null +++ b/src/Airports/GenAirports850/texparams.hxx @@ -0,0 +1,100 @@ +// texparams.hxx -- A simple class to hold texture application +// parameters for sections of the runway +// +// Written by Curtis Olson, started August 2000. +// +// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id: texparams.hxx,v 1.7 2004-11-19 22:25:49 curt Exp $ + + +#ifndef _TEXPARAMS_HXX +#define _TEXPARAMS_HXX + + +#ifndef __cplusplus +# error This library requires C++ +#endif + + +#include +#include + +#include + +class TGTexParams { + +private: + + Point3D ref; + double width; + double length; + double heading; + + double minu; + double maxu; + double minv; + double maxv; + +public: + + // Constructor and destructor + inline TGTexParams( void ) { } + inline TGTexParams( const Point3D &r, const double w, const double l, + const double h ) { + ref = r; + width = w; + length = l; + heading = h; + + minu = minv = 0.0; + maxu = maxv = 1.0; + } + inline ~TGTexParams( void ) { } + + inline Point3D get_ref() const { return ref; } + inline void set_ref( const Point3D &r ) { ref = r; } + + inline double get_width() const { return width; } + inline void set_width( const double w ) { width = w; } + + inline double get_length() const { return length; } + inline void set_length( const double l ) { length = l; } + + inline double get_heading() const { return heading; } + inline void set_heading( const double h ) { heading = h; } + + inline double get_minu() const { return minu; } + inline void set_minu( const double x ) { minu = x; } + + inline double get_maxu() const { return maxu; } + inline void set_maxu( const double x ) { maxu = x; } + + inline double get_minv() const { return minv; } + inline void set_minv( const double x ) { minv = x; } + + inline double get_maxv() const { return maxv; } + inline void set_maxv( const double x ) { maxv = x; } +}; + + +typedef std::vector < TGTexParams > texparams_list; +typedef texparams_list::iterator texparams_list_iterator; +typedef texparams_list::const_iterator const_texparams_list_iterator; + + +#endif // _TEXPARAMS_HXX