Quantum GIS API Documentation  master-ce49b66
src/core/qgsdistancearea.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002   qgsdistancearea.cpp - Distance and area calculations on the ellipsoid
00003  ---------------------------------------------------------------------------
00004   Date                 : September 2005
00005   Copyright            : (C) 2005 by Martin Dobias
00006   email                : won.der at centrum.sk
00007  ***************************************************************************
00008  *                                                                         *
00009  *   This program is free software; you can redistribute it and/or modify  *
00010  *   it under the terms of the GNU General Public License as published by  *
00011  *   the Free Software Foundation; either version 2 of the License, or     *
00012  *   (at your option) any later version.                                   *
00013  *                                                                         *
00014  ***************************************************************************/
00015 
00016 #include <cmath>
00017 #include <sqlite3.h>
00018 #include <QDir>
00019 #include <QString>
00020 #include <QLocale>
00021 #include <QObject>
00022 
00023 #include "qgis.h"
00024 #include "qgspoint.h"
00025 #include "qgscoordinatetransform.h"
00026 #include "qgscoordinatereferencesystem.h"
00027 #include "qgsgeometry.h"
00028 #include "qgsdistancearea.h"
00029 #include "qgsapplication.h"
00030 #include "qgslogger.h"
00031 #include "qgsmessagelog.h"
00032 
00033 // MSVC compiler doesn't have defined M_PI in math.h
00034 #ifndef M_PI
00035 #define M_PI          3.14159265358979323846
00036 #endif
00037 
00038 #define DEG2RAD(x)    ((x)*M_PI/180)
00039 
00040 
00041 QgsDistanceArea::QgsDistanceArea()
00042 {
00043   // init with default settings
00044   mEllipsoidalMode = false;
00045   mCoordTransform = new QgsCoordinateTransform;
00046   setSourceCrs( GEOCRS_ID ); // WGS 84
00047   setEllipsoid( GEO_NONE );
00048 }
00049 
00050 
00052 QgsDistanceArea::QgsDistanceArea( const QgsDistanceArea & origDA )
00053 {
00054   _copy( origDA );
00055 }
00056 
00057 QgsDistanceArea::~QgsDistanceArea()
00058 {
00059   delete mCoordTransform;
00060 }
00061 
00063 QgsDistanceArea & QgsDistanceArea::operator=( const QgsDistanceArea & origDA )
00064 {
00065   if ( this == & origDA )
00066   {
00067     // Do not copy unto self
00068     return *this;
00069   }
00070   _copy( origDA );
00071   return *this;
00072 }
00073 
00075 void QgsDistanceArea::_copy( const QgsDistanceArea & origDA )
00076 {
00077   mEllipsoidalMode = origDA.mEllipsoidalMode;
00078   mEllipsoid = origDA.mEllipsoid;
00079   mSemiMajor = origDA.mSemiMajor;
00080   mSemiMinor = origDA.mSemiMinor;
00081   mInvFlattening = origDA.mInvFlattening;
00082   // Some calculations and trig. Should not be TOO time consuming.
00083   // Alternatively we could copy the temp vars?
00084   computeAreaInit();
00085   mSourceRefSys = origDA.mSourceRefSys;
00086   mCoordTransform = new QgsCoordinateTransform( origDA.mCoordTransform->sourceCrs(), origDA.mCoordTransform->destCRS() );
00087 }
00088 
00089 void QgsDistanceArea::setEllipsoidalMode( bool flag )
00090 {
00091   mEllipsoidalMode = flag;
00092 }
00093 
00094 void QgsDistanceArea::setSourceCrs( long srsid )
00095 {
00096   QgsCoordinateReferenceSystem srcCRS;
00097   srcCRS.createFromSrsId( srsid );
00098   mCoordTransform->setSourceCrs( srcCRS );
00099 }
00100 
00101 void QgsDistanceArea::setSourceAuthId( QString authId )
00102 {
00103   QgsCoordinateReferenceSystem srcCRS;
00104   srcCRS.createFromOgcWmsCrs( authId );
00105   mCoordTransform->setSourceCrs( srcCRS );
00106 }
00107 
00108 bool QgsDistanceArea::setEllipsoid( const QString& ellipsoid )
00109 {
00110   QString radius, parameter2;
00111   //
00112   // SQLITE3 stuff - get parameters for selected ellipsoid
00113   //
00114   sqlite3      *myDatabase;
00115   const char   *myTail;
00116   sqlite3_stmt *myPreparedStatement;
00117   int           myResult;
00118 
00119   // Shortcut if ellipsoid is none.
00120   if ( ellipsoid == GEO_NONE )
00121   {
00122     mEllipsoid = GEO_NONE;
00123     return true;
00124   }
00125 
00126   // Check if we have a custom projection, and set from text string.
00127   // Format is "PARAMETER:<semi-major axis>:<semi minor axis>
00128   // Numbers must be with (optional) decimal point and no other separators (C locale)
00129   // Distances in meters.  Flattening is calculated.
00130   if ( ellipsoid.startsWith( "PARAMETER" ) )
00131   {
00132     QStringList paramList = ellipsoid.split( ":" );
00133     bool semiMajorOk, semiMinorOk;
00134     double semiMajor = paramList[1].toDouble( & semiMajorOk );
00135     double semiMinor = paramList[2].toDouble( & semiMinorOk );
00136     if ( semiMajorOk && semiMinorOk )
00137     {
00138       return setEllipsoid( semiMajor, semiMinor );
00139     }
00140     else
00141     {
00142       return false;
00143     }
00144   }
00145 
00146   // Continue with PROJ.4 list of ellipsoids.
00147 
00148   //check the db is available
00149   myResult = sqlite3_open_v2( QgsApplication::srsDbFilePath().toUtf8().data(), &myDatabase, SQLITE_OPEN_READONLY, NULL );
00150   if ( myResult )
00151   {
00152     QgsMessageLog::logMessage( QObject::tr( "Can't open database: %1" ).arg( sqlite3_errmsg( myDatabase ) ) );
00153     // XXX This will likely never happen since on open, sqlite creates the
00154     //     database if it does not exist.
00155     return false;
00156   }
00157   // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
00158   QString mySql = "select radius, parameter2 from tbl_ellipsoid where acronym='" + ellipsoid + "'";
00159   myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
00160   // XXX Need to free memory from the error msg if one is set
00161   if ( myResult == SQLITE_OK )
00162   {
00163     if ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
00164     {
00165       radius = QString(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
00166       parameter2 = QString(( char * )sqlite3_column_text( myPreparedStatement, 1 ) );
00167     }
00168   }
00169   // close the sqlite3 statement
00170   sqlite3_finalize( myPreparedStatement );
00171   sqlite3_close( myDatabase );
00172 
00173   // row for this ellipsoid wasn't found?
00174   if ( radius.isEmpty() || parameter2.isEmpty() )
00175   {
00176     QgsDebugMsg( QString( "setEllipsoid: no row in tbl_ellipsoid for acronym '%1'" ).arg( ellipsoid ) );
00177     return false;
00178   }
00179 
00180   // get major semiaxis
00181   if ( radius.left( 2 ) == "a=" )
00182     mSemiMajor = radius.mid( 2 ).toDouble();
00183   else
00184   {
00185     QgsDebugMsg( QString( "setEllipsoid: wrong format of radius field: '%1'" ).arg( radius ) );
00186     return false;
00187   }
00188 
00189   // get second parameter
00190   // one of values 'b' or 'f' is in field parameter2
00191   // second one must be computed using formula: invf = a/(a-b)
00192   if ( parameter2.left( 2 ) == "b=" )
00193   {
00194     mSemiMinor = parameter2.mid( 2 ).toDouble();
00195     mInvFlattening = mSemiMajor / ( mSemiMajor - mSemiMinor );
00196   }
00197   else if ( parameter2.left( 3 ) == "rf=" )
00198   {
00199     mInvFlattening = parameter2.mid( 3 ).toDouble();
00200     mSemiMinor = mSemiMajor - ( mSemiMajor / mInvFlattening );
00201   }
00202   else
00203   {
00204     QgsDebugMsg( QString( "setEllipsoid: wrong format of parameter2 field: '%1'" ).arg( parameter2 ) );
00205     return false;
00206   }
00207 
00208   QgsDebugMsg( QString( "setEllipsoid: a=%1, b=%2, 1/f=%3" ).arg( mSemiMajor ).arg( mSemiMinor ).arg( mInvFlattening ) );
00209 
00210 
00211   // get spatial ref system for ellipsoid
00212   QString proj4 = "+proj=longlat +ellps=" + ellipsoid + " +no_defs";
00213   QgsCoordinateReferenceSystem destCRS;
00214   destCRS.createFromProj4( proj4 );
00215   //TODO: createFromProj4 used to save to the user database any new CRS
00216   // this behavior was changed in order to separate creation and saving.
00217   // Not sure if it necessary to save it here, should be checked by someone
00218   // familiar with the code (should also give a more descriptive name to the generated CRS)
00219   if ( destCRS.srsid() == 0 )
00220   {
00221     QString myName = QString( " * %1 (%2)" )
00222                      .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ) )
00223                      .arg( destCRS.toProj4() );
00224     destCRS.saveAsUserCRS( myName );
00225   }
00226   //
00227 
00228   // set transformation from project CRS to ellipsoid coordinates
00229   mCoordTransform->setDestCRS( destCRS );
00230 
00231   // precalculate some values for area calculations
00232   computeAreaInit();
00233 
00234   mEllipsoid = ellipsoid;
00235   return true;
00236 }
00237 
00239 // Inverse flattening is calculated with invf = a/(a-b)
00240 // Also, b = a-(a/invf)
00241 bool  QgsDistanceArea::setEllipsoid( double semiMajor, double semiMinor )
00242 {
00243   mEllipsoid = QString( "PARAMETER:%1:%2" ).arg( semiMajor ).arg( semiMinor );
00244   mSemiMajor = semiMajor;
00245   mSemiMinor = semiMinor;
00246   mInvFlattening = mSemiMajor / ( mSemiMajor - mSemiMinor );
00247 
00248   computeAreaInit();
00249 
00250   return true;
00251 }
00252 
00253 
00254 
00255 double QgsDistanceArea::measure( QgsGeometry* geometry )
00256 {
00257   if ( !geometry )
00258     return 0.0;
00259 
00260   unsigned char* wkb = geometry->asWkb();
00261   if ( !wkb )
00262     return 0.0;
00263 
00264   unsigned char* ptr;
00265   unsigned int wkbType;
00266   double res, resTotal = 0;
00267   int count, i;
00268 
00269   memcpy( &wkbType, ( wkb + 1 ), sizeof( wkbType ) );
00270 
00271   // measure distance or area based on what is the type of geometry
00272   bool hasZptr = false;
00273 
00274   switch ( wkbType )
00275   {
00276     case QGis::WKBLineString25D:
00277       hasZptr = true;
00278     case QGis::WKBLineString:
00279       measureLine( wkb, &res, hasZptr );
00280       QgsDebugMsg( "returning " + QString::number( res ) );
00281       return res;
00282 
00283     case QGis::WKBMultiLineString25D:
00284       hasZptr = true;
00285     case QGis::WKBMultiLineString:
00286       count = *(( int* )( wkb + 5 ) );
00287       ptr = wkb + 9;
00288       for ( i = 0; i < count; i++ )
00289       {
00290         ptr = measureLine( ptr, &res, hasZptr );
00291         resTotal += res;
00292       }
00293       QgsDebugMsg( "returning " + QString::number( resTotal ) );
00294       return resTotal;
00295 
00296     case QGis::WKBPolygon25D:
00297       hasZptr = true;
00298     case QGis::WKBPolygon:
00299       measurePolygon( wkb, &res, 0, hasZptr );
00300       QgsDebugMsg( "returning " + QString::number( res ) );
00301       return res;
00302 
00303     case QGis::WKBMultiPolygon25D:
00304       hasZptr = true;
00305     case QGis::WKBMultiPolygon:
00306       count = *(( int* )( wkb + 5 ) );
00307       ptr = wkb + 9;
00308       for ( i = 0; i < count; i++ )
00309       {
00310         ptr = measurePolygon( ptr, &res, 0, hasZptr );
00311         resTotal += res;
00312       }
00313       QgsDebugMsg( "returning " + QString::number( resTotal ) );
00314       return resTotal;
00315 
00316     default:
00317       QgsDebugMsg( QString( "measure: unexpected geometry type: %1" ).arg( wkbType ) );
00318       return 0;
00319   }
00320 }
00321 
00322 double QgsDistanceArea::measurePerimeter( QgsGeometry* geometry )
00323 {
00324   if ( !geometry )
00325     return 0.0;
00326 
00327   unsigned char* wkb = geometry->asWkb();
00328   if ( !wkb )
00329     return 0.0;
00330 
00331   unsigned char* ptr;
00332   unsigned int wkbType;
00333   double res, resTotal = 0;
00334   int count, i;
00335 
00336   memcpy( &wkbType, ( wkb + 1 ), sizeof( wkbType ) );
00337 
00338   // measure distance or area based on what is the type of geometry
00339   bool hasZptr = false;
00340 
00341   switch ( wkbType )
00342   {
00343     case QGis::WKBLineString25D:
00344     case QGis::WKBLineString:
00345     case QGis::WKBMultiLineString25D:
00346     case QGis::WKBMultiLineString:
00347       return 0.0;
00348 
00349     case QGis::WKBPolygon25D:
00350       hasZptr = true;
00351     case QGis::WKBPolygon:
00352       measurePolygon( wkb, 0, &res, hasZptr );
00353       QgsDebugMsg( "returning " + QString::number( res ) );
00354       return res;
00355 
00356     case QGis::WKBMultiPolygon25D:
00357       hasZptr = true;
00358     case QGis::WKBMultiPolygon:
00359       count = *(( int* )( wkb + 5 ) );
00360       ptr = wkb + 9;
00361       for ( i = 0; i < count; i++ )
00362       {
00363         ptr = measurePolygon( ptr, 0, &res, hasZptr );
00364         resTotal += res;
00365       }
00366       QgsDebugMsg( "returning " + QString::number( resTotal ) );
00367       return resTotal;
00368 
00369     default:
00370       QgsDebugMsg( QString( "measure: unexpected geometry type: %1" ).arg( wkbType ) );
00371       return 0;
00372   }
00373 }
00374 
00375 
00376 unsigned char* QgsDistanceArea::measureLine( unsigned char* feature, double* area, bool hasZptr )
00377 {
00378   unsigned char *ptr = feature + 5;
00379   unsigned int nPoints = *(( int* )ptr );
00380   ptr = feature + 9;
00381 
00382   QList<QgsPoint> points;
00383   double x, y;
00384 
00385   QgsDebugMsg( "This feature WKB has " + QString::number( nPoints ) + " points" );
00386   // Extract the points from the WKB format into the vector
00387   for ( unsigned int i = 0; i < nPoints; ++i )
00388   {
00389     x = *(( double * ) ptr );
00390     ptr += sizeof( double );
00391     y = *(( double * ) ptr );
00392     ptr += sizeof( double );
00393     if ( hasZptr )
00394     {
00395       // totally ignore Z value
00396       ptr += sizeof( double );
00397     }
00398 
00399     points.append( QgsPoint( x, y ) );
00400   }
00401 
00402   *area = measureLine( points );
00403   return ptr;
00404 }
00405 
00406 double QgsDistanceArea::measureLine( const QList<QgsPoint>& points )
00407 {
00408   if ( points.size() < 2 )
00409     return 0;
00410 
00411   double total = 0;
00412   QgsPoint p1, p2;
00413 
00414   try
00415   {
00416     if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
00417       p1 = mCoordTransform->transform( points[0] );
00418     else
00419       p1 = points[0];
00420 
00421     for ( QList<QgsPoint>::const_iterator i = points.begin(); i != points.end(); ++i )
00422     {
00423       if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
00424       {
00425         p2 = mCoordTransform->transform( *i );
00426         total += computeDistanceBearing( p1, p2 );
00427       }
00428       else
00429       {
00430         p2 = *i;
00431         total += measureLine( p1, p2 );
00432       }
00433 
00434       p1 = p2;
00435     }
00436 
00437     return total;
00438   }
00439   catch ( QgsCsException &cse )
00440   {
00441     Q_UNUSED( cse );
00442     QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point. Unable to calculate line length." ) );
00443     return 0.0;
00444   }
00445 
00446 }
00447 
00448 double QgsDistanceArea::measureLine( const QgsPoint& p1, const QgsPoint& p2 )
00449 {
00450   double result;
00451 
00452   try
00453   {
00454     QgsPoint pp1 = p1, pp2 = p2;
00455 
00456     QgsDebugMsg( QString( "Measuring from %1 to %2" ).arg( p1.toString( 4 ) ).arg( p2.toString( 4 ) ) );
00457     if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
00458     {
00459       QgsDebugMsg( QString( "Ellipsoidal calculations is enabled, using ellipsoid %1" ).arg( mEllipsoid ) );
00460       QgsDebugMsg( QString( "From proj4 : %1" ).arg( mCoordTransform->sourceCrs().toProj4() ) );
00461       QgsDebugMsg( QString( "To   proj4 : %1" ).arg( mCoordTransform->destCRS().toProj4() ) );
00462       pp1 = mCoordTransform->transform( p1 );
00463       pp2 = mCoordTransform->transform( p2 );
00464       QgsDebugMsg( QString( "New points are %1 and %2, calculating..." ).arg( pp1.toString( 4 ) ).arg( pp2.toString( 4 ) ) );
00465       result = computeDistanceBearing( pp1, pp2 );
00466     }
00467     else
00468     {
00469       QgsDebugMsg( "Cartesian calculation on canvas coordinates" );
00470       result = sqrt(( p2.x() - p1.x() ) * ( p2.x() - p1.x() ) + ( p2.y() - p1.y() ) * ( p2.y() - p1.y() ) );
00471     }
00472   }
00473   catch ( QgsCsException &cse )
00474   {
00475     Q_UNUSED( cse );
00476     QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point. Unable to calculate line length." ) );
00477     result = 0.0;
00478   }
00479   QgsDebugMsg( QString( "The result was %1" ).arg( result ) );
00480   return result;
00481 }
00482 
00483 
00484 unsigned char* QgsDistanceArea::measurePolygon( unsigned char* feature, double* area, double* perimeter, bool hasZptr )
00485 {
00486   // get number of rings in the polygon
00487   unsigned int numRings = *(( int* )( feature + 1 + sizeof( int ) ) );
00488 
00489   if ( numRings == 0 )
00490     return 0;
00491 
00492   // Set pointer to the first ring
00493   unsigned char* ptr = feature + 1 + 2 * sizeof( int );
00494 
00495   QList<QgsPoint> points;
00496   QgsPoint pnt;
00497   double x, y;
00498   if ( area )
00499     *area = 0;
00500   if ( perimeter )
00501     *perimeter = 0;
00502 
00503   try
00504   {
00505     for ( unsigned int idx = 0; idx < numRings; idx++ )
00506     {
00507       int nPoints = *(( int* )ptr );
00508       ptr += 4;
00509 
00510       // Extract the points from the WKB and store in a pair of
00511       // vectors.
00512       for ( int jdx = 0; jdx < nPoints; jdx++ )
00513       {
00514         x = *(( double * ) ptr );
00515         ptr += sizeof( double );
00516         y = *(( double * ) ptr );
00517         ptr += sizeof( double );
00518         if ( hasZptr )
00519         {
00520           // totally ignore Z value
00521           ptr += sizeof( double );
00522         }
00523 
00524         pnt = QgsPoint( x, y );
00525 
00526         if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
00527         {
00528           pnt = mCoordTransform->transform( pnt );
00529         }
00530         points.append( pnt );
00531       }
00532 
00533       if ( points.size() > 2 )
00534       {
00535         if ( area )
00536         {
00537           double areaTmp = computePolygonArea( points );
00538           if ( idx == 0 )
00539           {
00540             // exterior ring
00541             *area += areaTmp;
00542           }
00543           else
00544           {
00545             *area -= areaTmp; // interior rings
00546           }
00547         }
00548 
00549         if ( perimeter )
00550         {
00551           if ( idx == 0 )
00552           {
00553             // exterior ring
00554             *perimeter += measureLine( points );
00555           }
00556         }
00557       }
00558 
00559       points.clear();
00560     }
00561   }
00562   catch ( QgsCsException &cse )
00563   {
00564     Q_UNUSED( cse );
00565     QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point. Unable to calculate polygon area or perimeter." ) );
00566   }
00567 
00568   return ptr;
00569 }
00570 
00571 
00572 double QgsDistanceArea::measurePolygon( const QList<QgsPoint>& points )
00573 {
00574 
00575   try
00576   {
00577     if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
00578     {
00579       QList<QgsPoint> pts;
00580       for ( QList<QgsPoint>::const_iterator i = points.begin(); i != points.end(); ++i )
00581       {
00582         pts.append( mCoordTransform->transform( *i ) );
00583       }
00584       return computePolygonArea( pts );
00585     }
00586     else
00587     {
00588       return computePolygonArea( points );
00589     }
00590   }
00591   catch ( QgsCsException &cse )
00592   {
00593     Q_UNUSED( cse );
00594     QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point. Unable to calculate polygon area." ) );
00595     return 0.0;
00596   }
00597 }
00598 
00599 
00600 double QgsDistanceArea::bearing( const QgsPoint& p1, const QgsPoint& p2 )
00601 {
00602   QgsPoint pp1 = p1, pp2 = p2;
00603   double bearing;
00604 
00605   if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
00606   {
00607     pp1 = mCoordTransform->transform( p1 );
00608     pp2 = mCoordTransform->transform( p2 );
00609     computeDistanceBearing( pp1, pp2, &bearing );
00610   }
00611   else //compute simple planar azimuth
00612   {
00613     double dx = p2.x() - p1.x();
00614     double dy = p2.y() - p1.y();
00615     bearing = atan2( dx, dy );
00616   }
00617 
00618   return bearing;
00619 }
00620 
00621 
00623 // distance calculation
00624 
00625 double QgsDistanceArea::computeDistanceBearing(
00626   const QgsPoint& p1, const QgsPoint& p2,
00627   double* course1, double* course2 )
00628 {
00629   if ( p1.x() == p2.x() && p1.y() == p2.y() )
00630     return 0;
00631 
00632   // ellipsoid
00633   double a = mSemiMajor;
00634   double b = mSemiMinor;
00635   double f = 1 / mInvFlattening;
00636 
00637   double p1_lat = DEG2RAD( p1.y() ), p1_lon = DEG2RAD( p1.x() );
00638   double p2_lat = DEG2RAD( p2.y() ), p2_lon = DEG2RAD( p2.x() );
00639 
00640   double L = p2_lon - p1_lon;
00641   double U1 = atan(( 1 - f ) * tan( p1_lat ) );
00642   double U2 = atan(( 1 - f ) * tan( p2_lat ) );
00643   double sinU1 = sin( U1 ), cosU1 = cos( U1 );
00644   double sinU2 = sin( U2 ), cosU2 = cos( U2 );
00645   double lambda = L;
00646   double lambdaP = 2 * M_PI;
00647 
00648   double sinLambda = 0;
00649   double cosLambda = 0;
00650   double sinSigma = 0;
00651   double cosSigma = 0;
00652   double sigma = 0;
00653   double alpha = 0;
00654   double cosSqAlpha = 0;
00655   double cos2SigmaM = 0;
00656   double C = 0;
00657   double tu1 = 0;
00658   double tu2 = 0;
00659 
00660   int iterLimit = 20;
00661   while ( qAbs( lambda - lambdaP ) > 1e-12 && --iterLimit > 0 )
00662   {
00663     sinLambda = sin( lambda );
00664     cosLambda = cos( lambda );
00665     tu1 = ( cosU2 * sinLambda );
00666     tu2 = ( cosU1 * sinU2 - sinU1 * cosU2 * cosLambda );
00667     sinSigma = sqrt( tu1 * tu1 + tu2 * tu2 );
00668     cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
00669     sigma = atan2( sinSigma, cosSigma );
00670     alpha = asin( cosU1 * cosU2 * sinLambda / sinSigma );
00671     cosSqAlpha = cos( alpha ) * cos( alpha );
00672     cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
00673     C = f / 16 * cosSqAlpha * ( 4 + f * ( 4 - 3 * cosSqAlpha ) );
00674     lambdaP = lambda;
00675     lambda = L + ( 1 - C ) * f * sin( alpha ) *
00676              ( sigma + C * sinSigma * ( cos2SigmaM + C * cosSigma * ( -1 + 2 * cos2SigmaM * cos2SigmaM ) ) );
00677   }
00678 
00679   if ( iterLimit == 0 )
00680     return -1;  // formula failed to converge
00681 
00682   double uSq = cosSqAlpha * ( a * a - b * b ) / ( b * b );
00683   double A = 1 + uSq / 16384 * ( 4096 + uSq * ( -768 + uSq * ( 320 - 175 * uSq ) ) );
00684   double B = uSq / 1024 * ( 256 + uSq * ( -128 + uSq * ( 74 - 47 * uSq ) ) );
00685   double deltaSigma = B * sinSigma * ( cos2SigmaM + B / 4 * ( cosSigma * ( -1 + 2 * cos2SigmaM * cos2SigmaM ) -
00686                                        B / 6 * cos2SigmaM * ( -3 + 4 * sinSigma * sinSigma ) * ( -3 + 4 * cos2SigmaM * cos2SigmaM ) ) );
00687   double s = b * A * ( sigma - deltaSigma );
00688 
00689   if ( course1 )
00690   {
00691     *course1 = atan2( tu1, tu2 );
00692   }
00693   if ( course2 )
00694   {
00695     // PI is added to return azimuth from P2 to P1
00696     *course2 = atan2( cosU1 * sinLambda, -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda ) + M_PI;
00697   }
00698 
00699   return s;
00700 }
00701 
00702 
00703 
00705 // stuff for measuring areas - copied from GRASS
00706 // don't know how does it work, but it's working .)
00707 // see G_begin_ellipsoid_polygon_area() in area_poly1.c
00708 
00709 double QgsDistanceArea::getQ( double x )
00710 {
00711   double sinx, sinx2;
00712 
00713   sinx = sin( x );
00714   sinx2 = sinx * sinx;
00715 
00716   return sinx *( 1 + sinx2 *( m_QA + sinx2 *( m_QB + sinx2 * m_QC ) ) );
00717 }
00718 
00719 
00720 double QgsDistanceArea::getQbar( double x )
00721 {
00722   double cosx, cosx2;
00723 
00724   cosx = cos( x );
00725   cosx2 = cosx * cosx;
00726 
00727   return cosx *( m_QbarA + cosx2 *( m_QbarB + cosx2 *( m_QbarC + cosx2 * m_QbarD ) ) );
00728 }
00729 
00730 
00731 void QgsDistanceArea::computeAreaInit()
00732 {
00733   double a2 = ( mSemiMajor * mSemiMajor );
00734   double e2 = 1 - ( a2 / ( mSemiMinor * mSemiMinor ) );
00735   double e4, e6;
00736 
00737   m_TwoPI = M_PI + M_PI;
00738 
00739   e4 = e2 * e2;
00740   e6 = e4 * e2;
00741 
00742   m_AE = a2 * ( 1 - e2 );
00743 
00744   m_QA = ( 2.0 / 3.0 ) * e2;
00745   m_QB = ( 3.0 / 5.0 ) * e4;
00746   m_QC = ( 4.0 / 7.0 ) * e6;
00747 
00748   m_QbarA = -1.0 - ( 2.0 / 3.0 ) * e2 - ( 3.0 / 5.0 ) * e4  - ( 4.0 / 7.0 ) * e6;
00749   m_QbarB = ( 2.0 / 9.0 ) * e2 + ( 2.0 / 5.0 ) * e4  + ( 4.0 / 7.0 ) * e6;
00750   m_QbarC =                     - ( 3.0 / 25.0 ) * e4 - ( 12.0 / 35.0 ) * e6;
00751   m_QbarD = ( 4.0 / 49.0 ) * e6;
00752 
00753   m_Qp = getQ( M_PI / 2 );
00754   m_E  = 4 * M_PI * m_Qp * m_AE;
00755   if ( m_E < 0.0 )
00756     m_E = -m_E;
00757 }
00758 
00759 
00760 double QgsDistanceArea::computePolygonArea( const QList<QgsPoint>& points )
00761 {
00762   double x1, y1, x2, y2, dx, dy;
00763   double Qbar1, Qbar2;
00764   double area;
00765 
00766   QgsDebugMsgLevel( "Ellipsoid: " + mEllipsoid, 3 );
00767   if (( ! mEllipsoidalMode ) || ( mEllipsoid == GEO_NONE ) )
00768   {
00769     return computePolygonFlatArea( points );
00770   }
00771   int n = points.size();
00772   x2 = DEG2RAD( points[n-1].x() );
00773   y2 = DEG2RAD( points[n-1].y() );
00774   Qbar2 = getQbar( y2 );
00775 
00776   area = 0.0;
00777 
00778   for ( int i = 0; i < n; i++ )
00779   {
00780     x1 = x2;
00781     y1 = y2;
00782     Qbar1 = Qbar2;
00783 
00784     x2 = DEG2RAD( points[i].x() );
00785     y2 = DEG2RAD( points[i].y() );
00786     Qbar2 = getQbar( y2 );
00787 
00788     if ( x1 > x2 )
00789       while ( x1 - x2 > M_PI )
00790         x2 += m_TwoPI;
00791     else if ( x2 > x1 )
00792       while ( x2 - x1 > M_PI )
00793         x1 += m_TwoPI;
00794 
00795     dx = x2 - x1;
00796     area += dx * ( m_Qp - getQ( y2 ) );
00797 
00798     if (( dy = y2 - y1 ) != 0.0 )
00799       area += dx * getQ( y2 ) - ( dx / dy ) * ( Qbar2 - Qbar1 );
00800   }
00801   if (( area *= m_AE ) < 0.0 )
00802     area = -area;
00803 
00804   /* kludge - if polygon circles the south pole the area will be
00805   * computed as if it cirlced the north pole. The correction is
00806   * the difference between total surface area of the earth and
00807   * the "north pole" area.
00808   */
00809   if ( area > m_E )
00810     area = m_E;
00811   if ( area > m_E / 2 )
00812     area = m_E - area;
00813 
00814   return area;
00815 }
00816 
00817 double QgsDistanceArea::computePolygonFlatArea( const QList<QgsPoint>& points )
00818 {
00819   // Normal plane area calculations.
00820   double area = 0.0;
00821   int i, size;
00822 
00823   size = points.size();
00824 
00825   // QgsDebugMsg("New area calc, nr of points: " + QString::number(size));
00826   for ( i = 0; i < size; i++ )
00827   {
00828     // QgsDebugMsg("Area from point: " + (points[i]).toString(2));
00829     // Using '% size', so that we always end with the starting point
00830     // and thus close the polygon.
00831     area = area + points[i].x() * points[( i+1 ) % size].y() - points[( i+1 ) % size].x() * points[i].y();
00832   }
00833   // QgsDebugMsg("Area from point: " + (points[i % size]).toString(2));
00834   area = area / 2.0;
00835   return qAbs( area ); // All areas are positive!
00836 }
00837 
00838 QString QgsDistanceArea::textUnit( double value, int decimals, QGis::UnitType u, bool isArea, bool keepBaseUnit )
00839 {
00840   QString unitLabel;
00841 
00842   switch ( u )
00843   {
00844     case QGis::Meters:
00845       if ( isArea )
00846       {
00847         if ( keepBaseUnit )
00848         {
00849           unitLabel = QObject::trUtf8( " m²" );
00850         }
00851         else if ( qAbs( value ) > 1000000.0 )
00852         {
00853           unitLabel = QObject::trUtf8( " km²" );
00854           value = value / 1000000.0;
00855         }
00856         else if ( qAbs( value ) > 10000.0 )
00857         {
00858           unitLabel = QObject::tr( " ha" );
00859           value = value / 10000.0;
00860         }
00861         else
00862         {
00863           unitLabel = QObject::trUtf8( " m²" );
00864         }
00865       }
00866       else
00867       {
00868         if ( keepBaseUnit || qAbs( value ) == 0.0 )
00869         {
00870           unitLabel = QObject::tr( " m" );
00871         }
00872         else if ( qAbs( value ) > 1000.0 )
00873         {
00874           unitLabel = QObject::tr( " km" );
00875           value = value / 1000;
00876         }
00877         else if ( qAbs( value ) < 0.01 )
00878         {
00879           unitLabel = QObject::tr( " mm" );
00880           value = value * 1000;
00881         }
00882         else if ( qAbs( value ) < 0.1 )
00883         {
00884           unitLabel = QObject::tr( " cm" );
00885           value = value * 100;
00886         }
00887         else
00888         {
00889           unitLabel = QObject::tr( " m" );
00890         }
00891       }
00892       break;
00893     case QGis::Feet:
00894       if ( isArea )
00895       {
00896         if ( keepBaseUnit  || qAbs( value ) <= 0.5*43560.0 )
00897         {
00898           // < 0.5 acre show sq ft
00899           unitLabel = QObject::tr( " sq ft" );
00900         }
00901         else if ( qAbs( value ) <= 0.5*5280.0*5280.0 )
00902         {
00903           // < 0.5 sq mile show acre
00904           unitLabel = QObject::tr( " acres" );
00905           value /= 43560.0;
00906         }
00907         else
00908         {
00909           // above 0.5 acre show sq mi
00910           unitLabel = QObject::tr( " sq mile" );
00911           value /= 5280.0 * 5280.0;
00912         }
00913       }
00914       else
00915       {
00916         if ( qAbs( value ) <= 528.0 || keepBaseUnit )
00917         {
00918           if ( qAbs( value ) == 1.0 )
00919           {
00920             unitLabel = QObject::tr( " foot" );
00921           }
00922           else
00923           {
00924             unitLabel = QObject::tr( " feet" );
00925           }
00926         }
00927         else
00928         {
00929           unitLabel = QObject::tr( " mile" );
00930           value /= 5280.0;
00931         }
00932       }
00933       break;
00934     case QGis::Degrees:
00935       if ( isArea )
00936       {
00937         unitLabel = QObject::tr( " sq.deg." );
00938       }
00939       else
00940       {
00941         if ( qAbs( value ) == 1.0 )
00942           unitLabel = QObject::tr( " degree" );
00943         else
00944           unitLabel = QObject::tr( " degrees" );
00945       }
00946       break;
00947     case QGis::UnknownUnit:
00948       unitLabel = QObject::tr( " unknown" );
00949     default:
00950       QgsDebugMsg( QString( "Error: not picked up map units - actual value = %1" ).arg( u ) );
00951   };
00952 
00953 
00954   return QLocale::system().toString( value, 'f', decimals ) + unitLabel;
00955 }
00956 
00957 void QgsDistanceArea::convertMeasurement( double &measure, QGis::UnitType &measureUnits, QGis::UnitType displayUnits, bool isArea )
00958 {
00959   // Helper for converting between meters and feet
00960   // The parameters measure and measureUnits are in/out
00961 
00962   if (( measureUnits == QGis::Degrees || measureUnits == QGis::Feet ) &&
00963       mEllipsoid != GEO_NONE &&
00964       mEllipsoidalMode )
00965   {
00966     // Measuring on an ellipsoid returned meters. Force!
00967     measureUnits = QGis::Meters;
00968     QgsDebugMsg( "We're measuring on an ellipsoid or using projections, the system is returning meters" );
00969   }
00970 
00971   // Only convert between meters and feet
00972   if ( measureUnits == QGis::Meters && displayUnits == QGis::Feet )
00973   {
00974     QgsDebugMsg( QString( "Converting %1 meters" ).arg( QString::number( measure ) ) );
00975     measure /= 0.3048;
00976     if ( isArea )
00977     {
00978       measure /= 0.3048;
00979     }
00980     QgsDebugMsg( QString( "to %1 feet" ).arg( QString::number( measure ) ) );
00981     measureUnits = QGis::Feet;
00982   }
00983   if ( measureUnits == QGis::Feet && displayUnits == QGis::Meters )
00984   {
00985     QgsDebugMsg( QString( "Converting %1 feet" ).arg( QString::number( measure ) ) );
00986     measure *= 0.3048;
00987     if ( isArea )
00988     {
00989       measure *= 0.3048;
00990     }
00991     QgsDebugMsg( QString( "to %1 meters" ).arg( QString::number( measure ) ) );
00992     measureUnits = QGis::Meters;
00993   }
00994 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines