QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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
42#define DEG2RAD(x) ((x)*M_PI/180)
43#define RAD2DEG(r) (180.0 * (r) / M_PI)
44#define POW2(x) ((x)*(x))
45
47{
48 // init with default settings
49 mSemiMajor = -1.0;
50 mSemiMinor = -1.0;
51 mInvFlattening = -1.0;
52 const QgsCoordinateTransformContext context; // this is ok - by default we have a source/dest of WGS84, so no reprojection takes place
53 setSourceCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), context ); // WGS 84
55}
56
58
60//****** IMPORTANT! editing this? make sure you update the move constructor too! *****
61 : mCoordTransform( other.mCoordTransform )
62 , mEllipsoid( other.mEllipsoid )
63 , mSemiMajor( other.mSemiMajor )
64 , mSemiMinor( other.mSemiMinor )
65 , mInvFlattening( other.mInvFlattening )
66//****** IMPORTANT! editing this? make sure you update the move constructor too! *****
67{
68 computeAreaInit();
69}
70
71
73 : mCoordTransform( std::move( other.mCoordTransform ) )
74 , mEllipsoid( std::move( other.mEllipsoid ) )
75 , mSemiMajor( other.mSemiMajor )
76 , mSemiMinor( other.mSemiMinor )
77 , mInvFlattening( other.mInvFlattening )
78 , mGeod( std::move( other.mGeod ) )
79{
80}
81
83{
84 if ( &other == this )
85 return *this;
86
87 //****** IMPORTANT! editing this? make sure you update the move assignment operator too! *****
88 mCoordTransform = other.mCoordTransform;
89 mEllipsoid = other.mEllipsoid;
90 mSemiMajor = other.mSemiMajor;
91 mSemiMinor = other.mSemiMinor;
92 mInvFlattening = other.mInvFlattening;
93 computeAreaInit();
94 //****** IMPORTANT! editing this? make sure you update the move assignment operator too! *****
95 return *this;
96}
97
99{
100 if ( &other == this )
101 return *this;
102
103 mCoordTransform = other.mCoordTransform;
104 mEllipsoid = other.mEllipsoid;
105 mSemiMajor = other.mSemiMajor;
106 mSemiMinor = other.mSemiMinor;
107 mInvFlattening = other.mInvFlattening;
108 mGeod = std::move( other.mGeod );
109 return *this;
110}
111
113{
114 return mEllipsoid != Qgis::geoNone();
115}
116
118{
119 mCoordTransform.setContext( context );
120 mCoordTransform.setSourceCrs( srcCRS );
121}
122
124{
125 // Shortcut if ellipsoid is none.
126 if ( ellipsoid == Qgis::geoNone() )
127 {
128 mEllipsoid = Qgis::geoNone();
129 mGeod.reset();
130 return true;
131 }
132
134 if ( !params.valid )
135 {
136 mGeod.reset();
137 return false;
138 }
139 else
140 {
141 mEllipsoid = ellipsoid;
142 setFromParams( params );
143 return true;
144 }
145}
146
147// Inverse flattening is calculated with invf = a/(a-b)
148// Also, b = a-(a/invf)
149bool QgsDistanceArea::setEllipsoid( double semiMajor, double semiMinor )
150{
151 mEllipsoid = QStringLiteral( "PARAMETER:%1:%2" ).arg( qgsDoubleToString( semiMajor ), qgsDoubleToString( semiMinor ) );
152 mSemiMajor = semiMajor;
153 mSemiMinor = semiMinor;
154 mInvFlattening = mSemiMajor / ( mSemiMajor - mSemiMinor );
155
156 computeAreaInit();
157
158 return true;
159}
160
161double QgsDistanceArea::measure( const QgsAbstractGeometry *geomV2, MeasureType type ) const
162{
163 if ( !geomV2 )
164 {
165 return 0.0;
166 }
167
168 const int geomDimension = geomV2->dimension();
169 if ( geomDimension <= 0 )
170 {
171 return 0.0;
172 }
173
174 MeasureType measureType = type;
175 if ( measureType == Default )
176 {
177 measureType = ( geomDimension == 1 ? Length : Area );
178 }
179
180 if ( !willUseEllipsoid() )
181 {
182 //no transform required
183 if ( measureType == Length )
184 {
185 return geomV2->length();
186 }
187 else
188 {
189 return geomV2->area();
190 }
191 }
192 else
193 {
194 //multigeom is sum of measured parts
195 const QgsGeometryCollection *collection = qgsgeometry_cast<const QgsGeometryCollection *>( geomV2 );
196 if ( collection )
197 {
198 double sum = 0;
199 for ( int i = 0; i < collection->numGeometries(); ++i )
200 {
201 sum += measure( collection->geometryN( i ), measureType );
202 }
203 return sum;
204 }
205
206 if ( measureType == Length )
207 {
208 const QgsCurve *curve = qgsgeometry_cast<const QgsCurve *>( geomV2 );
209 if ( !curve )
210 {
211 return 0.0;
212 }
213
214 QgsLineString *lineString = curve->curveToLine();
215 const double length = measureLine( lineString );
216 delete lineString;
217 return length;
218 }
219 else
220 {
221 const QgsSurface *surface = qgsgeometry_cast<const QgsSurface *>( geomV2 );
222 if ( !surface )
223 return 0.0;
224
225 double area = 0;
226 const QgsCurvePolygon *curvePolygon = qgsgeometry_cast<const QgsCurvePolygon *>( surface );
227 if ( curvePolygon )
228 {
229 QgsPolygon *polygon = curvePolygon->surfaceToPolygon();
230
231 const QgsCurve *outerRing = polygon->exteriorRing();
232 area += measurePolygon( outerRing );
233
234 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
235 {
236 const QgsCurve *innerRing = polygon->interiorRing( i );
237 area -= measurePolygon( innerRing );
238 }
239 delete polygon;
240 }
241 return area;
242 }
243 }
244}
245
246double QgsDistanceArea::measureArea( const QgsGeometry &geometry ) const
247{
248 if ( geometry.isNull() )
249 return 0.0;
250
251 const QgsAbstractGeometry *geomV2 = geometry.constGet();
252 return measure( geomV2, Area );
253}
254
255double QgsDistanceArea::measureLength( const QgsGeometry &geometry ) const
256{
257 if ( geometry.isNull() )
258 return 0.0;
259
260 const QgsAbstractGeometry *geomV2 = geometry.constGet();
261 return measure( geomV2, Length );
262}
263
264double QgsDistanceArea::measurePerimeter( const QgsGeometry &geometry ) const
265{
266 if ( geometry.isNull() )
267 return 0.0;
268
269 const QgsAbstractGeometry *geomV2 = geometry.constGet();
270 if ( !geomV2 || geomV2->dimension() < 2 )
271 {
272 return 0.0;
273 }
274
275 if ( !willUseEllipsoid() )
276 {
277 return geomV2->perimeter();
278 }
279
280 //create list with (single) surfaces
281 QVector< const QgsSurface * > surfaces;
282 const QgsSurface *surf = qgsgeometry_cast<const QgsSurface *>( geomV2 );
283 if ( surf )
284 {
285 surfaces.append( surf );
286 }
288 if ( multiSurf )
289 {
290 surfaces.reserve( ( surf ? 1 : 0 ) + multiSurf->numGeometries() );
291 for ( int i = 0; i < multiSurf->numGeometries(); ++i )
292 {
293 surfaces.append( static_cast<const QgsSurface *>( multiSurf->geometryN( i ) ) );
294 }
295 }
296
297 double length = 0;
298 QVector<const QgsSurface *>::const_iterator surfaceIt = surfaces.constBegin();
299 for ( ; surfaceIt != surfaces.constEnd(); ++surfaceIt )
300 {
301 if ( !*surfaceIt )
302 {
303 continue;
304 }
305
306 const QgsCurvePolygon *curvePolygon = qgsgeometry_cast<const QgsCurvePolygon *>( *surfaceIt );
307 if ( curvePolygon )
308 {
309 QgsPolygon *poly = curvePolygon->surfaceToPolygon();
310 const QgsCurve *outerRing = poly->exteriorRing();
311 if ( outerRing )
312 {
313 length += measure( outerRing );
314 }
315 const int nInnerRings = poly->numInteriorRings();
316 for ( int i = 0; i < nInnerRings; ++i )
317 {
318 length += measure( poly->interiorRing( i ) );
319 }
320 delete poly;
321 }
322 }
323 return length;
324}
325
326double QgsDistanceArea::measureLine( const QgsCurve *curve ) const
327{
328 if ( !curve )
329 {
330 return 0.0;
331 }
332
333 QgsPointSequence linePointsV2;
334 QVector<QgsPointXY> linePoints;
335 curve->points( linePointsV2 );
336 QgsGeometry::convertPointList( linePointsV2, linePoints );
337 return measureLine( linePoints );
338}
339
340double QgsDistanceArea::measureLine( const QVector<QgsPointXY> &points ) const
341{
342 if ( points.size() < 2 )
343 return 0;
344
345 double total = 0;
346 QgsPointXY p1, p2;
347
348 if ( willUseEllipsoid() )
349 {
350 if ( !mGeod )
351 computeAreaInit();
352 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::measureLine()", "Error creating geod_geodesic object" );
353 if ( !mGeod )
354 return 0;
355 }
356
357 if ( willUseEllipsoid() )
358 p1 = mCoordTransform.transform( points[0] );
359 else
360 p1 = points[0];
361
362 for ( QVector<QgsPointXY>::const_iterator i = points.constBegin(); i != points.constEnd(); ++i )
363 {
364 if ( willUseEllipsoid() )
365 {
366 p2 = mCoordTransform.transform( *i );
367
368 double distance = 0;
369 double azimuth1 = 0;
370 double azimuth2 = 0;
371 geod_inverse( mGeod.get(), p1.y(), p1.x(), p2.y(), p2.x(), &distance, &azimuth1, &azimuth2 );
372 total += distance;
373 }
374 else
375 {
376 p2 = *i;
377 total += measureLine( p1, p2 );
378 }
379
380 p1 = p2;
381 }
382
383 return total;
384}
385
386double QgsDistanceArea::measureLine( const QgsPointXY &p1, const QgsPointXY &p2 ) const
387{
388 double result;
389
390 if ( willUseEllipsoid() )
391 {
392 if ( !mGeod )
393 computeAreaInit();
394 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::measureLine()", "Error creating geod_geodesic object" );
395 if ( !mGeod )
396 return 0;
397 }
398
399 QgsPointXY pp1 = p1, pp2 = p2;
400
401 QgsDebugMsgLevel( QStringLiteral( "Measuring from %1 to %2" ).arg( p1.toString( 4 ), p2.toString( 4 ) ), 3 );
402 if ( willUseEllipsoid() )
403 {
404 QgsDebugMsgLevel( QStringLiteral( "Ellipsoidal calculations is enabled, using ellipsoid %1" ).arg( mEllipsoid ), 4 );
405 QgsDebugMsgLevel( QStringLiteral( "From proj4 : %1" ).arg( mCoordTransform.sourceCrs().toProj() ), 4 );
406 QgsDebugMsgLevel( QStringLiteral( "To proj4 : %1" ).arg( mCoordTransform.destinationCrs().toProj() ), 4 );
407 pp1 = mCoordTransform.transform( p1 );
408 pp2 = mCoordTransform.transform( p2 );
409 QgsDebugMsgLevel( QStringLiteral( "New points are %1 and %2, calculating..." ).arg( pp1.toString( 4 ), pp2.toString( 4 ) ), 4 );
410
411 double azimuth1 = 0;
412 double azimuth2 = 0;
413 geod_inverse( mGeod.get(), pp1.y(), pp1.x(), pp2.y(), pp2.x(), &result, &azimuth1, &azimuth2 );
414 }
415 else
416 {
417 QgsDebugMsgLevel( QStringLiteral( "Cartesian calculation on canvas coordinates" ), 4 );
418 result = p2.distance( p1 );
419 }
420
421 QgsDebugMsgLevel( QStringLiteral( "The result was %1" ).arg( result ), 3 );
422 return result;
423}
424
425double QgsDistanceArea::measureLineProjected( const QgsPointXY &p1, double distance, double azimuth, QgsPointXY *projectedPoint ) const
426{
427 double result = 0.0;
428 QgsPointXY p2;
429 if ( mCoordTransform.sourceCrs().isGeographic() && willUseEllipsoid() )
430 {
431 p2 = computeSpheroidProject( p1, distance, azimuth );
432 result = p1.distance( p2 );
433 }
434 else // Cartesian coordinates
435 {
436 result = distance; // Avoid rounding errors when using meters [return as sent]
437 if ( sourceCrs().mapUnits() != Qgis::DistanceUnit::Meters )
438 {
439 distance = ( distance * QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Meters, sourceCrs().mapUnits() ) );
440 result = p1.distance( p2 );
441 }
442 p2 = p1.project( distance, azimuth );
443 }
444 QgsDebugMsgLevel( QStringLiteral( "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]" )
445 .arg( QString::number( distance, 'f', 7 ),
447 QString::number( result, 'f', 7 ),
448 mCoordTransform.sourceCrs().isGeographic() ? QStringLiteral( "Geographic" ) : QStringLiteral( "Cartesian" ),
449 QgsUnitTypes::toString( sourceCrs().mapUnits() ) )
450 .arg( azimuth )
451 .arg( p1.asWkt(),
452 p2.asWkt(),
453 sourceCrs().description(),
454 mEllipsoid )
455 .arg( sourceCrs().isGeographic() )
456 .arg( QStringLiteral( "SemiMajor[%1] SemiMinor[%2] InvFlattening[%3] " ).arg( QString::number( mSemiMajor, 'f', 7 ), QString::number( mSemiMinor, 'f', 7 ), QString::number( mInvFlattening, 'f', 7 ) ) ), 4 );
457 if ( projectedPoint )
458 {
459 *projectedPoint = QgsPointXY( p2 );
460 }
461 return result;
462}
463
465 const QgsPointXY &p1, double distance, double azimuth ) const
466{
467 if ( !mGeod )
468 computeAreaInit();
469 if ( !mGeod )
470 return QgsPointXY();
471
472 double lat2 = 0;
473 double lon2 = 0;
474 double azimuth2 = 0;
475 geod_direct( mGeod.get(), p1.y(), p1.x(), RAD2DEG( azimuth ), distance, &lat2, &lon2, &azimuth2 );
476
477 return QgsPointXY( lon2, lat2 );
478}
479
480double QgsDistanceArea::latitudeGeodesicCrossesAntimeridian( const QgsPointXY &pp1, const QgsPointXY &pp2, double &fractionAlongLine ) const
481{
482 QgsPointXY p1 = pp1;
483 QgsPointXY p2 = pp2;
484 if ( p1.x() < -120 )
485 p1.setX( p1.x() + 360 );
486 if ( p2.x() < -120 )
487 p2.setX( p2.x() + 360 );
488
489 // we need p2.x() > 180 and p1.x() < 180
490 double p1x = p1.x() < 180 ? p1.x() : p2.x();
491 double p1y = p1.x() < 180 ? p1.y() : p2.y();
492 double p2x = p1.x() < 180 ? p2.x() : p1.x();
493 double p2y = p1.x() < 180 ? p2.y() : p1.y();
494 // lat/lon are our candidate intersection position - we want this to get as close to 180 as possible
495 // the first candidate is p2
496 double lat = p2y;
497 double lon = p2x;
498
499 if ( mEllipsoid == Qgis::geoNone() )
500 {
501 fractionAlongLine = ( 180 - p1x ) / ( p2x - p1x );
502 if ( p1.x() >= 180 )
503 fractionAlongLine = 1 - fractionAlongLine;
504 return p1y + ( 180 - p1x ) / ( p2x - p1x ) * ( p2y - p1y );
505 }
506
507 if ( !mGeod )
508 computeAreaInit();
509 Q_ASSERT_X( static_cast<bool>( mGeod ), "QgsDistanceArea::latitudeGeodesicCrossesAntimeridian()", "Error creating geod_geodesic object" );
510 if ( !mGeod )
511 return 0;
512
513 geod_geodesicline line;
514 geod_inverseline( &line, mGeod.get(), p1y, p1x, p2y, p2x, GEOD_ALL );
515
516 const double totalDist = line.s13;
517 double intersectionDist = line.s13;
518
519 int iterations = 0;
520 double t = 0;
521 // iterate until our intersection candidate is within ~1 mm of the antimeridian (or too many iterations happened)
522 while ( std::fabs( lon - 180.0 ) > 0.00000001 && iterations < 100 )
523 {
524 if ( iterations > 0 && std::fabs( p2x - p1x ) > 5 )
525 {
526 // if we have too large a range of longitudes, we use a binary search to narrow the window -- this ensures we will converge
527 if ( lon < 180 )
528 {
529 p1x = lon;
530 p1y = lat;
531 }
532 else
533 {
534 p2x = lon;
535 p2y = lat;
536 }
537 QgsDebugMsgLevel( QStringLiteral( "Narrowed window to %1, %2 - %3, %4" ).arg( p1x ).arg( p1y ).arg( p2x ).arg( p2y ), 4 );
538
539 geod_inverseline( &line, mGeod.get(), p1y, p1x, p2y, p2x, GEOD_ALL );
540 intersectionDist = line.s13 * 0.5;
541 }
542 else
543 {
544 // we have a sufficiently narrow window -- use Newton's method
545 // adjust intersection distance by fraction of how close the previous candidate was to 180 degrees longitude -
546 // this helps us close in to the correct longitude quickly
547 intersectionDist *= ( 180.0 - p1x ) / ( lon - p1x );
548 }
549
550 // now work out the point on the geodesic this far from p1 - this becomes our new candidate for crossing the antimeridian
551
552 geod_position( &line, intersectionDist, &lat, &lon, &t );
553 // we don't want to wrap longitudes > 180 around)
554 if ( lon < 0 )
555 lon += 360;
556
557 iterations++;
558 QgsDebugMsgLevel( QStringLiteral( "After %1 iterations lon is %2, lat is %3, dist from p1: %4" ).arg( iterations ).arg( lon ).arg( lat ).arg( intersectionDist ), 4 );
559 }
560
561 fractionAlongLine = intersectionDist / totalDist;
562 if ( p1.x() >= 180 )
563 fractionAlongLine = 1 - fractionAlongLine;
564
565 // either converged on 180 longitude or hit too many iterations
566 return lat;
567}
568
570{
572 return geometry;
573
574 QgsGeometry g = geometry;
575 // TODO - avoid segmentization of curved geometries (if this is even possible!)
578
579 auto res = std::make_unique< QgsMultiLineString >();
580 for ( auto part = g.const_parts_begin(); part != g.const_parts_end(); ++part )
581 {
583 if ( !line )
584 continue;
585 if ( line->isEmpty() )
586 {
587 continue;
588 }
589
590 const std::unique_ptr< QgsLineString > l = std::make_unique< QgsLineString >();
591 try
592 {
593 double x = 0;
594 double y = 0;
595 double z = 0;
596 double m = 0;
597 QVector< QgsPoint > newPoints;
598 newPoints.reserve( line->numPoints() );
599 double prevLon = 0;
600 double prevLat = 0;
601 double lon = 0;
602 double lat = 0;
603 double prevZ = 0;
604 double prevM = 0;
605 for ( int i = 0; i < line->numPoints(); i++ )
606 {
607 QgsPoint p = line->pointN( i );
608 x = p.x();
609 if ( mCoordTransform.sourceCrs().isGeographic() )
610 {
611 x = std::fmod( x, 360.0 );
612 if ( x > 180 )
613 x -= 360;
614 p.setX( x );
615 }
616 y = p.y();
617 lon = x;
618 lat = y;
619 mCoordTransform.transformInPlace( lon, lat, z );
620
621 //test if we crossed the antimeridian in this segment
622 if ( i > 0 && ( ( prevLon < -120 && lon > 120 ) || ( prevLon > 120 && lon < -120 ) ) )
623 {
624 // we did!
625 // when crossing the antimeridian, we need to calculate the latitude
626 // at which the geodesic intersects the antimeridian
627 double fract = 0;
628 const double lat180 = latitudeGeodesicCrossesAntimeridian( QgsPointXY( prevLon, prevLat ), QgsPointXY( lon, lat ), fract );
629 if ( line->is3D() )
630 {
631 z = prevZ + ( p.z() - prevZ ) * fract;
632 }
633 if ( line->isMeasure() )
634 {
635 m = prevM + ( p.m() - prevM ) * fract;
636 }
637
638 QgsPointXY antiMeridianPoint;
639 if ( prevLon < -120 )
640 antiMeridianPoint = mCoordTransform.transform( QgsPointXY( -180, lat180 ), Qgis::TransformDirection::Reverse );
641 else
642 antiMeridianPoint = mCoordTransform.transform( QgsPointXY( 180, lat180 ), Qgis::TransformDirection::Reverse );
643
644 QgsPoint newPoint( antiMeridianPoint );
645 if ( line->is3D() )
646 newPoint.addZValue( z );
647 if ( line->isMeasure() )
648 newPoint.addMValue( m );
649
650 if ( std::isfinite( newPoint.x() ) && std::isfinite( newPoint.y() ) )
651 {
652 newPoints << newPoint;
653 }
654 res->addGeometry( new QgsLineString( newPoints ) );
655
656 newPoints.clear();
657 newPoints.reserve( line->numPoints() - i + 1 );
658
659 if ( lon < -120 )
660 antiMeridianPoint = mCoordTransform.transform( QgsPointXY( -180, lat180 ), Qgis::TransformDirection::Reverse );
661 else
662 antiMeridianPoint = mCoordTransform.transform( QgsPointXY( 180, lat180 ), Qgis::TransformDirection::Reverse );
663
664 if ( std::isfinite( antiMeridianPoint.x() ) && std::isfinite( antiMeridianPoint.y() ) )
665 {
666 // we want to keep the previously calculated z/m value for newPoint, if present. They're the same each
667 // of the antimeridian split
668 newPoint.setX( antiMeridianPoint.x() );
669 newPoint.setY( antiMeridianPoint.y() );
670 newPoints << newPoint;
671 }
672 }
673 newPoints << p;
674
675 prevLon = lon;
676 prevLat = lat;
677 if ( line->is3D() )
678 prevZ = p.z();
679 if ( line->isMeasure() )
680 prevM = p.m();
681 }
682 res->addGeometry( new QgsLineString( newPoints ) );
683 }
684 catch ( QgsCsException & )
685 {
686 QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform linestring. Unable to calculate break point." ) );
687 res->addGeometry( line->clone() );
688 break;
689 }
690 }
691
692 return QgsGeometry( std::move( res ) );
693}
694
695
696QVector< QVector<QgsPointXY> > QgsDistanceArea::geodesicLine( const QgsPointXY &p1, const QgsPointXY &p2, const double interval, const bool breakLine ) const
697{
698 if ( !willUseEllipsoid() )
699 {
700 return QVector< QVector< QgsPointXY > >() << ( QVector< QgsPointXY >() << p1 << p2 );
701 }
702
703 if ( !mGeod )
704 computeAreaInit();
705 if ( !mGeod )
706 return QVector< QVector< QgsPointXY > >();
707
708 QgsPointXY pp1, pp2;
709 try
710 {
711 pp1 = mCoordTransform.transform( p1 );
712 pp2 = mCoordTransform.transform( p2 );
713 }
714 catch ( QgsCsException & )
715 {
716 QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point. Unable to calculate geodesic line." ) );
717 return QVector< QVector< QgsPointXY > >();
718 }
719
720 geod_geodesicline line;
721 geod_inverseline( &line, mGeod.get(), pp1.y(), pp1.x(), pp2.y(), pp2.x(), GEOD_ALL );
722 const double totalDist = line.s13;
723
724 QVector< QVector< QgsPointXY > > res;
725 QVector< QgsPointXY > currentPart;
726 currentPart << p1;
727 double d = interval;
728 double prevLon = pp1.x();
729 double prevLat = pp1.y();
730 bool lastRun = false;
731 double t = 0;
732 while ( true )
733 {
734 double lat, lon;
735 if ( lastRun )
736 {
737 lat = pp2.y();
738 lon = pp2.x();
739 if ( lon > 180 )
740 lon -= 360;
741 }
742 else
743 {
744 geod_position( &line, d, &lat, &lon, &t );
745 }
746
747 if ( breakLine && ( ( prevLon < -120 && lon > 120 ) || ( prevLon > 120 && lon < -120 ) ) )
748 {
749 // when breaking the geodesic at the antimeridian, we need to calculate the latitude
750 // at which the geodesic intersects the antimeridian, and add points to both line segments at this latitude
751 // on the antimeridian.
752 double fraction;
753 const double lat180 = latitudeGeodesicCrossesAntimeridian( QgsPointXY( prevLon, prevLat ), QgsPointXY( lon, lat ), fraction );
754
755 try
756 {
757 QgsPointXY p;
758 if ( prevLon < -120 )
759 p = mCoordTransform.transform( QgsPointXY( -180, lat180 ), Qgis::TransformDirection::Reverse );
760 else
761 p = mCoordTransform.transform( QgsPointXY( 180, lat180 ), Qgis::TransformDirection::Reverse );
762
763 if ( std::isfinite( p.x() ) && std::isfinite( p.y() ) )
764 currentPart << p;
765 }
766 catch ( QgsCsException & )
767 {
768 QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point." ) );
769 }
770
771 res << currentPart;
772 currentPart.clear();
773 try
774 {
775 QgsPointXY p;
776 if ( lon < -120 )
777 p = mCoordTransform.transform( QgsPointXY( -180, lat180 ), Qgis::TransformDirection::Reverse );
778 else
779 p = mCoordTransform.transform( QgsPointXY( 180, lat180 ), Qgis::TransformDirection::Reverse );
780
781 if ( std::isfinite( p.x() ) && std::isfinite( p.y() ) )
782 currentPart << p;
783 }
784 catch ( QgsCsException & )
785 {
786 QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point." ) );
787 }
788
789 }
790
791 prevLon = lon;
792 prevLat = lat;
793
794 try
795 {
796 currentPart << mCoordTransform.transform( QgsPointXY( lon, lat ), Qgis::TransformDirection::Reverse );
797 }
798 catch ( QgsCsException & )
799 {
800 QgsMessageLog::logMessage( QObject::tr( "Caught a coordinate system exception while trying to transform a point." ) );
801 }
802
803 if ( lastRun )
804 break;
805
806 d += interval;
807 if ( d >= totalDist )
808 lastRun = true;
809 }
810 res << currentPart;
811 return res;
812}
813
815{
816 return willUseEllipsoid() ? Qgis::DistanceUnit::Meters : mCoordTransform.sourceCrs().mapUnits();
817}
818
820{
822 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( QStringLiteral( "Converted length of %1 %2 to %3 %4" ).arg( length )
1002 .arg( QgsUnitTypes::toString( measureUnits ) )
1003 .arg( result )
1004 .arg( QgsUnitTypes::toString( toUnits ) ), 3 );
1005 return result;
1006}
1007
1009{
1010 // get the conversion factor between the specified units
1011 const Qgis::AreaUnit measureUnits = areaUnits();
1012 const double factorUnits = QgsUnitTypes::fromUnitToUnitFactor( measureUnits, toUnits );
1013
1014 const double result = area * factorUnits;
1015 QgsDebugMsgLevel( QStringLiteral( "Converted area of %1 %2 to %3 %4" ).arg( area )
1016 .arg( QgsUnitTypes::toString( measureUnits ) )
1017 .arg( result )
1018 .arg( QgsUnitTypes::toString( toUnits ) ), 3 );
1019 return result;
1020}
DistanceUnit
Units of distance.
Definition qgis.h:5013
@ Meters
Meters.
Definition qgis.h:5014
AreaUnit
Units of area.
Definition qgis.h:5090
@ SquareMeters
Square meters.
Definition qgis.h:5091
@ Line
Lines.
Definition qgis.h:360
static QString geoNone()
Constant that holds the string representation for "No ellipse/No CRS".
Definition qgis.h:6323
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2673
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:60
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:206
QString asWkt() const
Returns the well known text representation for the point (e.g.
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
void setX(double x)
Sets the x value of the point.
Definition qgspointxy.h:119
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
void setY(double y)
Sets the point's y-coordinate.
Definition qgspoint.h:337
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
Definition qgspoint.cpp:572
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
Definition qgspoint.cpp:561
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:326
double z
Definition qgspoint.h:54
double x
Definition qgspoint.h:52
double m
Definition qgspoint.h:55
double y
Definition qgspoint.h:53
Polygon geometry type.
Definition qgspolygon.h:33
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:6524
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPoint > QgsPointSequence
#define RAD2DEG(r)
#define DEG2RAD(x)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61
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).