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