|
Quantum GIS API Documentation
master-693a1fe
|
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 }