QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
Loading...
Searching...
No Matches
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 "qgsscalecalculator.h"
22#include "qgsrectangle.h"
23#include "qgsunittypes.h"
24#include <QSizeF>
25
27 : mDpi( dpi )
28 , mMapUnits( mapUnits )
29{}
30
32{
33 mDpi = dpi;
34}
36{
37 return mDpi;
38}
39
41{
42 QgsDebugMsgLevel( QStringLiteral( "Map units set to %1" ).arg( qgsEnumValueToKey( mapUnits ) ), 3 );
43 mMapUnits = mapUnits;
44}
45
47{
48 QgsDebugMsgLevel( QStringLiteral( "Map units returned as %1" ).arg( qgsEnumValueToKey( mMapUnits ) ), 4 );
49 return mMapUnits;
50}
51
52double QgsScaleCalculator::calculate( const QgsRectangle &mapExtent, double canvasWidth ) const
53{
54 if ( qgsDoubleNear( canvasWidth, 0. ) || qgsDoubleNear( mDpi, 0.0 ) )
55 {
56 QgsDebugError( QStringLiteral( "Can't calculate scale from the input values" ) );
57 return 0;
58 }
59
60 double conversionFactor = 0;
61 double delta = 0;
62 calculateMetrics( mapExtent, delta, conversionFactor );
63
64 const double scale = ( delta * conversionFactor ) / ( static_cast< double >( canvasWidth ) / mDpi );
65 QgsDebugMsgLevel( QStringLiteral( "scale = %1 conversionFactor = %2" ).arg( scale ).arg( conversionFactor ), 4 );
66 return scale;
67}
68
69QSizeF QgsScaleCalculator::calculateImageSize( const QgsRectangle &mapExtent, double scale ) const
70{
71 if ( qgsDoubleNear( scale, 0.0 ) || qgsDoubleNear( mDpi, 0.0 ) )
72 {
73 QgsDebugError( QStringLiteral( "Can't calculate image size from the input values" ) );
74 return QSizeF();
75 }
76 double conversionFactor = 0;
77 double delta = 0;
78
79 calculateMetrics( mapExtent, delta, conversionFactor );
80 const double imageWidth = ( delta * conversionFactor ) / ( static_cast< double >( scale ) ) * mDpi;
81 const double deltaHeight = ( mapExtent.yMaximum() - mapExtent.yMinimum() ) * delta / ( mapExtent.xMaximum() - mapExtent.xMinimum() );
82 const double imageHeight = ( deltaHeight * conversionFactor ) / ( static_cast< double >( scale ) ) * mDpi;
83
84 QgsDebugMsgLevel( QStringLiteral( "imageWidth = %1 imageHeight = %2 conversionFactor = %3" )
85 .arg( imageWidth ).arg( imageHeight ).arg( conversionFactor ), 4 );
86
87 return QSizeF( imageWidth, imageHeight );
88}
89
90void QgsScaleCalculator::calculateMetrics( const QgsRectangle &mapExtent, double &delta, double &conversionFactor ) const
91{
92 delta = mapExtent.xMaximum() - mapExtent.xMinimum();
93
94 switch ( mMapUnits )
95 {
97 conversionFactor = 1;
98 break;
99
147 // convert to inches
148 conversionFactor = QgsUnitTypes::fromUnitToUnitFactor( mMapUnits, Qgis::DistanceUnit::Inches );
149 break;
150
152 // assume degrees to maintain old API
153 [[fallthrough]];
154
156 // degrees require conversion to meters first
157 conversionFactor = 39.3700787;
158 delta = calculateGeographicDistance( mapExtent );
159 break;
160 }
161}
162
164{
165 // need to calculate the x distance in meters
166 // We'll use the middle latitude for the calculation
167 // Note this is an approximation (although very close) but calculating scale
168 // for geographic data over large extents is quasi-meaningless
169
170 // The distance between two points on a sphere can be estimated
171 // using the Haversine formula. This gives the shortest distance
172 // between two points on the sphere. However, what we're after is
173 // the distance from the left of the given extent and the right of
174 // it. This is not necessarily the shortest distance between two
175 // points on a sphere.
176 //
177 // The code below uses the Haversine formula, but with some changes
178 // to cope with the above problem, and also to deal with the extent
179 // possibly extending beyond +/-180 degrees:
180 //
181 // - Use the Halversine formula to calculate the distance from -90 to
182 // +90 degrees at the mean latitude.
183 // - Scale this distance by the number of degrees between
184 // mapExtent.xMinimum() and mapExtent.xMaximum();
185 // - For a slight improvemnt, allow for the ellipsoid shape of earth.
186
187
188 // For a longitude change of 180 degrees
189 const double lat = ( mapExtent.yMaximum() + mapExtent.yMinimum() ) * 0.5;
190 static const double RADS = ( 4.0 * std::atan( 1.0 ) ) / 180.0;
191 const double a = std::pow( std::cos( lat * RADS ), 2 );
192 const double c = 2.0 * std::atan2( std::sqrt( a ), std::sqrt( 1.0 - a ) );
193 static const double RA = 6378000; // [m]
194 // The eccentricity. This comes from sqrt(1.0 - rb*rb/(ra*ra)) with rb set
195 // to 6357000 m.
196 static const double E = 0.0810820288;
197 const double radius = RA * ( 1.0 - E * E ) /
198 std::pow( 1.0 - E * E * std::sin( lat * RADS ) * std::sin( lat * RADS ), 1.5 );
199 const double meters = ( mapExtent.xMaximum() - mapExtent.xMinimum() ) / 180.0 * radius * c;
200
201 QgsDebugMsgLevel( "Distance across map extent (m): " + QString::number( meters ), 4 );
202
203 return meters;
204}
DistanceUnit
Units of distance.
Definition qgis.h:4669
@ YardsBritishSears1922Truncated
British yards (Sears 1922 truncated)
@ Feet
Imperial feet.
@ MilesUSSurvey
US Survey miles.
@ LinksBritishSears1922
British links (Sears 1922)
@ YardsBritishBenoit1895A
British yards (Benoit 1895 A)
@ LinksBritishBenoit1895A
British links (Benoit 1895 A)
@ Centimeters
Centimeters.
@ YardsIndian1975
Indian yards (1975)
@ FeetUSSurvey
US Survey feet.
@ Millimeters
Millimeters.
@ FeetBritishSears1922
British feet (Sears 1922)
@ YardsClarkes
Clarke's yards.
@ YardsIndian
Indian yards.
@ FeetBritishBenoit1895B
British feet (Benoit 1895 B)
@ Miles
Terrestrial miles.
@ LinksUSSurvey
US Survey links.
@ ChainsUSSurvey
US Survey chains.
@ FeetClarkes
Clarke's feet.
@ Unknown
Unknown distance unit.
@ Yards
Imperial yards.
@ FeetBritish1936
British feet (1936)
@ FeetIndian1962
Indian feet (1962)
@ YardsBritishSears1922
British yards (Sears 1922)
@ FeetIndian1937
Indian feet (1937)
@ YardsIndian1937
Indian yards (1937)
@ Degrees
Degrees, for planar geographic CRS distance measurements.
@ ChainsBritishBenoit1895B
British chains (Benoit 1895 B)
@ LinksBritishSears1922Truncated
British links (Sears 1922 truncated)
@ ChainsBritishBenoit1895A
British chains (Benoit 1895 A)
@ YardsBritishBenoit1895B
British yards (Benoit 1895 B)
@ FeetBritish1865
British feet (1865)
@ YardsIndian1962
Indian yards (1962)
@ FeetBritishSears1922Truncated
British feet (Sears 1922 truncated)
@ MetersGermanLegal
German legal meter.
@ LinksBritishBenoit1895B
British links (Benoit 1895 B)
@ ChainsInternational
International chains.
@ LinksInternational
International links.
@ ChainsBritishSears1922Truncated
British chains (Sears 1922 truncated)
@ FeetIndian
Indian (geodetic) feet.
@ NauticalMiles
Nautical miles.
@ ChainsClarkes
Clarke's chains.
@ LinksClarkes
Clarke's links.
@ ChainsBritishSears1922
British chains (Sears 1922)
@ Kilometers
Kilometers.
@ FeetIndian1975
Indian feet (1975)
@ FeetGoldCoast
Gold Coast feet.
@ FeetBritishBenoit1895A
British feet (Benoit 1895 A)
A rectangle specified with double values.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
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(Qgis::DistanceUnit mapUnits)
Set the map units.
double calculateGeographicDistance(const QgsRectangle &mapExtent) const
Calculate the distance between two points in geographic coordinates.
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.
Qgis::DistanceUnit mapUnits() const
Returns current map units.
QgsScaleCalculator(double dpi=0, Qgis::DistanceUnit mapUnits=Qgis::DistanceUnit::Meters)
Constructor.
static Q_INVOKABLE double fromUnitToUnitFactor(Qgis::DistanceUnit fromUnit, Qgis::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
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
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6108
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5917
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38