QGIS API Documentation 3.99.0-Master (d270888f95f)
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}
83
85{
86 if ( &other == this )
87 return *this;
88
89 //****** IMPORTANT! editing this? make sure you update the move assignment operator too! *****
90 mCoordTransform = other.mCoordTransform;
91 mEllipsoid = other.mEllipsoid;
92 mSemiMajor = other.mSemiMajor;
93 mSemiMinor = other.mSemiMinor;
94 mInvFlattening = other.mInvFlattening;
95 computeAreaInit();
96 //****** IMPORTANT! editing this? make sure you update the move assignment operator too! *****
97 return *this;
98}
99
101{
102 if ( &other == this )
103 return *this;
104
105 mCoordTransform = other.mCoordTransform;
106 mEllipsoid = other.mEllipsoid;
107 mSemiMajor = other.mSemiMajor;
108 mSemiMinor = other.mSemiMinor;
109 mInvFlattening = other.mInvFlattening;
110 mGeod = std::move( other.mGeod );
111 return *this;
112}
113
115{
116 return mEllipsoid != Qgis::geoNone();
117}
118
120{
121 mCoordTransform.setContext( context );
122 mCoordTransform.setSourceCrs( srcCRS );
123}
124
126{
127 // Shortcut if ellipsoid is none.
128 if ( ellipsoid == Qgis::geoNone() )
129 {
130 mEllipsoid = Qgis::geoNone();
131 mGeod.reset();
132 return true;
133 }
134
136 if ( !params.valid )
137 {
138 mGeod.reset();
139 return false;
140 }
141 else
142 {
143 mEllipsoid = ellipsoid;
144 setFromParams( params );
145 return true;
146 }
147}
148
149// Inverse flattening is calculated with invf = a/(a-b)
150// Also, b = a-(a/invf)
151bool QgsDistanceArea::setEllipsoid( double semiMajor, double semiMinor )
152{
153 mEllipsoid = u"PARAMETER:%1:%2"_s.arg( qgsDoubleToString( semiMajor ), qgsDoubleToString( semiMinor ) );
154 mSemiMajor = semiMajor;
155 mSemiMinor = semiMinor;
156 mInvFlattening = mSemiMajor / ( mSemiMajor - mSemiMinor );
157
158 computeAreaInit();
159
160 return true;
161}
162
163double QgsDistanceArea::measure( const QgsAbstractGeometry *geomV2, MeasureType type ) const
164{
165 if ( !geomV2 )
166 {
167 return 0.0;
168 }
169
170 const int geomDimension = geomV2->dimension();
171 if ( geomDimension <= 0 )
172 {
173 return 0.0;
174 }
175
176 MeasureType measureType = type;
177 if ( measureType == Default )
178 {
179 measureType = ( geomDimension == 1 ? Length : Area );
180 }
181
182 if ( !willUseEllipsoid() )
183 {
184 //no transform required
185 if ( measureType == Length )
186 {
187 return geomV2->length();
188 }
189 else
190 {
191 return geomV2->area();
192 }
193 }
194 else
195 {
196 //multigeom is sum of measured parts
197 const QgsGeometryCollection *collection = qgsgeometry_cast<const QgsGeometryCollection *>( geomV2 );
198 if ( collection )
199 {
200 double sum = 0;
201 for ( int i = 0; i < collection->numGeometries(); ++i )
202 {
203 sum += measure( collection->geometryN( i ), measureType );
204 }
205 return sum;
206 }
207
208 if ( measureType == Length )
209 {
210 const QgsCurve *curve = qgsgeometry_cast<const QgsCurve *>( geomV2 );
211 if ( !curve )
212 {
213 return 0.0;
214 }
215
216 QgsLineString *lineString = curve->curveToLine();
217 const double length = measureLine( lineString );
218 delete lineString;
219 return length;
220 }
221 else
222 {
223 const QgsSurface *surface = qgsgeometry_cast<const QgsSurface *>( geomV2 );
224 if ( !surface )
225 return 0.0;
226
227 double area = 0;
228 const QgsCurvePolygon *curvePolygon = qgsgeometry_cast<const QgsCurvePolygon *>( surface );
229 if ( curvePolygon )
230 {
231 QgsPolygon *polygon = curvePolygon->surfaceToPolygon();
232
233 const QgsCurve *outerRing = polygon->exteriorRing();
234 area += measurePolygon( outerRing );
235
236 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
237 {
238 const QgsCurve *innerRing = polygon->interiorRing( i );
239 area -= measurePolygon( innerRing );
240 }
241 delete polygon;
242 }
243 return area;
244 }
245 }
246}
247
248double QgsDistanceArea::measureArea( const QgsGeometry &geometry ) const
249{
250 if ( geometry.isNull() )
251 return 0.0;
252
253 const QgsAbstractGeometry *geomV2 = geometry.constGet();
254 return measure( geomV2, Area );
255}
256
257double QgsDistanceArea::measureLength( const QgsGeometry &geometry ) const
258{
259 if ( geometry.isNull() )
260 return 0.0;
261
262 const QgsAbstractGeometry *geomV2 = geometry.constGet();
263 return measure( geomV2, Length );
264}
265
266double QgsDistanceArea::measurePerimeter( const QgsGeometry &geometry ) const
267{
268 if ( geometry.isNull() )
269 return 0.0;
270
271 const QgsAbstractGeometry *geomV2 = geometry.constGet();
272 if ( !geomV2 || geomV2->dimension() < 2 )
273 {
274 return 0.0;
275 }
276
277 if ( !willUseEllipsoid() )
278 {
279 return geomV2->perimeter();
280 }
281
282 //create list with (single) surfaces
283 QVector< const QgsSurface * > surfaces;
284 const QgsSurface *surf = qgsgeometry_cast<const QgsSurface *>( geomV2 );
285 if ( surf )
286 {
287 surfaces.append( surf );
288 }
290 if ( multiSurf )
291 {
292 surfaces.reserve( ( surf ? 1 : 0 ) + multiSurf->numGeometries() );
293 for ( int i = 0; i < multiSurf->numGeometries(); ++i )
294 {
295 surfaces.append( static_cast<const QgsSurface *>( multiSurf->geometryN( i ) ) );
296 }
297 }
298
299 double length = 0;
300 QVector<const QgsSurface *>::const_iterator surfaceIt = surfaces.constBegin();
301 for ( ; surfaceIt != surfaces.constEnd(); ++surfaceIt )
302 {
303 if ( !*surfaceIt )
304 {
305 continue;
306 }
307
308 const QgsCurvePolygon *curvePolygon = qgsgeometry_cast<const QgsCurvePolygon *>( *surfaceIt );
309 if ( curvePolygon )
310 {
311 QgsPolygon *poly = curvePolygon->surfaceToPolygon();
312 const QgsCurve *outerRing = poly->exteriorRing();
313 if ( outerRing )
314 {
315 length += measure( outerRing );
316 }
317 const int nInnerRings = poly->numInteriorRings();
318 for ( int i = 0; i < nInnerRings; ++i )
319 {
320 length += measure( poly->interiorRing( i ) );
321 }
322 delete poly;
323 }
324 }
325 return length;
326}
327
328double QgsDistanceArea::measureLine( const QgsCurve *curve ) const
329{
330 if ( !curve )
331 {
332 return 0.0;
333 }
334
335 QgsPointSequence linePointsV2;
336 QVector<QgsPointXY> linePoints;
337 curve->points( linePointsV2 );
338 QgsGeometry::convertPointList( linePointsV2, linePoints );
339 return measureLine( linePoints );
340}
341
342double QgsDistanceArea::measureLine( const QVector<QgsPointXY> &points ) const
343{
344 if ( points.size() < 2 )
345 return 0;
346
347 double total = 0;
348 QgsPointXY p1, p2;
349
350 if ( willUseEllipsoid() )
351 {
352 if ( !mGeod )
353 computeAreaInit();
354 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::measureLine()", "Error creating geod_geodesic object" );
355 if ( !mGeod )
356 return 0;
357 }
358
359 if ( willUseEllipsoid() )
360 p1 = mCoordTransform.transform( points[0] );
361 else
362 p1 = points[0];
363
364 for ( QVector<QgsPointXY>::const_iterator i = points.constBegin(); i != points.constEnd(); ++i )
365 {
366 if ( willUseEllipsoid() )
367 {
368 p2 = mCoordTransform.transform( *i );
369
370 double distance = 0;
371 double azimuth1 = 0;
372 double azimuth2 = 0;
373 geod_inverse( mGeod.get(), p1.y(), p1.x(), p2.y(), p2.x(), &distance, &azimuth1, &azimuth2 );
374 total += distance;
375 }
376 else
377 {
378 p2 = *i;
379 total += measureLine( p1, p2 );
380 }
381
382 p1 = p2;
383 }
384
385 return total;
386}
387
388double QgsDistanceArea::measureLine( const QgsPointXY &p1, const QgsPointXY &p2 ) const
389{
390 double result;
391
392 if ( willUseEllipsoid() )
393 {
394 if ( !mGeod )
395 computeAreaInit();
396 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::measureLine()", "Error creating geod_geodesic object" );
397 if ( !mGeod )
398 return 0;
399 }
400
401 QgsPointXY pp1 = p1, pp2 = p2;
402
403 QgsDebugMsgLevel( u"Measuring from %1 to %2"_s.arg( p1.toString( 4 ), p2.toString( 4 ) ), 3 );
404 if ( willUseEllipsoid() )
405 {
406 QgsDebugMsgLevel( u"Ellipsoidal calculations is enabled, using ellipsoid %1"_s.arg( mEllipsoid ), 4 );
407 QgsDebugMsgLevel( u"From proj4 : %1"_s.arg( mCoordTransform.sourceCrs().toProj() ), 4 );
408 QgsDebugMsgLevel( u"To proj4 : %1"_s.arg( mCoordTransform.destinationCrs().toProj() ), 4 );
409 pp1 = mCoordTransform.transform( p1 );
410 pp2 = mCoordTransform.transform( p2 );
411 QgsDebugMsgLevel( u"New points are %1 and %2, calculating..."_s.arg( pp1.toString( 4 ), pp2.toString( 4 ) ), 4 );
412
413 double azimuth1 = 0;
414 double azimuth2 = 0;
415 geod_inverse( mGeod.get(), pp1.y(), pp1.x(), pp2.y(), pp2.x(), &result, &azimuth1, &azimuth2 );
416 }
417 else
418 {
419 QgsDebugMsgLevel( u"Cartesian calculation on canvas coordinates"_s, 4 );
420 result = p2.distance( p1 );
421 }
422
423 QgsDebugMsgLevel( u"The result was %1"_s.arg( result ), 3 );
424 return result;
425}
426
427double QgsDistanceArea::measureLineProjected( const QgsPointXY &p1, double distance, double azimuth, QgsPointXY *projectedPoint ) const
428{
429 double result = 0.0;
430 QgsPointXY p2;
431 if ( mCoordTransform.sourceCrs().isGeographic() && willUseEllipsoid() )
432 {
433 p2 = computeSpheroidProject( p1, distance, azimuth );
434 result = p1.distance( p2 );
435 }
436 else // Cartesian coordinates
437 {
438 result = distance; // Avoid rounding errors when using meters [return as sent]
439 if ( sourceCrs().mapUnits() != Qgis::DistanceUnit::Meters )
440 {
441 distance = ( distance * QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Meters, sourceCrs().mapUnits() ) );
442 result = p1.distance( p2 );
443 }
444 p2 = p1.project( distance, azimuth );
445 }
446 QgsDebugMsgLevel( 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( QString::number( distance, 'f', 7 ),
449 QString::number( result, 'f', 7 ),
450 mCoordTransform.sourceCrs().isGeographic() ? u"Geographic"_s : u"Cartesian"_s,
451 QgsUnitTypes::toString( sourceCrs().mapUnits() ) )
452 .arg( azimuth )
453 .arg( p1.asWkt(),
454 p2.asWkt(),
455 sourceCrs().description(),
456 mEllipsoid )
457 .arg( sourceCrs().isGeographic() )
458 .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 ) ) ), 4 );
459 if ( projectedPoint )
460 {
461 *projectedPoint = QgsPointXY( p2 );
462 }
463 return result;
464}
465
467 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
793 prevLon = lon;
794 prevLat = lat;
795
796 try
797 {
798 currentPart << mCoordTransform.transform( QgsPointXY( lon, lat ), Qgis::TransformDirection::Reverse );
799 }
800 catch ( QgsCsException & )
801 {
802 QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point." ) );
803 }
804
805 if ( lastRun )
806 break;
807
808 d += interval;
809 if ( d >= totalDist )
810 lastRun = true;
811 }
812 res << currentPart;
813 return res;
814}
815
817{
818 return willUseEllipsoid() ? Qgis::DistanceUnit::Meters : mCoordTransform.sourceCrs().mapUnits();
819}
820
822{
824 QgsUnitTypes::distanceToAreaUnit( mCoordTransform.sourceCrs().mapUnits() );
825}
826
827double QgsDistanceArea::measurePolygon( const QgsCurve *curve ) const
828{
829 if ( !curve )
830 {
831 return 0.0;
832 }
833
834 QgsPointSequence linePointsV2;
835 curve->points( linePointsV2 );
836 QVector<QgsPointXY> linePoints;
837 QgsGeometry::convertPointList( linePointsV2, linePoints );
838 return measurePolygon( linePoints );
839}
840
841
842double QgsDistanceArea::measurePolygon( const QVector<QgsPointXY> &points ) const
843{
844 if ( willUseEllipsoid() )
845 {
846 QVector<QgsPointXY> pts;
847 for ( QVector<QgsPointXY>::const_iterator i = points.constBegin(); i != points.constEnd(); ++i )
848 {
849 pts.append( mCoordTransform.transform( *i ) );
850 }
851 return computePolygonArea( pts );
852 }
853 else
854 {
855 return computePolygonArea( points );
856 }
857}
858
859
860double QgsDistanceArea::bearing( const QgsPointXY &p1, const QgsPointXY &p2 ) const
861{
862 QgsPointXY pp1 = p1, pp2 = p2;
863 double bearing;
864
865 if ( willUseEllipsoid() )
866 {
867 pp1 = mCoordTransform.transform( p1 );
868 pp2 = mCoordTransform.transform( p2 );
869
870 if ( !mGeod )
871 computeAreaInit();
872 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::bearing()", "Error creating geod_geodesic object" );
873 if ( !mGeod )
874 return 0;
875
876 double distance = 0;
877 double azimuth1 = 0;
878 double azimuth2 = 0;
879 geod_inverse( mGeod.get(), pp1.y(), pp1.x(), pp2.y(), pp2.x(), &distance, &azimuth1, &azimuth2 );
880
881 bearing = DEG2RAD( azimuth1 );
882 }
883 else //compute simple planar azimuth
884 {
885 const double dx = p2.x() - p1.x();
886 const double dy = p2.y() - p1.y();
887 // Note: the prototype of std::atan2 is (y,x), to return the angle of
888 // vector (x,y) from the horizontal axis in counter-clock-wise orientation.
889 // But a bearing is expressed in clock-wise order from the vertical axis, so
890 // M_PI / 2 - std::atan2( dy, dx ) == std::atan2( dx, dy )
891 bearing = std::atan2( dx, dy );
892 }
893
894 return bearing;
895}
896
897void QgsDistanceArea::computeAreaInit() const
898{
899 //don't try to perform calculations if no ellipsoid
900 if ( mEllipsoid == Qgis::geoNone() )
901 {
902 mGeod.reset();
903 return;
904 }
905
906 mGeod = std::make_unique<geod_geodesic>( );
907 geod_init( mGeod.get(), mSemiMajor, 1 / mInvFlattening );
908}
909
910void QgsDistanceArea::setFromParams( const QgsEllipsoidUtils::EllipsoidParameters &params )
911{
912 if ( params.useCustomParameters )
913 {
914 setEllipsoid( params.semiMajor, params.semiMinor );
915 mCoordTransform.setDestinationCrs( params.crs );
916 }
917 else
918 {
919 mSemiMajor = params.semiMajor;
920 mSemiMinor = params.semiMinor;
921 mInvFlattening = params.inverseFlattening;
922 mCoordTransform.setDestinationCrs( params.crs );
923 computeAreaInit();
924 }
925}
926
927double QgsDistanceArea::computePolygonArea( const QVector<QgsPointXY> &points ) const
928{
929 if ( points.isEmpty() )
930 {
931 return 0;
932 }
933
934 QgsDebugMsgLevel( "Ellipsoid: " + mEllipsoid, 3 );
935 if ( !willUseEllipsoid() )
936 {
937 return computePolygonFlatArea( points );
938 }
939
940 if ( !mGeod )
941 computeAreaInit();
942 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::computePolygonArea()", "Error creating geod_geodesic object" );
943 if ( !mGeod )
944 return 0;
945
946 struct geod_polygon p;
947 geod_polygon_init( &p, 0 );
948
949 const bool isClosed = points.constFirst() == points.constLast();
950
951 /* GeographicLib does not need a closed ring,
952 * see example for geod_polygonarea() in geodesic.h */
953 /* add points in reverse order */
954 int i = points.size();
955 while ( ( isClosed && --i ) || ( !isClosed && --i >= 0 ) )
956 geod_polygon_addpoint( mGeod.get(), &p, points.at( i ).y(), points.at( i ).x() );
957
958 double area = 0;
959 double perimeter = 0;
960 geod_polygon_compute( mGeod.get(), &p, 0, 1, &area, &perimeter );
961
962 return std::fabs( area );
963}
964
965double QgsDistanceArea::computePolygonFlatArea( const QVector<QgsPointXY> &points ) const
966{
967 // Normal plane area calculations.
968 double area = 0.0;
969 int i, size;
970
971 size = points.size();
972
973 // QgsDebugMsgLevel("New area calc, nr of points: " + QString::number(size), 2);
974 for ( i = 0; i < size; i++ )
975 {
976 // QgsDebugMsgLevel("Area from point: " + (points[i]).toString(2), 2);
977 // Using '% size', so that we always end with the starting point
978 // and thus close the polygon.
979 area = area + points[i].x() * points[( i + 1 ) % size].y() - points[( i + 1 ) % size].x() * points[i].y();
980 }
981 // QgsDebugMsgLevel("Area from point: " + (points[i % size]).toString(2), 2);
982 area = area / 2.0;
983 return std::fabs( area ); // All areas are positive!
984}
985
986QString QgsDistanceArea::formatDistance( double distance, int decimals, Qgis::DistanceUnit unit, bool keepBaseUnit )
987{
988 return QgsUnitTypes::formatDistance( distance, decimals, unit, keepBaseUnit );
989}
990
991QString QgsDistanceArea::formatArea( double area, int decimals, Qgis::AreaUnit unit, bool keepBaseUnit )
992{
993 return QgsUnitTypes::formatArea( area, decimals, unit, keepBaseUnit );
994}
995
997{
998 // get the conversion factor between the specified units
999 const Qgis::DistanceUnit measureUnits = lengthUnits();
1000 const double factorUnits = QgsUnitTypes::fromUnitToUnitFactor( measureUnits, toUnits );
1001
1002 const double result = length * factorUnits;
1003 QgsDebugMsgLevel( u"Converted length of %1 %2 to %3 %4"_s.arg( length )
1004 .arg( QgsUnitTypes::toString( measureUnits ) )
1005 .arg( result )
1006 .arg( QgsUnitTypes::toString( toUnits ) ), 3 );
1007 return result;
1008}
1009
1011{
1012 // get the conversion factor between the specified units
1013 const Qgis::AreaUnit measureUnits = areaUnits();
1014 const double factorUnits = QgsUnitTypes::fromUnitToUnitFactor( measureUnits, toUnits );
1015
1016 const double result = area * factorUnits;
1017 QgsDebugMsgLevel( u"Converted area of %1 %2 to %3 %4"_s.arg( area )
1018 .arg( QgsUnitTypes::toString( measureUnits ) )
1019 .arg( result )
1020 .arg( QgsUnitTypes::toString( toUnits ) ), 3 );
1021 return result;
1022}
DistanceUnit
Units of distance.
Definition qgis.h:5085
@ Meters
Meters.
Definition qgis.h:5086
AreaUnit
Units of area.
Definition qgis.h:5162
@ SquareMeters
Square meters.
Definition qgis.h:5163
@ Line
Lines.
Definition qgis.h:367
static QString geoNone()
Constant that holds the string representation for "No ellipse/No CRS".
Definition qgis.h:6615
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2731
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())
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:208
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:121
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:341
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
Definition qgspoint.cpp:575
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
Definition qgspoint.cpp:564
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:330
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:6817
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).