QGIS API Documentation  2.8.6-Wien
qgsdistancearea.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsdistancearea.cpp - Distance and area calculations on the ellipsoid
3  ---------------------------------------------------------------------------
4  Date : September 2005
5  Copyright : (C) 2005 by Martin Dobias
6  email : won.der at centrum.sk
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include <cmath>
17 #include <sqlite3.h>
18 #include <QDir>
19 #include <QString>
20 #include <QLocale>
21 #include <QObject>
22 
23 #include "qgis.h"
24 #include "qgspoint.h"
25 #include "qgscoordinatetransform.h"
27 #include "qgsgeometry.h"
28 #include "qgsdistancearea.h"
29 #include "qgsapplication.h"
30 #include "qgslogger.h"
31 #include "qgsmessagelog.h"
32 
33 // MSVC compiler doesn't have defined M_PI in math.h
34 #ifndef M_PI
35 #define M_PI 3.14159265358979323846
36 #endif
37 
38 #define DEG2RAD(x) ((x)*M_PI/180)
39 
40 
42 {
43  // init with default settings
44  mEllipsoidalMode = false;
45  mCoordTransform = new QgsCoordinateTransform;
46  setSourceCrs( GEOCRS_ID ); // WGS 84
48 }
49 
50 
53 {
54  _copy( origDA );
55 }
56 
58 {
59  delete mCoordTransform;
60 }
61 
64 {
65  if ( this == & origDA )
66  {
67  // Do not copy unto self
68  return *this;
69  }
70  _copy( origDA );
71  return *this;
72 }
73 
75 void QgsDistanceArea::_copy( const QgsDistanceArea & origDA )
76 {
77  mEllipsoidalMode = origDA.mEllipsoidalMode;
78  mEllipsoid = origDA.mEllipsoid;
79  mSemiMajor = origDA.mSemiMajor;
80  mSemiMinor = origDA.mSemiMinor;
81  mInvFlattening = origDA.mInvFlattening;
82  // Some calculations and trig. Should not be TOO time consuming.
83  // Alternatively we could copy the temp vars?
85  mCoordTransform = new QgsCoordinateTransform( origDA.mCoordTransform->sourceCrs(), origDA.mCoordTransform->destCRS() );
86 }
87 
89 {
90  mEllipsoidalMode = flag;
91 }
92 
94 {
96  srcCRS.createFromSrsId( srsid );
97  mCoordTransform->setSourceCrs( srcCRS );
98 }
99 
101 {
102  mCoordTransform->setSourceCrs( srcCRS );
103 }
104 
105 void QgsDistanceArea::setSourceAuthId( QString authId )
106 {
108  srcCRS.createFromOgcWmsCrs( authId );
109  mCoordTransform->setSourceCrs( srcCRS );
110 }
111 
113 {
114  QString radius, parameter2;
115  //
116  // SQLITE3 stuff - get parameters for selected ellipsoid
117  //
118  sqlite3 *myDatabase;
119  const char *myTail;
120  sqlite3_stmt *myPreparedStatement;
121  int myResult;
122 
123  // Shortcut if ellipsoid is none.
124  if ( ellipsoid == GEO_NONE )
125  {
126  mEllipsoid = GEO_NONE;
127  return true;
128  }
129 
130  // Check if we have a custom projection, and set from text string.
131  // Format is "PARAMETER:<semi-major axis>:<semi minor axis>
132  // Numbers must be with (optional) decimal point and no other separators (C locale)
133  // Distances in meters. Flattening is calculated.
134  if ( ellipsoid.startsWith( "PARAMETER" ) )
135  {
136  QStringList paramList = ellipsoid.split( ":" );
137  bool semiMajorOk, semiMinorOk;
138  double semiMajor = paramList[1].toDouble( & semiMajorOk );
139  double semiMinor = paramList[2].toDouble( & semiMinorOk );
140  if ( semiMajorOk && semiMinorOk )
141  {
142  return setEllipsoid( semiMajor, semiMinor );
143  }
144  else
145  {
146  return false;
147  }
148  }
149 
150  // Continue with PROJ.4 list of ellipsoids.
151 
152  //check the db is available
153  myResult = sqlite3_open_v2( QgsApplication::srsDbFilePath().toUtf8().data(), &myDatabase, SQLITE_OPEN_READONLY, NULL );
154  if ( myResult )
155  {
156  QgsMessageLog::logMessage( QObject::tr( "Can't open database: %1" ).arg( sqlite3_errmsg( myDatabase ) ) );
157  // XXX This will likely never happen since on open, sqlite creates the
158  // database if it does not exist.
159  return false;
160  }
161  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
162  QString mySql = "select radius, parameter2 from tbl_ellipsoid where acronym='" + ellipsoid + "'";
163  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
164  // XXX Need to free memory from the error msg if one is set
165  if ( myResult == SQLITE_OK )
166  {
167  if ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
168  {
169  radius = QString(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
170  parameter2 = QString(( char * )sqlite3_column_text( myPreparedStatement, 1 ) );
171  }
172  }
173  // close the sqlite3 statement
174  sqlite3_finalize( myPreparedStatement );
175  sqlite3_close( myDatabase );
176 
177  // row for this ellipsoid wasn't found?
178  if ( radius.isEmpty() || parameter2.isEmpty() )
179  {
180  QgsDebugMsg( QString( "setEllipsoid: no row in tbl_ellipsoid for acronym '%1'" ).arg( ellipsoid ) );
181  return false;
182  }
183 
184  // get major semiaxis
185  if ( radius.left( 2 ) == "a=" )
186  mSemiMajor = radius.mid( 2 ).toDouble();
187  else
188  {
189  QgsDebugMsg( QString( "setEllipsoid: wrong format of radius field: '%1'" ).arg( radius ) );
190  return false;
191  }
192 
193  // get second parameter
194  // one of values 'b' or 'f' is in field parameter2
195  // second one must be computed using formula: invf = a/(a-b)
196  if ( parameter2.left( 2 ) == "b=" )
197  {
198  mSemiMinor = parameter2.mid( 2 ).toDouble();
199  mInvFlattening = mSemiMajor / ( mSemiMajor - mSemiMinor );
200  }
201  else if ( parameter2.left( 3 ) == "rf=" )
202  {
203  mInvFlattening = parameter2.mid( 3 ).toDouble();
204  mSemiMinor = mSemiMajor - ( mSemiMajor / mInvFlattening );
205  }
206  else
207  {
208  QgsDebugMsg( QString( "setEllipsoid: wrong format of parameter2 field: '%1'" ).arg( parameter2 ) );
209  return false;
210  }
211 
212  QgsDebugMsg( QString( "setEllipsoid: a=%1, b=%2, 1/f=%3" ).arg( mSemiMajor ).arg( mSemiMinor ).arg( mInvFlattening ) );
213 
214 
215  // get spatial ref system for ellipsoid
216  QString proj4 = "+proj=longlat +ellps=" + ellipsoid + " +no_defs";
218  destCRS.createFromProj4( proj4 );
219  //TODO: createFromProj4 used to save to the user database any new CRS
220  // this behavior was changed in order to separate creation and saving.
221  // Not sure if it necessary to save it here, should be checked by someone
222  // familiar with the code (should also give a more descriptive name to the generated CRS)
223  if ( destCRS.srsid() == 0 )
224  {
225  QString myName = QString( " * %1 (%2)" )
226  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ) )
227  .arg( destCRS.toProj4() );
228  destCRS.saveAsUserCRS( myName );
229  }
230  //
231 
232  // set transformation from project CRS to ellipsoid coordinates
233  mCoordTransform->setDestCRS( destCRS );
234 
235  mEllipsoid = ellipsoid;
236 
237  // precalculate some values for area calculations
238  computeAreaInit();
239 
240  return true;
241 }
242 
244 // Inverse flattening is calculated with invf = a/(a-b)
245 // Also, b = a-(a/invf)
246 bool QgsDistanceArea::setEllipsoid( double semiMajor, double semiMinor )
247 {
248  mEllipsoid = QString( "PARAMETER:%1:%2" ).arg( semiMajor ).arg( semiMinor );
249  mSemiMajor = semiMajor;
250  mSemiMinor = semiMinor;
251  mInvFlattening = mSemiMajor / ( mSemiMajor - mSemiMinor );
252 
253  computeAreaInit();
254 
255  return true;
256 }
257 
259 {
260  if ( !geometry )
261  return 0.0;
262 
263  const unsigned char* wkb = geometry->asWkb();
264  if ( !wkb )
265  return 0.0;
266 
267  QgsConstWkbPtr wkbPtr( wkb + 1 );
268 
269  QGis::WkbType wkbType;
270  wkbPtr >> wkbType;
271 
272  double res, resTotal = 0;
273  int count, i;
274 
275  // measure distance or area based on what is the type of geometry
276  bool hasZptr = false;
277 
278  switch ( wkbType )
279  {
281  hasZptr = true;
282  //intentional fall-through
283  case QGis::WKBLineString:
284  measureLine( wkb, &res, hasZptr );
285  QgsDebugMsg( "returning " + QString::number( res ) );
286  return res;
287 
289  hasZptr = true;
290  //intentional fall-through
292  wkbPtr >> count;
293  for ( i = 0; i < count; i++ )
294  {
295  wkbPtr = measureLine( wkbPtr, &res, hasZptr );
296  resTotal += res;
297  }
298  QgsDebugMsg( "returning " + QString::number( resTotal ) );
299  return resTotal;
300 
301  case QGis::WKBPolygon25D:
302  hasZptr = true;
303  //intentional fall-through
304  case QGis::WKBPolygon:
305  measurePolygon( wkb, &res, 0, hasZptr );
306  QgsDebugMsg( "returning " + QString::number( res ) );
307  return res;
308 
310  hasZptr = true;
311  //intentional fall-through
313  wkbPtr >> count;
314  for ( i = 0; i < count; i++ )
315  {
316  wkbPtr = measurePolygon( wkbPtr, &res, 0, hasZptr );
317  if ( !wkbPtr )
318  {
319  QgsDebugMsg( "measurePolygon returned 0" );
320  break;
321  }
322  resTotal += res;
323  }
324  QgsDebugMsg( "returning " + QString::number( resTotal ) );
325  return resTotal;
326 
327  default:
328  QgsDebugMsg( QString( "measure: unexpected geometry type: %1" ).arg( wkbType ) );
329  return 0;
330  }
331 }
332 
334 {
335  if ( !geometry )
336  return 0.0;
337 
338  const unsigned char* wkb = geometry->asWkb();
339  if ( !wkb )
340  return 0.0;
341 
342  QgsConstWkbPtr wkbPtr( wkb + 1 );
343  QGis::WkbType wkbType;
344  wkbPtr >> wkbType;
345 
346  double res = 0.0, resTotal = 0.0;
347  int count, i;
348 
349  // measure distance or area based on what is the type of geometry
350  bool hasZptr = false;
351 
352  switch ( wkbType )
353  {
355  case QGis::WKBLineString:
358  return 0.0;
359 
360  case QGis::WKBPolygon25D:
361  hasZptr = true;
362  //intentional fall-through
363  case QGis::WKBPolygon:
364  measurePolygon( wkb, 0, &res, hasZptr );
365  QgsDebugMsg( "returning " + QString::number( res ) );
366  return res;
367 
369  hasZptr = true;
370  //intentional fall-through
372  wkbPtr >> count;
373  for ( i = 0; i < count; i++ )
374  {
375  wkbPtr = measurePolygon( wkbPtr, 0, &res, hasZptr );
376  if ( !wkbPtr )
377  {
378  QgsDebugMsg( "measurePolygon returned 0" );
379  break;
380  }
381  resTotal += res;
382  }
383  QgsDebugMsg( "returning " + QString::number( resTotal ) );
384  return resTotal;
385 
386  default:
387  QgsDebugMsg( QString( "measure: unexpected geometry type: %1" ).arg( wkbType ) );
388  return 0;
389  }
390 }
391 
392 
393 const unsigned char* QgsDistanceArea::measureLine( const unsigned char* feature, double* area, bool hasZptr )
394 {
395  QgsConstWkbPtr wkbPtr( feature + 1 + sizeof( int ) );
396  int nPoints;
397  wkbPtr >> nPoints;
398 
399  QList<QgsPoint> points;
400  double x, y;
401 
402  QgsDebugMsg( "This feature WKB has " + QString::number( nPoints ) + " points" );
403  // Extract the points from the WKB format into the vector
404  for ( int i = 0; i < nPoints; ++i )
405  {
406  wkbPtr >> x >> y;
407  if ( hasZptr )
408  {
409  // totally ignore Z value
410  wkbPtr += sizeof( double );
411  }
412 
413  points.append( QgsPoint( x, y ) );
414  }
415 
416  *area = measureLine( points );
417  return wkbPtr;
418 }
419 
420 double QgsDistanceArea::measureLine( const QList<QgsPoint> &points )
421 {
422  if ( points.size() < 2 )
423  return 0;
424 
425  double total = 0;
426  QgsPoint p1, p2;
427 
428  try
429  {
430  if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
431  p1 = mCoordTransform->transform( points[0] );
432  else
433  p1 = points[0];
434 
435  for ( QList<QgsPoint>::const_iterator i = points.begin(); i != points.end(); ++i )
436  {
437  if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
438  {
439  p2 = mCoordTransform->transform( *i );
440  total += computeDistanceBearing( p1, p2 );
441  }
442  else
443  {
444  p2 = *i;
445  total += measureLine( p1, p2 );
446  }
447 
448  p1 = p2;
449  }
450 
451  return total;
452  }
453  catch ( QgsCsException &cse )
454  {
455  Q_UNUSED( cse );
456  QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point. Unable to calculate line length." ) );
457  return 0.0;
458  }
459 
460 }
461 
462 double QgsDistanceArea::measureLine( const QgsPoint &p1, const QgsPoint &p2 )
463 {
464  QGis::UnitType units;
465  return measureLine( p1, p2, units );
466 }
467 
468 double QgsDistanceArea::measureLine( const QgsPoint& p1, const QgsPoint& p2, QGis::UnitType& units )
469 {
470  double result;
471  units = mCoordTransform->sourceCrs().mapUnits();
472 
473  try
474  {
475  QgsPoint pp1 = p1, pp2 = p2;
476 
477  QgsDebugMsgLevel( QString( "Measuring from %1 to %2" ).arg( p1.toString( 4 ) ).arg( p2.toString( 4 ) ), 3 );
478  if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
479  {
480  units = QGis::Meters;
481  QgsDebugMsgLevel( QString( "Ellipsoidal calculations is enabled, using ellipsoid %1" ).arg( mEllipsoid ), 4 );
482  QgsDebugMsgLevel( QString( "From proj4 : %1" ).arg( mCoordTransform->sourceCrs().toProj4() ), 4 );
483  QgsDebugMsgLevel( QString( "To proj4 : %1" ).arg( mCoordTransform->destCRS().toProj4() ), 4 );
484  pp1 = mCoordTransform->transform( p1 );
485  pp2 = mCoordTransform->transform( p2 );
486  QgsDebugMsgLevel( QString( "New points are %1 and %2, calculating..." ).arg( pp1.toString( 4 ) ).arg( pp2.toString( 4 ) ), 4 );
487  result = computeDistanceBearing( pp1, pp2 );
488  }
489  else
490  {
491  QgsDebugMsgLevel( "Cartesian calculation on canvas coordinates", 4 );
492  result = computeDistanceFlat( p1, p2 );
493  }
494  }
495  catch ( QgsCsException &cse )
496  {
497  Q_UNUSED( cse );
498  QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point. Unable to calculate line length." ) );
499  result = 0.0;
500  }
501  QgsDebugMsgLevel( QString( "The result was %1" ).arg( result ), 3 );
502  return result;
503 }
504 
505 
506 const unsigned char *QgsDistanceArea::measurePolygon( const unsigned char* feature, double* area, double* perimeter, bool hasZptr )
507 {
508  if ( !feature )
509  {
510  QgsDebugMsg( "no feature to measure" );
511  return 0;
512  }
513 
514  QgsConstWkbPtr wkbPtr( feature + 1 + sizeof( int ) );
515 
516  // get number of rings in the polygon
517  int numRings;
518  wkbPtr >> numRings;
519 
520  if ( numRings == 0 )
521  {
522  QgsDebugMsg( "no rings to measure" );
523  return 0;
524  }
525 
526  // Set pointer to the first ring
527  QList<QgsPoint> points;
528  QgsPoint pnt;
529  double x, y;
530  if ( area )
531  *area = 0;
532  if ( perimeter )
533  *perimeter = 0;
534 
535  try
536  {
537  for ( int idx = 0; idx < numRings; idx++ )
538  {
539  int nPoints;
540  wkbPtr >> nPoints;
541 
542  // Extract the points from the WKB and store in a pair of
543  // vectors.
544  for ( int jdx = 0; jdx < nPoints; jdx++ )
545  {
546  wkbPtr >> x >> y;
547  if ( hasZptr )
548  {
549  // totally ignore Z value
550  wkbPtr += sizeof( double );
551  }
552 
553  pnt = QgsPoint( x, y );
554 
555  if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
556  {
557  pnt = mCoordTransform->transform( pnt );
558  }
559  points.append( pnt );
560  }
561 
562  if ( points.size() > 2 )
563  {
564  if ( area )
565  {
566  double areaTmp = computePolygonArea( points );
567  if ( idx == 0 )
568  {
569  // exterior ring
570  *area += areaTmp;
571  }
572  else
573  {
574  *area -= areaTmp; // interior rings
575  }
576  }
577 
578  if ( perimeter )
579  {
580  if ( idx == 0 )
581  {
582  // exterior ring
583  *perimeter += computeDistance( points );
584  }
585  }
586  }
587 
588  points.clear();
589  }
590  }
591  catch ( QgsCsException &cse )
592  {
593  Q_UNUSED( cse );
594  QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point. Unable to calculate polygon area or perimeter." ) );
595  }
596 
597  return wkbPtr;
598 }
599 
600 
601 double QgsDistanceArea::measurePolygon( const QList<QgsPoint>& points )
602 {
603  try
604  {
605  if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
606  {
607  QList<QgsPoint> pts;
608  for ( QList<QgsPoint>::const_iterator i = points.begin(); i != points.end(); ++i )
609  {
610  pts.append( mCoordTransform->transform( *i ) );
611  }
612  return computePolygonArea( pts );
613  }
614  else
615  {
616  return computePolygonArea( points );
617  }
618  }
619  catch ( QgsCsException &cse )
620  {
621  Q_UNUSED( cse );
622  QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point. Unable to calculate polygon area." ) );
623  return 0.0;
624  }
625 }
626 
627 
628 double QgsDistanceArea::bearing( const QgsPoint& p1, const QgsPoint& p2 )
629 {
630  QgsPoint pp1 = p1, pp2 = p2;
631  double bearing;
632 
633  if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
634  {
635  pp1 = mCoordTransform->transform( p1 );
636  pp2 = mCoordTransform->transform( p2 );
637  computeDistanceBearing( pp1, pp2, &bearing );
638  }
639  else //compute simple planar azimuth
640  {
641  double dx = p2.x() - p1.x();
642  double dy = p2.y() - p1.y();
643  bearing = atan2( dx, dy );
644  }
645 
646  return bearing;
647 }
648 
649 
651 // distance calculation
652 
654  const QgsPoint& p1, const QgsPoint& p2,
655  double* course1, double* course2 )
656 {
657  if ( p1.x() == p2.x() && p1.y() == p2.y() )
658  return 0;
659 
660  // ellipsoid
661  double a = mSemiMajor;
662  double b = mSemiMinor;
663  double f = 1 / mInvFlattening;
664 
665  double p1_lat = DEG2RAD( p1.y() ), p1_lon = DEG2RAD( p1.x() );
666  double p2_lat = DEG2RAD( p2.y() ), p2_lon = DEG2RAD( p2.x() );
667 
668  double L = p2_lon - p1_lon;
669  double U1 = atan(( 1 - f ) * tan( p1_lat ) );
670  double U2 = atan(( 1 - f ) * tan( p2_lat ) );
671  double sinU1 = sin( U1 ), cosU1 = cos( U1 );
672  double sinU2 = sin( U2 ), cosU2 = cos( U2 );
673  double lambda = L;
674  double lambdaP = 2 * M_PI;
675 
676  double sinLambda = 0;
677  double cosLambda = 0;
678  double sinSigma = 0;
679  double cosSigma = 0;
680  double sigma = 0;
681  double alpha = 0;
682  double cosSqAlpha = 0;
683  double cos2SigmaM = 0;
684  double C = 0;
685  double tu1 = 0;
686  double tu2 = 0;
687 
688  int iterLimit = 20;
689  while ( qAbs( lambda - lambdaP ) > 1e-12 && --iterLimit > 0 )
690  {
691  sinLambda = sin( lambda );
692  cosLambda = cos( lambda );
693  tu1 = ( cosU2 * sinLambda );
694  tu2 = ( cosU1 * sinU2 - sinU1 * cosU2 * cosLambda );
695  sinSigma = sqrt( tu1 * tu1 + tu2 * tu2 );
696  cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
697  sigma = atan2( sinSigma, cosSigma );
698  alpha = asin( cosU1 * cosU2 * sinLambda / sinSigma );
699  cosSqAlpha = cos( alpha ) * cos( alpha );
700  cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
701  C = f / 16 * cosSqAlpha * ( 4 + f * ( 4 - 3 * cosSqAlpha ) );
702  lambdaP = lambda;
703  lambda = L + ( 1 - C ) * f * sin( alpha ) *
704  ( sigma + C * sinSigma * ( cos2SigmaM + C * cosSigma * ( -1 + 2 * cos2SigmaM * cos2SigmaM ) ) );
705  }
706 
707  if ( iterLimit == 0 )
708  return -1; // formula failed to converge
709 
710  double uSq = cosSqAlpha * ( a * a - b * b ) / ( b * b );
711  double A = 1 + uSq / 16384 * ( 4096 + uSq * ( -768 + uSq * ( 320 - 175 * uSq ) ) );
712  double B = uSq / 1024 * ( 256 + uSq * ( -128 + uSq * ( 74 - 47 * uSq ) ) );
713  double deltaSigma = B * sinSigma * ( cos2SigmaM + B / 4 * ( cosSigma * ( -1 + 2 * cos2SigmaM * cos2SigmaM ) -
714  B / 6 * cos2SigmaM * ( -3 + 4 * sinSigma * sinSigma ) * ( -3 + 4 * cos2SigmaM * cos2SigmaM ) ) );
715  double s = b * A * ( sigma - deltaSigma );
716 
717  if ( course1 )
718  {
719  *course1 = atan2( tu1, tu2 );
720  }
721  if ( course2 )
722  {
723  // PI is added to return azimuth from P2 to P1
724  *course2 = atan2( cosU1 * sinLambda, -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda ) + M_PI;
725  }
726 
727  return s;
728 }
729 
731 {
732  return sqrt(( p2.x() - p1.x() ) * ( p2.x() - p1.x() ) + ( p2.y() - p1.y() ) * ( p2.y() - p1.y() ) );
733 }
734 
735 double QgsDistanceArea::computeDistance( const QList<QgsPoint>& points )
736 {
737  if ( points.size() < 2 )
738  return 0;
739 
740  double total = 0;
741  QgsPoint p1, p2;
742 
743  try
744  {
745  p1 = points[0];
746 
747  for ( QList<QgsPoint>::const_iterator i = points.begin(); i != points.end(); ++i )
748  {
749  p2 = *i;
750  if ( mEllipsoidalMode && ( mEllipsoid != GEO_NONE ) )
751  {
752  total += computeDistanceBearing( p1, p2 );
753  }
754  else
755  {
756  total += computeDistanceFlat( p1, p2 );
757  }
758 
759  p1 = p2;
760  }
761 
762  return total;
763  }
764  catch ( QgsCsException &cse )
765  {
766  Q_UNUSED( cse );
767  QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point. Unable to calculate line length." ) );
768  return 0.0;
769  }
770 }
771 
772 
773 
775 // stuff for measuring areas - copied from GRASS
776 // don't know how does it work, but it's working .)
777 // see G_begin_ellipsoid_polygon_area() in area_poly1.c
778 
779 double QgsDistanceArea::getQ( double x )
780 {
781  double sinx, sinx2;
782 
783  sinx = sin( x );
784  sinx2 = sinx * sinx;
785 
786  return sinx *( 1 + sinx2 *( m_QA + sinx2 *( m_QB + sinx2 * m_QC ) ) );
787 }
788 
789 
790 double QgsDistanceArea::getQbar( double x )
791 {
792  double cosx, cosx2;
793 
794  cosx = cos( x );
795  cosx2 = cosx * cosx;
796 
797  return cosx *( m_QbarA + cosx2 *( m_QbarB + cosx2 *( m_QbarC + cosx2 * m_QbarD ) ) );
798 }
799 
800 
802 {
803  //don't try to perform calculations if no ellipsoid
804  if ( mEllipsoid == GEO_NONE )
805  {
806  return;
807  }
808 
809  double a2 = ( mSemiMajor * mSemiMajor );
810  double e2 = 1 - ( a2 / ( mSemiMinor * mSemiMinor ) );
811  double e4, e6;
812 
813  m_TwoPI = M_PI + M_PI;
814 
815  e4 = e2 * e2;
816  e6 = e4 * e2;
817 
818  m_AE = a2 * ( 1 - e2 );
819 
820  m_QA = ( 2.0 / 3.0 ) * e2;
821  m_QB = ( 3.0 / 5.0 ) * e4;
822  m_QC = ( 4.0 / 7.0 ) * e6;
823 
824  m_QbarA = -1.0 - ( 2.0 / 3.0 ) * e2 - ( 3.0 / 5.0 ) * e4 - ( 4.0 / 7.0 ) * e6;
825  m_QbarB = ( 2.0 / 9.0 ) * e2 + ( 2.0 / 5.0 ) * e4 + ( 4.0 / 7.0 ) * e6;
826  m_QbarC = - ( 3.0 / 25.0 ) * e4 - ( 12.0 / 35.0 ) * e6;
827  m_QbarD = ( 4.0 / 49.0 ) * e6;
828 
829  m_Qp = getQ( M_PI / 2 );
830  m_E = 4 * M_PI * m_Qp * m_AE;
831  if ( m_E < 0.0 )
832  m_E = -m_E;
833 }
834 
835 
836 double QgsDistanceArea::computePolygonArea( const QList<QgsPoint>& points )
837 {
838  double x1, y1, x2, y2, dx, dy;
839  double Qbar1, Qbar2;
840  double area;
841 
842  QgsDebugMsgLevel( "Ellipsoid: " + mEllipsoid, 3 );
843  if (( ! mEllipsoidalMode ) || ( mEllipsoid == GEO_NONE ) )
844  {
845  return computePolygonFlatArea( points );
846  }
847  int n = points.size();
848  x2 = DEG2RAD( points[n-1].x() );
849  y2 = DEG2RAD( points[n-1].y() );
850  Qbar2 = getQbar( y2 );
851 
852  area = 0.0;
853 
854  for ( int i = 0; i < n; i++ )
855  {
856  x1 = x2;
857  y1 = y2;
858  Qbar1 = Qbar2;
859 
860  x2 = DEG2RAD( points[i].x() );
861  y2 = DEG2RAD( points[i].y() );
862  Qbar2 = getQbar( y2 );
863 
864  if ( x1 > x2 )
865  while ( x1 - x2 > M_PI )
866  x2 += m_TwoPI;
867  else if ( x2 > x1 )
868  while ( x2 - x1 > M_PI )
869  x1 += m_TwoPI;
870 
871  dx = x2 - x1;
872  area += dx * ( m_Qp - getQ( y2 ) );
873 
874  if (( dy = y2 - y1 ) != 0.0 )
875  area += dx * getQ( y2 ) - ( dx / dy ) * ( Qbar2 - Qbar1 );
876  }
877  if (( area *= m_AE ) < 0.0 )
878  area = -area;
879 
880  /* kludge - if polygon circles the south pole the area will be
881  * computed as if it cirlced the north pole. The correction is
882  * the difference between total surface area of the earth and
883  * the "north pole" area.
884  */
885  if ( area > m_E )
886  area = m_E;
887  if ( area > m_E / 2 )
888  area = m_E - area;
889 
890  return area;
891 }
892 
893 double QgsDistanceArea::computePolygonFlatArea( const QList<QgsPoint>& points )
894 {
895  // Normal plane area calculations.
896  double area = 0.0;
897  int i, size;
898 
899  size = points.size();
900 
901  // QgsDebugMsg("New area calc, nr of points: " + QString::number(size));
902  for ( i = 0; i < size; i++ )
903  {
904  // QgsDebugMsg("Area from point: " + (points[i]).toString(2));
905  // Using '% size', so that we always end with the starting point
906  // and thus close the polygon.
907  area = area + points[i].x() * points[( i+1 ) % size].y() - points[( i+1 ) % size].x() * points[i].y();
908  }
909  // QgsDebugMsg("Area from point: " + (points[i % size]).toString(2));
910  area = area / 2.0;
911  return qAbs( area ); // All areas are positive!
912 }
913 
914 QString QgsDistanceArea::textUnit( double value, int decimals, QGis::UnitType u, bool isArea, bool keepBaseUnit )
915 {
916  QString unitLabel;
917 
918  switch ( u )
919  {
920  case QGis::Meters:
921  if ( isArea )
922  {
923  if ( keepBaseUnit )
924  {
925  unitLabel = QObject::trUtf8( " m²" );
926  }
927  else if ( qAbs( value ) > 1000000.0 )
928  {
929  unitLabel = QObject::trUtf8( " km²" );
930  value = value / 1000000.0;
931  }
932  else if ( qAbs( value ) > 10000.0 )
933  {
934  unitLabel = QObject::tr( " ha" );
935  value = value / 10000.0;
936  }
937  else
938  {
939  unitLabel = QObject::trUtf8( " m²" );
940  }
941  }
942  else
943  {
944  if ( keepBaseUnit || qAbs( value ) == 0.0 )
945  {
946  unitLabel = QObject::tr( " m" );
947  }
948  else if ( qAbs( value ) > 1000.0 )
949  {
950  unitLabel = QObject::tr( " km" );
951  value = value / 1000;
952  }
953  else if ( qAbs( value ) < 0.01 )
954  {
955  unitLabel = QObject::tr( " mm" );
956  value = value * 1000;
957  }
958  else if ( qAbs( value ) < 0.1 )
959  {
960  unitLabel = QObject::tr( " cm" );
961  value = value * 100;
962  }
963  else
964  {
965  unitLabel = QObject::tr( " m" );
966  }
967  }
968  break;
969  case QGis::Feet:
970  if ( isArea )
971  {
972  if ( keepBaseUnit || qAbs( value ) <= 0.5*43560.0 )
973  {
974  // < 0.5 acre show sq ft
975  unitLabel = QObject::tr( " sq ft" );
976  }
977  else if ( qAbs( value ) <= 0.5*5280.0*5280.0 )
978  {
979  // < 0.5 sq mile show acre
980  unitLabel = QObject::tr( " acres" );
981  value /= 43560.0;
982  }
983  else
984  {
985  // above 0.5 acre show sq mi
986  unitLabel = QObject::tr( " sq mile" );
987  value /= 5280.0 * 5280.0;
988  }
989  }
990  else
991  {
992  if ( qAbs( value ) <= 528.0 || keepBaseUnit )
993  {
994  if ( qAbs( value ) == 1.0 )
995  {
996  unitLabel = QObject::tr( " foot" );
997  }
998  else
999  {
1000  unitLabel = QObject::tr( " feet" );
1001  }
1002  }
1003  else
1004  {
1005  unitLabel = QObject::tr( " mile" );
1006  value /= 5280.0;
1007  }
1008  }
1009  break;
1010  case QGis::NauticalMiles:
1011  if ( isArea )
1012  {
1013  unitLabel = QObject::tr( " sq. NM" );
1014  }
1015  else
1016  {
1017  unitLabel = QObject::tr( " NM" );
1018  }
1019  break;
1020  case QGis::Degrees:
1021  if ( isArea )
1022  {
1023  unitLabel = QObject::tr( " sq.deg." );
1024  }
1025  else
1026  {
1027  if ( qAbs( value ) == 1.0 )
1028  unitLabel = QObject::tr( " degree" );
1029  else
1030  unitLabel = QObject::tr( " degrees" );
1031  }
1032  break;
1033  case QGis::UnknownUnit:
1034  unitLabel = QObject::tr( " unknown" );
1035  //intentional fall-through
1036  default:
1037  QgsDebugMsg( QString( "Error: not picked up map units - actual value = %1" ).arg( u ) );
1038  }
1039 
1040  return QLocale::system().toString( value, 'f', decimals ) + unitLabel;
1041 }
1042 
1043 void QgsDistanceArea::convertMeasurement( double &measure, QGis::UnitType &measureUnits, QGis::UnitType displayUnits, bool isArea )
1044 {
1045  // Helper for converting between meters and feet and degrees and NauticalMiles...
1046  // The parameters measure and measureUnits are in/out
1047 
1048  if (( measureUnits == QGis::Degrees || measureUnits == QGis::Feet || measureUnits == QGis::NauticalMiles ) &&
1049  mEllipsoid != GEO_NONE &&
1050  mEllipsoidalMode )
1051  {
1052  // Measuring on an ellipsoid returned meters. Force!
1053  measureUnits = QGis::Meters;
1054  QgsDebugMsg( "We're measuring on an ellipsoid or using projections, the system is returning meters" );
1055  }
1056  else if ( mEllipsoidalMode && mEllipsoid == GEO_NONE )
1057  {
1058  // Measuring in plane within the source CRS. Force its map units
1059  measureUnits = mCoordTransform->sourceCrs().mapUnits();
1060  QgsDebugMsg( "We're measuing on planimetric distance/area on given CRS, measured value is in CRS units" );
1061  }
1062 
1063  // Gets the conversion factor between the specified units
1064  double factorUnits = QGis::fromUnitToUnitFactor( measureUnits, displayUnits );
1065  if ( isArea )
1066  factorUnits *= factorUnits;
1067 
1068  QgsDebugMsg( QString( "Converting %1 %2" ).arg( QString::number( measure ), QGis::toLiteral( measureUnits ) ) );
1069  measure *= factorUnits;
1070  QgsDebugMsg( QString( "to %1 %2" ).arg( QString::number( measure ), QGis::toLiteral( displayUnits ) ) );
1071  measureUnits = displayUnits;
1072 }
1073 
const QgsCoordinateReferenceSystem & sourceCrs() const
double computePolygonFlatArea(const QList< QgsPoint > &points)
double computeDistance(const QList< QgsPoint > &points)
calculate distance with given coordinates (does not do a transform anymore)
~QgsDistanceArea()
Destructor.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
bool saveAsUserCRS(QString name)
Copied from QgsCustomProjectionDialog /// Please refactor into SQL handler !!! ///.
void setSourceCrs(const QgsCoordinateReferenceSystem &theCRS)
void setSourceCrs(long srsid)
sets source spatial reference system (by QGIS CRS)
void computeAreaInit()
precalculates some values (must be called always when changing ellipsoid)
#define DEG2RAD(x)
WkbType
Used for symbology operations.
Definition: qgis.h:53
QgsPoint transform(const QgsPoint &p, TransformDirection direction=ForwardTransform) const
bool setEllipsoid(const QString &ellipsoid)
sets ellipsoid by its acronym
double x() const
Definition: qgspoint.h:126
static void logMessage(QString message, QString tag=QString::null, MessageLevel level=WARNING)
add a message to the instance (and create it if necessary)
bool createFromOgcWmsCrs(QString theCrs)
Set up this CRS from the given OGC CRS.
double measurePerimeter(QgsGeometry *geometry)
measures perimeter of polygon
double measurePolygon(const QList< QgsPoint > &points)
measures polygon area
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:34
double measure(QgsGeometry *geometry)
general measurement (line distance or polygon area)
double computePolygonArea(const QList< QgsPoint > &points)
calculates area of polygon on ellipsoid algorithm has been taken from GRASS: gis/area_poly1.c
double bearing(const QgsPoint &p1, const QgsPoint &p2)
compute bearing - in radians
#define M_PI
const long GEOCRS_ID
Magic number for a geographic coord sys in QGIS srs.db tbl_srs.srs_id.
Definition: qgis.h:398
QString toString() const
String representation of the point (x,y)
Definition: qgspoint.cpp:126
A class to represent a point.
Definition: qgspoint.h:63
struct sqlite3 sqlite3
QgsDistanceArea & operator=(const QgsDistanceArea &origDA)
Assignment operator.
static QString textUnit(double value, int decimals, QGis::UnitType u, bool isArea, bool keepBaseUnit=false)
General purpose distance and area calculator.
void setDestCRS(const QgsCoordinateReferenceSystem &theCRS)
const QString & ellipsoid() const
returns ellipsoid&#39;s acronym
Class for storing a coordinate reference system (CRS)
static const QString srsDbFilePath()
Returns the path to the srs.db file.
double measureLine(const QList< QgsPoint > &points)
measures line
const CORE_EXPORT QString GEO_NONE
Constant that holds the string representation for "No ellips/No CRS".
Definition: qgis.cpp:73
static double fromUnitToUnitFactor(QGis::UnitType fromUnit, QGis::UnitType toUnit)
Returns the conversion factor between the specified units.
Definition: qgis.cpp:135
Class for doing transforms between two map coordinate systems.
UnitType
Map units that qgis supports.
Definition: qgis.h:229
double y() const
Definition: qgspoint.h:134
void convertMeasurement(double &measure, QGis::UnitType &measureUnits, QGis::UnitType displayUnits, bool isArea)
Helper for conversion between physical units.
Custom exception class for Coordinate Reference System related exceptions.
static QString toLiteral(QGis::UnitType unit)
Provides the canonical name of the type value.
Definition: qgis.cpp:113
double computeDistanceFlat(const QgsPoint &p1, const QgsPoint &p2)
uses flat / planimetric / Euclidean distance
const QgsCoordinateReferenceSystem & destCRS() const
const unsigned char * asWkb() const
Returns the buffer containing this geometry in WKB format.
QgsDistanceArea()
Constructor.
void setEllipsoidalMode(bool flag)
sets whether coordinates must be projected to ellipsoid before measuring
bool createFromProj4(const QString &theProjString)
QString toProj4() const
Get the Proj Proj4 string representation of this srs.
double computeDistanceBearing(const QgsPoint &p1, const QgsPoint &p2, double *course1=NULL, double *course2=NULL)
calculates distance from two points on ellipsoid based on inverse Vincenty&#39;s formulae ...
void setSourceAuthId(QString authid)
sets source spatial reference system by authid
#define tr(sourceText)