QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgsscalecalculator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsscalecalculator.h
3 Calculates scale based on map extent and units
4 -------------------
5 begin : May 18, 2004
6 copyright : (C) 2004 by Gary E.Sherman
7 email : sherman at mrcc.com
8 ***************************************************************************/
9
10/***************************************************************************
11 * *
12 * This program is free software; you can redistribute it and/or modify *
13 * it under the terms of the GNU General Public License as published by *
14 * the Free Software Foundation; either version 2 of the License, or *
15 * (at your option) any later version. *
16 * *
17 ***************************************************************************/
18
19#include <cmath>
20#include "qgslogger.h"
21#include "qgsrectangle.h"
22#include "qgsscalecalculator.h"
23
25 : mDpi( dpi )
26 , mMapUnits( mapUnits )
27{}
28
30{
31 mDpi = dpi;
32}
34{
35 return mDpi;
36}
37
39{
40 QgsDebugMsgLevel( QStringLiteral( "Map units set to %1" ).arg( QString::number( mapUnits ) ), 3 );
41 mMapUnits = mapUnits;
42}
43
45{
46 QgsDebugMsgLevel( QStringLiteral( "Map units returned as %1" ).arg( QString::number( mMapUnits ) ), 4 );
47 return mMapUnits;
48}
49
50double QgsScaleCalculator::calculate( const QgsRectangle &mapExtent, double canvasWidth ) const
51{
52 if ( qgsDoubleNear( canvasWidth, 0. ) || qgsDoubleNear( mDpi, 0.0 ) )
53 {
54 QgsDebugMsg( QStringLiteral( "Can't calculate scale from the input values" ) );
55 return 0;
56 }
57
58 double conversionFactor = 0;
59 double delta = 0;
60 calculateMetrics( mapExtent, delta, conversionFactor );
61
62 const double scale = ( delta * conversionFactor ) / ( static_cast< double >( canvasWidth ) / mDpi );
63 QgsDebugMsgLevel( QStringLiteral( "scale = %1 conversionFactor = %2" ).arg( scale ).arg( conversionFactor ), 4 );
64 return scale;
65}
66
67QSizeF QgsScaleCalculator::calculateImageSize( const QgsRectangle &mapExtent, double scale ) const
68{
69 if ( qgsDoubleNear( scale, 0.0 ) || qgsDoubleNear( mDpi, 0.0 ) )
70 {
71 QgsDebugMsg( QStringLiteral( "Can't calculate image size from the input values" ) );
72 return QSizeF();
73 }
74 double conversionFactor = 0;
75 double delta = 0;
76
77 calculateMetrics( mapExtent, delta, conversionFactor );
78 const double imageWidth = ( delta * conversionFactor ) / ( static_cast< double >( scale ) ) * mDpi;
79 const double deltaHeight = ( mapExtent.yMaximum() - mapExtent.yMinimum() ) * delta / ( mapExtent.xMaximum() - mapExtent.xMinimum() );
80 const double imageHeight = ( deltaHeight * conversionFactor ) / ( static_cast< double >( scale ) ) * mDpi;
81
82 QgsDebugMsgLevel( QStringLiteral( "imageWidth = %1 imageHeight = %2 conversionFactor = %3" )
83 .arg( imageWidth ).arg( imageHeight ).arg( conversionFactor ), 4 );
84
85 return QSizeF( imageWidth, imageHeight );
86}
87
88void QgsScaleCalculator::calculateMetrics( const QgsRectangle &mapExtent, double &delta, double &conversionFactor ) const
89{
90 delta = mapExtent.xMaximum() - mapExtent.xMinimum();
91 switch ( mMapUnits )
92 {
94 // convert meters to inches
95 conversionFactor = 39.3700787;
96 break;
98 conversionFactor = 12.0;
99 break;
101 // convert nautical miles to inches
102 conversionFactor = 72913.4;
103 break;
104 default:
106 // degrees require conversion to meters first
107 conversionFactor = 39.3700787;
108 delta = calculateGeographicDistance( mapExtent );
109 break;
110 }
111}
112
114{
115 // need to calculate the x distance in meters
116 // We'll use the middle latitude for the calculation
117 // Note this is an approximation (although very close) but calculating scale
118 // for geographic data over large extents is quasi-meaningless
119
120 // The distance between two points on a sphere can be estimated
121 // using the Haversine formula. This gives the shortest distance
122 // between two points on the sphere. However, what we're after is
123 // the distance from the left of the given extent and the right of
124 // it. This is not necessarily the shortest distance between two
125 // points on a sphere.
126 //
127 // The code below uses the Haversine formula, but with some changes
128 // to cope with the above problem, and also to deal with the extent
129 // possibly extending beyond +/-180 degrees:
130 //
131 // - Use the Halversine formula to calculate the distance from -90 to
132 // +90 degrees at the mean latitude.
133 // - Scale this distance by the number of degrees between
134 // mapExtent.xMinimum() and mapExtent.xMaximum();
135 // - For a slight improvemnt, allow for the ellipsoid shape of earth.
136
137
138 // For a longitude change of 180 degrees
139 const double lat = ( mapExtent.yMaximum() + mapExtent.yMinimum() ) * 0.5;
140 static const double RADS = ( 4.0 * std::atan( 1.0 ) ) / 180.0;
141 const double a = std::pow( std::cos( lat * RADS ), 2 );
142 const double c = 2.0 * std::atan2( std::sqrt( a ), std::sqrt( 1.0 - a ) );
143 static const double RA = 6378000; // [m]
144 // The eccentricity. This comes from sqrt(1.0 - rb*rb/(ra*ra)) with rb set
145 // to 6357000 m.
146 static const double E = 0.0810820288;
147 const double radius = RA * ( 1.0 - E * E ) /
148 std::pow( 1.0 - E * E * std::sin( lat * RADS ) * std::sin( lat * RADS ), 1.5 );
149 const double meters = ( mapExtent.xMaximum() - mapExtent.xMinimum() ) / 180.0 * radius * c;
150
151 QgsDebugMsgLevel( "Distance across map extent (m): " + QString::number( meters ), 4 );
152
153 return meters;
154}
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
double calculate(const QgsRectangle &mapExtent, double canvasWidth) const
Calculate the scale denominator.
void setDpi(double dpi)
Sets the dpi (dots per inch) for the output resolution, to be used in scale calculations.
void setMapUnits(QgsUnitTypes::DistanceUnit mapUnits)
Set the map units.
double calculateGeographicDistance(const QgsRectangle &mapExtent) const
Calculate the distance between two points in geographic coordinates.
QgsUnitTypes::DistanceUnit mapUnits() const
Returns current map units.
QSizeF calculateImageSize(const QgsRectangle &mapExtent, double scale) const
Calculate the image size in pixel (physical) units.
double dpi() const
Returns the DPI (dots per inch) used in scale calculations.
QgsScaleCalculator(double dpi=0, QgsUnitTypes::DistanceUnit mapUnits=QgsUnitTypes::DistanceMeters)
Constructor.
DistanceUnit
Units of distance.
Definition: qgsunittypes.h:68
@ DistanceMeters
Meters.
Definition: qgsunittypes.h:69
@ DistanceDegrees
Degrees, for planar geographic CRS distance measurements.
Definition: qgsunittypes.h:75
@ DistanceFeet
Imperial feet.
Definition: qgsunittypes.h:71
@ DistanceNauticalMiles
Nautical miles.
Definition: qgsunittypes.h:72
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2527
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38