QGIS API Documentation  3.37.0-Master (a5b4d9743e8)
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 
31 void QgsScaleCalculator::setDpi( double dpi )
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 
52 double 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 
69 QSizeF 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 
90 void 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 
108  // convert to inches
109  conversionFactor = QgsUnitTypes::fromUnitToUnitFactor( mMapUnits, Qgis::DistanceUnit::Inches );
110  break;
111 
113  // assume degrees to maintain old API
114  [[fallthrough]];
115 
117  // degrees require conversion to meters first
118  conversionFactor = 39.3700787;
119  delta = calculateGeographicDistance( mapExtent );
120  break;
121  }
122 }
123 
125 {
126  // need to calculate the x distance in meters
127  // We'll use the middle latitude for the calculation
128  // Note this is an approximation (although very close) but calculating scale
129  // for geographic data over large extents is quasi-meaningless
130 
131  // The distance between two points on a sphere can be estimated
132  // using the Haversine formula. This gives the shortest distance
133  // between two points on the sphere. However, what we're after is
134  // the distance from the left of the given extent and the right of
135  // it. This is not necessarily the shortest distance between two
136  // points on a sphere.
137  //
138  // The code below uses the Haversine formula, but with some changes
139  // to cope with the above problem, and also to deal with the extent
140  // possibly extending beyond +/-180 degrees:
141  //
142  // - Use the Halversine formula to calculate the distance from -90 to
143  // +90 degrees at the mean latitude.
144  // - Scale this distance by the number of degrees between
145  // mapExtent.xMinimum() and mapExtent.xMaximum();
146  // - For a slight improvemnt, allow for the ellipsoid shape of earth.
147 
148 
149  // For a longitude change of 180 degrees
150  const double lat = ( mapExtent.yMaximum() + mapExtent.yMinimum() ) * 0.5;
151  static const double RADS = ( 4.0 * std::atan( 1.0 ) ) / 180.0;
152  const double a = std::pow( std::cos( lat * RADS ), 2 );
153  const double c = 2.0 * std::atan2( std::sqrt( a ), std::sqrt( 1.0 - a ) );
154  static const double RA = 6378000; // [m]
155  // The eccentricity. This comes from sqrt(1.0 - rb*rb/(ra*ra)) with rb set
156  // to 6357000 m.
157  static const double E = 0.0810820288;
158  const double radius = RA * ( 1.0 - E * E ) /
159  std::pow( 1.0 - E * E * std::sin( lat * RADS ) * std::sin( lat * RADS ), 1.5 );
160  const double meters = ( mapExtent.xMaximum() - mapExtent.xMinimum() ) / 180.0 * radius * c;
161 
162  QgsDebugMsgLevel( "Distance across map extent (m): " + QString::number( meters ), 4 );
163 
164  return meters;
165 }
DistanceUnit
Units of distance.
Definition: qgis.h:4090
@ Feet
Imperial feet.
@ Centimeters
Centimeters.
@ Millimeters
Millimeters.
@ Miles
Terrestrial miles.
@ Unknown
Unknown distance unit.
@ Yards
Imperial yards.
@ Degrees
Degrees, for planar geographic CRS distance measurements.
@ Inches
Inches (since QGIS 3.32)
@ NauticalMiles
Nautical miles.
@ Kilometers
Kilometers.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:201
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:211
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:196
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:206
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:5363
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5172
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugError(str)
Definition: qgslogger.h:38