QGIS API Documentation 4.1.0-Master (60fea48833c)
Loading...
Searching...
No Matches
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 "qgsdistancearea.h"
17
18#include <cmath>
19#include <geodesic.h>
20#include <memory>
21
22#include "qgis.h"
25#include "qgscurvepolygon.h"
26#include "qgsexception.h"
27#include "qgsgeometry.h"
29#include "qgslinestring.h"
30#include "qgslogger.h"
31#include "qgsmessagelog.h"
32#include "qgsmultilinestring.h"
33#include "qgsmultisurface.h"
34#include "qgspointxy.h"
35#include "qgspolygon.h"
36#include "qgssurface.h"
37#include "qgsunittypes.h"
38
39#include <QObject>
40#include <QString>
41
42using namespace Qt::StringLiterals;
43
44#define DEG2RAD( x ) ( ( x ) * M_PI / 180 )
45#define RAD2DEG( r ) ( 180.0 * ( r ) / M_PI )
46#define POW2( x ) ( ( x ) * ( x ) )
47
49{
50 // init with default settings
51 mSemiMajor = -1.0;
52 mSemiMinor = -1.0;
53 mInvFlattening = -1.0;
54 const QgsCoordinateTransformContext context; // this is ok - by default we have a source/dest of WGS84, so no reprojection takes place
55 setSourceCrs( QgsCoordinateReferenceSystem( u"EPSG:4326"_s ), context ); // WGS 84
57}
58
60
62 //****** IMPORTANT! editing this? make sure you update the move constructor too! *****
63 : mCoordTransform( other.mCoordTransform )
64 , mEllipsoid( other.mEllipsoid )
65 , mSemiMajor( other.mSemiMajor )
66 , mSemiMinor( other.mSemiMinor )
67 , mInvFlattening( other.mInvFlattening )
68//****** IMPORTANT! editing this? make sure you update the move constructor too! *****
69{
70 computeAreaInit();
71}
72
73
75 : mCoordTransform( std::move( other.mCoordTransform ) )
76 , mEllipsoid( std::move( other.mEllipsoid ) )
77 , mSemiMajor( other.mSemiMajor )
78 , mSemiMinor( other.mSemiMinor )
79 , mInvFlattening( other.mInvFlattening )
80 , mGeod( std::move( other.mGeod ) )
81{}
82
84{
85 if ( &other == this )
86 return *this;
87
88 //****** IMPORTANT! editing this? make sure you update the move assignment operator too! *****
89 mCoordTransform = other.mCoordTransform;
90 mEllipsoid = other.mEllipsoid;
91 mSemiMajor = other.mSemiMajor;
92 mSemiMinor = other.mSemiMinor;
93 mInvFlattening = other.mInvFlattening;
94 computeAreaInit();
95 //****** IMPORTANT! editing this? make sure you update the move assignment operator too! *****
96 return *this;
97}
98
100{
101 if ( &other == this )
102 return *this;
103
104 mCoordTransform = other.mCoordTransform;
105 mEllipsoid = other.mEllipsoid;
106 mSemiMajor = other.mSemiMajor;
107 mSemiMinor = other.mSemiMinor;
108 mInvFlattening = other.mInvFlattening;
109 mGeod = std::move( other.mGeod );
110 return *this;
111}
112
114{
115 return mEllipsoid != Qgis::geoNone();
116}
117
119{
120 mCoordTransform.setContext( context );
121 mCoordTransform.setSourceCrs( srcCRS );
122}
123
125{
126 // Shortcut if ellipsoid is none.
127 if ( ellipsoid == Qgis::geoNone() )
128 {
129 mEllipsoid = Qgis::geoNone();
130 mGeod.reset();
131 return true;
132 }
133
135 if ( !params.valid )
136 {
137 mGeod.reset();
138 return false;
139 }
140 else
141 {
142 mEllipsoid = ellipsoid;
143 setFromParams( params );
144 return true;
145 }
146}
147
148// Inverse flattening is calculated with invf = a/(a-b)
149// Also, b = a-(a/invf)
150bool QgsDistanceArea::setEllipsoid( double semiMajor, double semiMinor )
151{
152 mEllipsoid = u"PARAMETER:%1:%2"_s.arg( qgsDoubleToString( semiMajor ), qgsDoubleToString( semiMinor ) );
153 mSemiMajor = semiMajor;
154 mSemiMinor = semiMinor;
155 mInvFlattening = mSemiMajor / ( mSemiMajor - mSemiMinor );
156
157 computeAreaInit();
158
159 return true;
160}
161
162double QgsDistanceArea::measure( const QgsAbstractGeometry *geomV2, MeasureType type ) const
163{
164 if ( !geomV2 )
165 {
166 return 0.0;
167 }
168
169 const int geomDimension = geomV2->dimension();
170 if ( geomDimension <= 0 )
171 {
172 return 0.0;
173 }
174
175 MeasureType measureType = type;
176 if ( measureType == Default )
177 {
178 measureType = ( geomDimension == 1 ? Length : Area );
179 }
180
181 if ( !willUseEllipsoid() )
182 {
183 //no transform required
184 if ( measureType == Length )
185 {
186 return geomV2->length();
187 }
188 else
189 {
190 return geomV2->area();
191 }
192 }
193 else
194 {
195 //multigeom is sum of measured parts
196 const QgsGeometryCollection *collection = qgsgeometry_cast<const QgsGeometryCollection *>( geomV2 );
197 if ( collection )
198 {
199 double sum = 0;
200 for ( int i = 0; i < collection->numGeometries(); ++i )
201 {
202 sum += measure( collection->geometryN( i ), measureType );
203 }
204 return sum;
205 }
206
207 if ( measureType == Length )
208 {
209 const QgsCurve *curve = qgsgeometry_cast<const QgsCurve *>( geomV2 );
210 if ( !curve )
211 {
212 return 0.0;
213 }
214
215 QgsLineString *lineString = curve->curveToLine();
216 const double length = measureLine( lineString );
217 delete lineString;
218 return length;
219 }
220 else
221 {
222 const QgsSurface *surface = qgsgeometry_cast<const QgsSurface *>( geomV2 );
223 if ( !surface )
224 return 0.0;
225
226 double area = 0;
227 const QgsCurvePolygon *curvePolygon = qgsgeometry_cast<const QgsCurvePolygon *>( surface );
228 if ( curvePolygon )
229 {
230 QgsPolygon *polygon = curvePolygon->surfaceToPolygon();
231
232 const QgsCurve *outerRing = polygon->exteriorRing();
233 area += measurePolygon( outerRing );
234
235 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
236 {
237 const QgsCurve *innerRing = polygon->interiorRing( i );
238 area -= measurePolygon( innerRing );
239 }
240 delete polygon;
241 }
242 return area;
243 }
244 }
245}
246
247double QgsDistanceArea::measureArea( const QgsGeometry &geometry ) const
248{
249 if ( geometry.isNull() )
250 return 0.0;
251
252 const QgsAbstractGeometry *geomV2 = geometry.constGet();
253 return measure( geomV2, Area );
254}
255
256double QgsDistanceArea::measureLength( const QgsGeometry &geometry ) const
257{
258 if ( geometry.isNull() )
259 return 0.0;
260
261 const QgsAbstractGeometry *geomV2 = geometry.constGet();
262 return measure( geomV2, Length );
263}
264
265double QgsDistanceArea::measurePerimeter( const QgsGeometry &geometry ) const
266{
267 if ( geometry.isNull() )
268 return 0.0;
269
270 const QgsAbstractGeometry *geomV2 = geometry.constGet();
271 if ( !geomV2 || geomV2->dimension() < 2 )
272 {
273 return 0.0;
274 }
275
276 if ( !willUseEllipsoid() )
277 {
278 return geomV2->perimeter();
279 }
280
281 //create list with (single) surfaces
282 QVector< const QgsSurface * > surfaces;
283 const QgsSurface *surf = qgsgeometry_cast<const QgsSurface *>( geomV2 );
284 if ( surf )
285 {
286 surfaces.append( surf );
287 }
289 if ( multiSurf )
290 {
291 surfaces.reserve( ( surf ? 1 : 0 ) + multiSurf->numGeometries() );
292 for ( int i = 0; i < multiSurf->numGeometries(); ++i )
293 {
294 surfaces.append( static_cast<const QgsSurface *>( multiSurf->geometryN( i ) ) );
295 }
296 }
297
298 double length = 0;
299 QVector<const QgsSurface *>::const_iterator surfaceIt = surfaces.constBegin();
300 for ( ; surfaceIt != surfaces.constEnd(); ++surfaceIt )
301 {
302 if ( !*surfaceIt )
303 {
304 continue;
305 }
306
307 const QgsCurvePolygon *curvePolygon = qgsgeometry_cast<const QgsCurvePolygon *>( *surfaceIt );
308 if ( curvePolygon )
309 {
310 QgsPolygon *poly = curvePolygon->surfaceToPolygon();
311 const QgsCurve *outerRing = poly->exteriorRing();
312 if ( outerRing )
313 {
314 length += measure( outerRing );
315 }
316 const int nInnerRings = poly->numInteriorRings();
317 for ( int i = 0; i < nInnerRings; ++i )
318 {
319 length += measure( poly->interiorRing( i ) );
320 }
321 delete poly;
322 }
323 }
324 return length;
325}
326
327double QgsDistanceArea::measureLine( const QgsCurve *curve ) const
328{
329 if ( !curve )
330 {
331 return 0.0;
332 }
333
334 QgsPointSequence linePointsV2;
335 QVector<QgsPointXY> linePoints;
336 curve->points( linePointsV2 );
337 QgsGeometry::convertPointList( linePointsV2, linePoints );
338 return measureLine( linePoints );
339}
340
341double QgsDistanceArea::measureLine( const QVector<QgsPointXY> &points ) const
342{
343 if ( points.size() < 2 )
344 return 0;
345
346 double total = 0;
347 QgsPointXY p1, p2;
348
349 if ( willUseEllipsoid() )
350 {
351 if ( !mGeod )
352 computeAreaInit();
353 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::measureLine()", "Error creating geod_geodesic object" );
354 if ( !mGeod )
355 return 0;
356 }
357
358 if ( willUseEllipsoid() )
359 p1 = mCoordTransform.transform( points[0] );
360 else
361 p1 = points[0];
362
363 for ( QVector<QgsPointXY>::const_iterator i = points.constBegin(); i != points.constEnd(); ++i )
364 {
365 if ( willUseEllipsoid() )
366 {
367 p2 = mCoordTransform.transform( *i );
368
369 double distance = 0;
370 double azimuth1 = 0;
371 double azimuth2 = 0;
372 geod_inverse( mGeod.get(), p1.y(), p1.x(), p2.y(), p2.x(), &distance, &azimuth1, &azimuth2 );
373 total += distance;
374 }
375 else
376 {
377 p2 = *i;
378 total += measureLine( p1, p2 );
379 }
380
381 p1 = p2;
382 }
383
384 return total;
385}
386
387double QgsDistanceArea::measureLine( const QgsPointXY &p1, const QgsPointXY &p2 ) const
388{
389 double result;
390
391 if ( willUseEllipsoid() )
392 {
393 if ( !mGeod )
394 computeAreaInit();
395 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::measureLine()", "Error creating geod_geodesic object" );
396 if ( !mGeod )
397 return 0;
398 }
399
400 QgsPointXY pp1 = p1, pp2 = p2;
401
402 QgsDebugMsgLevel( u"Measuring from %1 to %2"_s.arg( p1.toString( 4 ), p2.toString( 4 ) ), 3 );
403 if ( willUseEllipsoid() )
404 {
405 QgsDebugMsgLevel( u"Ellipsoidal calculations is enabled, using ellipsoid %1"_s.arg( mEllipsoid ), 4 );
406 QgsDebugMsgLevel( u"From proj4 : %1"_s.arg( mCoordTransform.sourceCrs().toProj() ), 4 );
407 QgsDebugMsgLevel( u"To proj4 : %1"_s.arg( mCoordTransform.destinationCrs().toProj() ), 4 );
408 pp1 = mCoordTransform.transform( p1 );
409 pp2 = mCoordTransform.transform( p2 );
410 QgsDebugMsgLevel( u"New points are %1 and %2, calculating..."_s.arg( pp1.toString( 4 ), pp2.toString( 4 ) ), 4 );
411
412 double azimuth1 = 0;
413 double azimuth2 = 0;
414 geod_inverse( mGeod.get(), pp1.y(), pp1.x(), pp2.y(), pp2.x(), &result, &azimuth1, &azimuth2 );
415 }
416 else
417 {
418 QgsDebugMsgLevel( u"Cartesian calculation on canvas coordinates"_s, 4 );
419 result = p2.distance( p1 );
420 }
421
422 QgsDebugMsgLevel( u"The result was %1"_s.arg( result ), 3 );
423 return result;
424}
425
426double QgsDistanceArea::measureLineProjected( const QgsPointXY &p1, double distance, double azimuth, QgsPointXY *projectedPoint ) const
427{
428 double result = 0.0;
429 QgsPointXY p2;
430 if ( mCoordTransform.sourceCrs().isGeographic() && willUseEllipsoid() )
431 {
432 p2 = computeSpheroidProject( p1, distance, azimuth );
433 result = p1.distance( p2 );
434 }
435 else // Cartesian coordinates
436 {
437 result = distance; // Avoid rounding errors when using meters [return as sent]
438 if ( sourceCrs().mapUnits() != Qgis::DistanceUnit::Meters )
439 {
440 distance = ( distance * QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Meters, sourceCrs().mapUnits() ) );
441 result = p1.distance( p2 );
442 }
443 p2 = p1.project( distance, azimuth );
444 }
446 u"Converted distance of %1 %2 to %3 distance %4 %5, using azimuth[%6] from point[%7] to point[%8] sourceCrs[%9] mEllipsoid[%10] isGeographic[%11] [%12]"_s
447 .arg(
448 QString::number( distance, 'f', 7 ),
450 QString::number( result, 'f', 7 ),
451 mCoordTransform.sourceCrs().isGeographic() ? u"Geographic"_s : u"Cartesian"_s,
452 QgsUnitTypes::toString( sourceCrs().mapUnits() )
453 )
454 .arg( azimuth )
455 .arg( p1.asWkt(), p2.asWkt(), sourceCrs().description(), mEllipsoid )
456 .arg( sourceCrs().isGeographic() )
457 .arg( u"SemiMajor[%1] SemiMinor[%2] InvFlattening[%3] "_s.arg( QString::number( mSemiMajor, 'f', 7 ), QString::number( mSemiMinor, 'f', 7 ), QString::number( mInvFlattening, 'f', 7 ) ) ),
458 4
459 );
460 if ( projectedPoint )
461 {
462 *projectedPoint = QgsPointXY( p2 );
463 }
464 return result;
465}
466
467QgsPointXY QgsDistanceArea::computeSpheroidProject( const QgsPointXY &p1, double distance, double azimuth ) const
468{
469 if ( !mGeod )
470 computeAreaInit();
471 if ( !mGeod )
472 return QgsPointXY();
473
474 double lat2 = 0;
475 double lon2 = 0;
476 double azimuth2 = 0;
477 geod_direct( mGeod.get(), p1.y(), p1.x(), RAD2DEG( azimuth ), distance, &lat2, &lon2, &azimuth2 );
478
479 return QgsPointXY( lon2, lat2 );
480}
481
482double QgsDistanceArea::latitudeGeodesicCrossesAntimeridian( const QgsPointXY &pp1, const QgsPointXY &pp2, double &fractionAlongLine ) const
483{
484 QgsPointXY p1 = pp1;
485 QgsPointXY p2 = pp2;
486 if ( p1.x() < -120 )
487 p1.setX( p1.x() + 360 );
488 if ( p2.x() < -120 )
489 p2.setX( p2.x() + 360 );
490
491 // we need p2.x() > 180 and p1.x() < 180
492 double p1x = p1.x() < 180 ? p1.x() : p2.x();
493 double p1y = p1.x() < 180 ? p1.y() : p2.y();
494 double p2x = p1.x() < 180 ? p2.x() : p1.x();
495 double p2y = p1.x() < 180 ? p2.y() : p1.y();
496 // lat/lon are our candidate intersection position - we want this to get as close to 180 as possible
497 // the first candidate is p2
498 double lat = p2y;
499 double lon = p2x;
500
501 if ( mEllipsoid == Qgis::geoNone() )
502 {
503 fractionAlongLine = ( 180 - p1x ) / ( p2x - p1x );
504 if ( p1.x() >= 180 )
505 fractionAlongLine = 1 - fractionAlongLine;
506 return p1y + ( 180 - p1x ) / ( p2x - p1x ) * ( p2y - p1y );
507 }
508
509 if ( !mGeod )
510 computeAreaInit();
511 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::latitudeGeodesicCrossesAntimeridian()", "Error creating geod_geodesic object" );
512 if ( !mGeod )
513 return 0;
514
515 geod_geodesicline line;
516 geod_inverseline( &line, mGeod.get(), p1y, p1x, p2y, p2x, GEOD_ALL );
517
518 const double totalDist = line.s13;
519 double intersectionDist = line.s13;
520
521 int iterations = 0;
522 double t = 0;
523 // iterate until our intersection candidate is within ~1 mm of the antimeridian (or too many iterations happened)
524 while ( std::fabs( lon - 180.0 ) > 0.00000001 && iterations < 100 )
525 {
526 if ( iterations > 0 && std::fabs( p2x - p1x ) > 5 )
527 {
528 // if we have too large a range of longitudes, we use a binary search to narrow the window -- this ensures we will converge
529 if ( lon < 180 )
530 {
531 p1x = lon;
532 p1y = lat;
533 }
534 else
535 {
536 p2x = lon;
537 p2y = lat;
538 }
539 QgsDebugMsgLevel( u"Narrowed window to %1, %2 - %3, %4"_s.arg( p1x ).arg( p1y ).arg( p2x ).arg( p2y ), 4 );
540
541 geod_inverseline( &line, mGeod.get(), p1y, p1x, p2y, p2x, GEOD_ALL );
542 intersectionDist = line.s13 * 0.5;
543 }
544 else
545 {
546 // we have a sufficiently narrow window -- use Newton's method
547 // adjust intersection distance by fraction of how close the previous candidate was to 180 degrees longitude -
548 // this helps us close in to the correct longitude quickly
549 intersectionDist *= ( 180.0 - p1x ) / ( lon - p1x );
550 }
551
552 // now work out the point on the geodesic this far from p1 - this becomes our new candidate for crossing the antimeridian
553
554 geod_position( &line, intersectionDist, &lat, &lon, &t );
555 // we don't want to wrap longitudes > 180 around)
556 if ( lon < 0 )
557 lon += 360;
558
559 iterations++;
560 QgsDebugMsgLevel( u"After %1 iterations lon is %2, lat is %3, dist from p1: %4"_s.arg( iterations ).arg( lon ).arg( lat ).arg( intersectionDist ), 4 );
561 }
562
563 fractionAlongLine = intersectionDist / totalDist;
564 if ( p1.x() >= 180 )
565 fractionAlongLine = 1 - fractionAlongLine;
566
567 // either converged on 180 longitude or hit too many iterations
568 return lat;
569}
570
572{
574 return geometry;
575
576 QgsGeometry g = geometry;
577 // TODO - avoid segmentization of curved geometries (if this is even possible!)
580
581 auto res = std::make_unique< QgsMultiLineString >();
582 for ( auto part = g.const_parts_begin(); part != g.const_parts_end(); ++part )
583 {
585 if ( !line )
586 continue;
587 if ( line->isEmpty() )
588 {
589 continue;
590 }
591
592 const std::unique_ptr< QgsLineString > l = std::make_unique< QgsLineString >();
593 try
594 {
595 double x = 0;
596 double y = 0;
597 double z = 0;
598 double m = 0;
599 QVector< QgsPoint > newPoints;
600 newPoints.reserve( line->numPoints() );
601 double prevLon = 0;
602 double prevLat = 0;
603 double lon = 0;
604 double lat = 0;
605 double prevZ = 0;
606 double prevM = 0;
607 for ( int i = 0; i < line->numPoints(); i++ )
608 {
609 QgsPoint p = line->pointN( i );
610 x = p.x();
611 if ( mCoordTransform.sourceCrs().isGeographic() )
612 {
613 x = std::fmod( x, 360.0 );
614 if ( x > 180 )
615 x -= 360;
616 p.setX( x );
617 }
618 y = p.y();
619 lon = x;
620 lat = y;
621 mCoordTransform.transformInPlace( lon, lat, z );
622
623 //test if we crossed the antimeridian in this segment
624 if ( i > 0 && ( ( prevLon < -120 && lon > 120 ) || ( prevLon > 120 && lon < -120 ) ) )
625 {
626 // we did!
627 // when crossing the antimeridian, we need to calculate the latitude
628 // at which the geodesic intersects the antimeridian
629 double fract = 0;
630 const double lat180 = latitudeGeodesicCrossesAntimeridian( QgsPointXY( prevLon, prevLat ), QgsPointXY( lon, lat ), fract );
631 if ( line->is3D() )
632 {
633 z = prevZ + ( p.z() - prevZ ) * fract;
634 }
635 if ( line->isMeasure() )
636 {
637 m = prevM + ( p.m() - prevM ) * fract;
638 }
639
640 QgsPointXY antiMeridianPoint;
641 if ( prevLon < -120 )
642 antiMeridianPoint = mCoordTransform.transform( QgsPointXY( -180, lat180 ), Qgis::TransformDirection::Reverse );
643 else
644 antiMeridianPoint = mCoordTransform.transform( QgsPointXY( 180, lat180 ), Qgis::TransformDirection::Reverse );
645
646 QgsPoint newPoint( antiMeridianPoint );
647 if ( line->is3D() )
648 newPoint.addZValue( z );
649 if ( line->isMeasure() )
650 newPoint.addMValue( m );
651
652 if ( std::isfinite( newPoint.x() ) && std::isfinite( newPoint.y() ) )
653 {
654 newPoints << newPoint;
655 }
656 res->addGeometry( new QgsLineString( newPoints ) );
657
658 newPoints.clear();
659 newPoints.reserve( line->numPoints() - i + 1 );
660
661 if ( lon < -120 )
662 antiMeridianPoint = mCoordTransform.transform( QgsPointXY( -180, lat180 ), Qgis::TransformDirection::Reverse );
663 else
664 antiMeridianPoint = mCoordTransform.transform( QgsPointXY( 180, lat180 ), Qgis::TransformDirection::Reverse );
665
666 if ( std::isfinite( antiMeridianPoint.x() ) && std::isfinite( antiMeridianPoint.y() ) )
667 {
668 // we want to keep the previously calculated z/m value for newPoint, if present. They're the same each
669 // of the antimeridian split
670 newPoint.setX( antiMeridianPoint.x() );
671 newPoint.setY( antiMeridianPoint.y() );
672 newPoints << newPoint;
673 }
674 }
675 newPoints << p;
676
677 prevLon = lon;
678 prevLat = lat;
679 if ( line->is3D() )
680 prevZ = p.z();
681 if ( line->isMeasure() )
682 prevM = p.m();
683 }
684 res->addGeometry( new QgsLineString( newPoints ) );
685 }
686 catch ( QgsCsException & )
687 {
688 QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform linestring. Unable to calculate break point." ) );
689 res->addGeometry( line->clone() );
690 break;
691 }
692 }
693
694 return QgsGeometry( std::move( res ) );
695}
696
697
698QVector< QVector<QgsPointXY> > QgsDistanceArea::geodesicLine( const QgsPointXY &p1, const QgsPointXY &p2, const double interval, const bool breakLine ) const
699{
700 if ( !willUseEllipsoid() )
701 {
702 return QVector< QVector< QgsPointXY > >() << ( QVector< QgsPointXY >() << p1 << p2 );
703 }
704
705 if ( !mGeod )
706 computeAreaInit();
707 if ( !mGeod )
708 return QVector< QVector< QgsPointXY > >();
709
710 QgsPointXY pp1, pp2;
711 try
712 {
713 pp1 = mCoordTransform.transform( p1 );
714 pp2 = mCoordTransform.transform( p2 );
715 }
716 catch ( QgsCsException & )
717 {
718 QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point. Unable to calculate geodesic line." ) );
719 return QVector< QVector< QgsPointXY > >();
720 }
721
722 geod_geodesicline line;
723 geod_inverseline( &line, mGeod.get(), pp1.y(), pp1.x(), pp2.y(), pp2.x(), GEOD_ALL );
724 const double totalDist = line.s13;
725
726 QVector< QVector< QgsPointXY > > res;
727 QVector< QgsPointXY > currentPart;
728 currentPart << p1;
729 double d = interval;
730 double prevLon = pp1.x();
731 double prevLat = pp1.y();
732 bool lastRun = false;
733 double t = 0;
734 while ( true )
735 {
736 double lat, lon;
737 if ( lastRun )
738 {
739 lat = pp2.y();
740 lon = pp2.x();
741 if ( lon > 180 )
742 lon -= 360;
743 }
744 else
745 {
746 geod_position( &line, d, &lat, &lon, &t );
747 }
748
749 if ( breakLine && ( ( prevLon < -120 && lon > 120 ) || ( prevLon > 120 && lon < -120 ) ) )
750 {
751 // when breaking the geodesic at the antimeridian, we need to calculate the latitude
752 // at which the geodesic intersects the antimeridian, and add points to both line segments at this latitude
753 // on the antimeridian.
754 double fraction;
755 const double lat180 = latitudeGeodesicCrossesAntimeridian( QgsPointXY( prevLon, prevLat ), QgsPointXY( lon, lat ), fraction );
756
757 try
758 {
759 QgsPointXY p;
760 if ( prevLon < -120 )
761 p = mCoordTransform.transform( QgsPointXY( -180, lat180 ), Qgis::TransformDirection::Reverse );
762 else
763 p = mCoordTransform.transform( QgsPointXY( 180, lat180 ), Qgis::TransformDirection::Reverse );
764
765 if ( std::isfinite( p.x() ) && std::isfinite( p.y() ) )
766 currentPart << p;
767 }
768 catch ( QgsCsException & )
769 {
770 QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point." ) );
771 }
772
773 res << currentPart;
774 currentPart.clear();
775 try
776 {
777 QgsPointXY p;
778 if ( lon < -120 )
779 p = mCoordTransform.transform( QgsPointXY( -180, lat180 ), Qgis::TransformDirection::Reverse );
780 else
781 p = mCoordTransform.transform( QgsPointXY( 180, lat180 ), Qgis::TransformDirection::Reverse );
782
783 if ( std::isfinite( p.x() ) && std::isfinite( p.y() ) )
784 currentPart << p;
785 }
786 catch ( QgsCsException & )
787 {
788 QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point." ) );
789 }
790 }
791
792 prevLon = lon;
793 prevLat = lat;
794
795 try
796 {
797 currentPart << mCoordTransform.transform( QgsPointXY( lon, lat ), Qgis::TransformDirection::Reverse );
798 }
799 catch ( QgsCsException & )
800 {
801 QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point." ) );
802 }
803
804 if ( lastRun )
805 break;
806
807 d += interval;
808 if ( d >= totalDist )
809 lastRun = true;
810 }
811 res << currentPart;
812 return res;
813}
814
816{
817 return willUseEllipsoid() ? Qgis::DistanceUnit::Meters : mCoordTransform.sourceCrs().mapUnits();
818}
819
821{
822 return willUseEllipsoid() ? Qgis::AreaUnit::SquareMeters : QgsUnitTypes::distanceToAreaUnit( mCoordTransform.sourceCrs().mapUnits() );
823}
824
825double QgsDistanceArea::measurePolygon( const QgsCurve *curve ) const
826{
827 if ( !curve )
828 {
829 return 0.0;
830 }
831
832 QgsPointSequence linePointsV2;
833 curve->points( linePointsV2 );
834 QVector<QgsPointXY> linePoints;
835 QgsGeometry::convertPointList( linePointsV2, linePoints );
836 return measurePolygon( linePoints );
837}
838
839
840double QgsDistanceArea::measurePolygon( const QVector<QgsPointXY> &points ) const
841{
842 if ( willUseEllipsoid() )
843 {
844 QVector<QgsPointXY> pts;
845 for ( QVector<QgsPointXY>::const_iterator i = points.constBegin(); i != points.constEnd(); ++i )
846 {
847 pts.append( mCoordTransform.transform( *i ) );
848 }
849 return computePolygonArea( pts );
850 }
851 else
852 {
853 return computePolygonArea( points );
854 }
855}
856
857
858double QgsDistanceArea::bearing( const QgsPointXY &p1, const QgsPointXY &p2 ) const
859{
860 QgsPointXY pp1 = p1, pp2 = p2;
861 double bearing;
862
863 if ( willUseEllipsoid() )
864 {
865 pp1 = mCoordTransform.transform( p1 );
866 pp2 = mCoordTransform.transform( p2 );
867
868 if ( !mGeod )
869 computeAreaInit();
870 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::bearing()", "Error creating geod_geodesic object" );
871 if ( !mGeod )
872 return 0;
873
874 double distance = 0;
875 double azimuth1 = 0;
876 double azimuth2 = 0;
877 geod_inverse( mGeod.get(), pp1.y(), pp1.x(), pp2.y(), pp2.x(), &distance, &azimuth1, &azimuth2 );
878
879 bearing = DEG2RAD( azimuth1 );
880 }
881 else //compute simple planar azimuth
882 {
883 const double dx = p2.x() - p1.x();
884 const double dy = p2.y() - p1.y();
885 // Note: the prototype of std::atan2 is (y,x), to return the angle of
886 // vector (x,y) from the horizontal axis in counter-clock-wise orientation.
887 // But a bearing is expressed in clock-wise order from the vertical axis, so
888 // M_PI / 2 - std::atan2( dy, dx ) == std::atan2( dx, dy )
889 bearing = std::atan2( dx, dy );
890 }
891
892 return bearing;
893}
894
895void QgsDistanceArea::computeAreaInit() const
896{
897 //don't try to perform calculations if no ellipsoid
898 if ( mEllipsoid == Qgis::geoNone() )
899 {
900 mGeod.reset();
901 return;
902 }
903
904 mGeod = std::make_unique<geod_geodesic>();
905 geod_init( mGeod.get(), mSemiMajor, 1 / mInvFlattening );
906}
907
908void QgsDistanceArea::setFromParams( const QgsEllipsoidUtils::EllipsoidParameters &params )
909{
910 if ( params.useCustomParameters )
911 {
912 setEllipsoid( params.semiMajor, params.semiMinor );
913 mCoordTransform.setDestinationCrs( params.crs );
914 }
915 else
916 {
917 mSemiMajor = params.semiMajor;
918 mSemiMinor = params.semiMinor;
919 mInvFlattening = params.inverseFlattening;
920 mCoordTransform.setDestinationCrs( params.crs );
921 computeAreaInit();
922 }
923}
924
925double QgsDistanceArea::computePolygonArea( const QVector<QgsPointXY> &points ) const
926{
927 if ( points.isEmpty() )
928 {
929 return 0;
930 }
931
932 QgsDebugMsgLevel( "Ellipsoid: " + mEllipsoid, 3 );
933 if ( !willUseEllipsoid() )
934 {
935 return computePolygonFlatArea( points );
936 }
937
938 if ( !mGeod )
939 computeAreaInit();
940 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::computePolygonArea()", "Error creating geod_geodesic object" );
941 if ( !mGeod )
942 return 0;
943
944 struct geod_polygon p;
945 geod_polygon_init( &p, 0 );
946
947 const bool isClosed = points.constFirst() == points.constLast();
948
949 /* GeographicLib does not need a closed ring,
950 * see example for geod_polygonarea() in geodesic.h */
951 /* add points in reverse order */
952 int i = points.size();
953 while ( ( isClosed && --i ) || ( !isClosed && --i >= 0 ) )
954 geod_polygon_addpoint( mGeod.get(), &p, points.at( i ).y(), points.at( i ).x() );
955
956 double area = 0;
957 double perimeter = 0;
958 geod_polygon_compute( mGeod.get(), &p, 0, 1, &area, &perimeter );
959
960 return std::fabs( area );
961}
962
963double QgsDistanceArea::computePolygonFlatArea( const QVector<QgsPointXY> &points ) const
964{
965 // Normal plane area calculations.
966 double area = 0.0;
967 int i, size;
968
969 size = points.size();
970
971 // QgsDebugMsgLevel("New area calc, nr of points: " + QString::number(size), 2);
972 for ( i = 0; i < size; i++ )
973 {
974 // QgsDebugMsgLevel("Area from point: " + (points[i]).toString(2), 2);
975 // Using '% size', so that we always end with the starting point
976 // and thus close the polygon.
977 area = area + points[i].x() * points[( i + 1 ) % size].y() - points[( i + 1 ) % size].x() * points[i].y();
978 }
979 // QgsDebugMsgLevel("Area from point: " + (points[i % size]).toString(2), 2);
980 area = area / 2.0;
981 return std::fabs( area ); // All areas are positive!
982}
983
984QString QgsDistanceArea::formatDistance( double distance, int decimals, Qgis::DistanceUnit unit, bool keepBaseUnit )
985{
986 return QgsUnitTypes::formatDistance( distance, decimals, unit, keepBaseUnit );
987}
988
989QString QgsDistanceArea::formatArea( double area, int decimals, Qgis::AreaUnit unit, bool keepBaseUnit )
990{
991 return QgsUnitTypes::formatArea( area, decimals, unit, keepBaseUnit );
992}
993
995{
996 // get the conversion factor between the specified units
997 const Qgis::DistanceUnit measureUnits = lengthUnits();
998 const double factorUnits = QgsUnitTypes::fromUnitToUnitFactor( measureUnits, toUnits );
999
1000 const double result = length * factorUnits;
1001 QgsDebugMsgLevel( u"Converted length of %1 %2 to %3 %4"_s.arg( length ).arg( QgsUnitTypes::toString( measureUnits ) ).arg( result ).arg( QgsUnitTypes::toString( toUnits ) ), 3 );
1002 return result;
1003}
1004
1006{
1007 // get the conversion factor between the specified units
1008 const Qgis::AreaUnit measureUnits = areaUnits();
1009 const double factorUnits = QgsUnitTypes::fromUnitToUnitFactor( measureUnits, toUnits );
1010
1011 const double result = area * factorUnits;
1012 QgsDebugMsgLevel( u"Converted area of %1 %2 to %3 %4"_s.arg( area ).arg( QgsUnitTypes::toString( measureUnits ) ).arg( result ).arg( QgsUnitTypes::toString( toUnits ) ), 3 );
1013 return result;
1014}
DistanceUnit
Units of distance.
Definition qgis.h:5170
@ Meters
Meters.
Definition qgis.h:5171
AreaUnit
Units of area.
Definition qgis.h:5247
@ SquareMeters
Square meters.
Definition qgis.h:5248
@ Line
Lines.
Definition qgis.h:381
static QString geoNone()
Constant that holds the string representation for "No ellipse/No CRS".
Definition qgis.h:6707
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2766
Abstract base class for all geometries.
bool isMeasure() const
Returns true if the geometry contains m values.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
virtual double perimeter() const
Returns the planar, 2-dimensional perimeter of the geometry.
virtual double length() const
Returns the planar, 2-dimensional length of the geometry.
virtual int dimension() const =0
Returns the inherent dimension of the geometry.
virtual double area() const
Returns the planar, 2-dimensional area of the geometry.
Represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
Custom exception class for Coordinate Reference System related exceptions.
Curve polygon geometry type.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
virtual QgsPolygon * surfaceToPolygon() const
Gets a polygon representation of this surface.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
virtual void points(QgsPointSequence &pt) const =0
Returns a list of points within the curve.
virtual QgsLineString * curveToLine(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const =0
Returns a new line string geometry corresponding to a segmentized approximation of the curve.
QgsDistanceArea & operator=(const QgsDistanceArea &other)
double latitudeGeodesicCrossesAntimeridian(const QgsPointXY &p1, const QgsPointXY &p2, double &fractionAlongLine) const
Calculates the latitude at which the geodesic line joining p1 and p2 crosses the antimeridian (longit...
static QString formatDistance(double distance, int decimals, Qgis::DistanceUnit unit, bool keepBaseUnit=false)
Returns an distance formatted as a friendly string.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source spatial reference system.
double measureArea(const QgsGeometry &geometry) const
Measures the area of a geometry.
double convertLengthMeasurement(double length, Qgis::DistanceUnit toUnits) const
Takes a length measurement calculated by this QgsDistanceArea object and converts it to a different d...
QVector< QVector< QgsPointXY > > geodesicLine(const QgsPointXY &p1, const QgsPointXY &p2, double interval, bool breakLine=false) const
Calculates the geodesic line between p1 and p2, which represents the shortest path on the ellipsoid b...
double measurePerimeter(const QgsGeometry &geometry) const
Measures the perimeter of a polygon geometry.
double measureLength(const QgsGeometry &geometry) const
Measures the length of a geometry.
double bearing(const QgsPointXY &p1, const QgsPointXY &p2) const
Computes the bearing (in radians) between two points.
QString ellipsoid() const
Returns ellipsoid's acronym.
double measureLine(const QVector< QgsPointXY > &points) const
Measures the length of a line with multiple segments.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
QgsGeometry splitGeometryAtAntimeridian(const QgsGeometry &geometry) const
Splits a (Multi)LineString geometry at the antimeridian (longitude +/- 180 degrees).
Qgis::DistanceUnit lengthUnits() const
Returns the units of distance for length calculations made by this object.
double measurePolygon(const QVector< QgsPointXY > &points) const
Measures the area of the polygon described by a set of points.
double measureLineProjected(const QgsPointXY &p1, double distance=1, double azimuth=M_PI_2, QgsPointXY *projectedPoint=nullptr) const
Calculates the distance from one point with distance in meters and azimuth (direction) When the sourc...
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
QgsPointXY computeSpheroidProject(const QgsPointXY &p1, double distance=1, double azimuth=M_PI_2) const
Given a location, an azimuth and a distance, computes the location of the projected point.
bool willUseEllipsoid() const
Returns whether calculations will use the ellipsoid.
double convertAreaMeasurement(double area, Qgis::AreaUnit toUnits) const
Takes an area measurement calculated by this QgsDistanceArea object and converts it to a different ar...
static QString formatArea(double area, int decimals, Qgis::AreaUnit unit, bool keepBaseUnit=false)
Returns an area formatted as a friendly string.
Qgis::AreaUnit areaUnits() const
Returns the units of area for areal calculations made by this object.
static EllipsoidParameters ellipsoidParameters(const QString &ellipsoid)
Returns the parameters for the specified ellipsoid.
int numGeometries() const
Returns the number of geometries within the collection.
const QgsAbstractGeometry * geometryN(int n) const
Returns a const reference to a geometry from within the collection.
A geometry is the spatial representation of a feature.
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
void convertToStraightSegment(double tolerance=M_PI/180., QgsAbstractGeometry::SegmentationToleranceType toleranceType=QgsAbstractGeometry::MaximumAngle)
Converts the geometry to straight line segments, if it is a curved geometry type.
QgsAbstractGeometry::const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary part after the last part of the geometry.
static void convertPointList(const QVector< QgsPointXY > &input, QgsPointSequence &output)
Upgrades a point list from QgsPointXY to QgsPoint.
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.).
Line string geometry type, with support for z-dimension and m-values.
bool isEmpty() const override
Returns true if the geometry is empty.
int numPoints() const override
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
QgsLineString * clone() const override
Clones the geometry by performing a deep copy.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
Multi surface geometry collection.
Represents a 2D point.
Definition qgspointxy.h:62
QgsPointXY project(double distance, double bearing) const
Returns a new point which corresponds to this point projected by a specified distance in a specified ...
QString toString(int precision=-1) const
Returns a string representation of the point (x, y) with a preset precision.
double distance(double x, double y) const
Returns the distance between this point and a specified x, y coordinate.
Definition qgspointxy.h:209
QString asWkt() const
Returns the well known text representation for the point (e.g.
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
void setX(double x)
Sets the x value of the point.
Definition qgspointxy.h:122
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
void setY(double y)
Sets the point's y-coordinate.
Definition qgspoint.h:387
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
Definition qgspoint.cpp:599
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
Definition qgspoint.cpp:588
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:376
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
double m
Definition qgspoint.h:59
double y
Definition qgspoint.h:57
Polygon geometry type.
Definition qgspolygon.h:37
Surface geometry type.
Definition qgssurface.h:34
static Q_INVOKABLE QString toString(Qgis::DistanceUnit unit)
Returns a translated string representing a distance unit.
static Q_INVOKABLE QString formatArea(double area, int decimals, Qgis::AreaUnit unit, bool keepBaseUnit=false)
Returns an area formatted as a friendly string.
static Q_INVOKABLE double fromUnitToUnitFactor(Qgis::DistanceUnit fromUnit, Qgis::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
static Q_INVOKABLE QString formatDistance(double distance, int decimals, Qgis::DistanceUnit unit, bool keepBaseUnit=false)
Returns an distance formatted as a friendly string.
static Q_INVOKABLE Qgis::AreaUnit distanceToAreaUnit(Qgis::DistanceUnit distanceUnit)
Converts a distance unit to its corresponding area unit, e.g., meters to square meters.
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static Q_INVOKABLE bool isCurvedType(Qgis::WkbType type)
Returns true if the WKB type is a curved type or can contain curved geometries.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6893
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPoint > QgsPointSequence
#define RAD2DEG(r)
#define DEG2RAD(x)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
Contains parameters for an ellipsoid.
double semiMajor
Semi-major axis, in meters.
bool valid
Whether ellipsoid parameters are valid.
double semiMinor
Semi-minor axis, in meters.
QgsCoordinateReferenceSystem crs
Associated coordinate reference system.
double inverseFlattening
Inverse flattening.
bool useCustomParameters
Whether custom parameters alone should be used (semiMajor/semiMinor only).