Skip to content

Commit

Permalink
[FEATURE] New unit type for rendering in map unit meters sizes
Browse files Browse the repository at this point in the history
Allows setting symbols other map item sizes to Map Units in Meters. This allows setting the size always in meters, regardless of what the underlying map units are (e.g. they can be it geographic degrees). The size in meters is calculated based on the current project ellipsoid setting and a projection of the distances in meters at the center of the current map extent.
  • Loading branch information
mj10777 authored and nyalldawson committed Jul 13, 2017
1 parent 69dab42 commit 172e809
Show file tree
Hide file tree
Showing 28 changed files with 328 additions and 64 deletions.
3 changes: 3 additions & 0 deletions python/core/qgsdistancearea.sip
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ Constructor
code (and documentation) taken from rttopo project
https://git.osgeo.org/gogs/rttopo/librttopo
- spheroid_project.spheroid_project(...)
- Valid bounds checking for degrees (latitude=+- 85.05115) is based values used for
-> 'WGS84 Web Mercator (Auxiliary Sphere)' calculations
--> latitudes outside these bounds cause the calculations to become unstable and can return invalid results
.. versionadded:: 3.0
\param p1 - location of first geographic (latitude/longitude) point as degrees.
\param distance - distance in meters.
Expand Down
20 changes: 20 additions & 0 deletions python/core/qgsmapsettings.sip
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,26 @@ Get units of map's geographical coordinates - used for scale calculation
:rtype: QgsUnitTypes.DistanceUnit
%End

bool setEllipsoid( const QString &ellipsoid );
%Docstring
Sets the ``ellipsoid`` by its acronym. Known ellipsoid acronyms can be
retrieved using QgsEllipsoidUtils.acronyms().
Calculations will only use the ellipsoid if a valid ellipsoid has been set.
:return: true if ellipsoid was successfully set
.. versionadded:: 3.0
.. seealso:: ellipsoid()
:rtype: bool
%End

QString ellipsoid() const;
%Docstring
Returns ellipsoid's acronym. Calculations will only use the
ellipsoid if a valid ellipsoid has been set.
.. versionadded:: 3.0
.. seealso:: setEllipsoid()
:rtype: str
%End

void setBackgroundColor( const QColor &color );
%Docstring
Set the background color of the map
Expand Down
23 changes: 23 additions & 0 deletions python/core/qgsrendercontext.sip
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ class QgsRenderContext
:rtype: QgsCoordinateTransform
%End

const QgsDistanceArea &distanceArea() const;
%Docstring
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
.. versionadded:: 3.0
:rtype: QgsDistanceArea
%End

const QgsRectangle &extent() const;
%Docstring
:rtype: QgsRectangle
Expand Down Expand Up @@ -182,6 +189,13 @@ Sets coordinate transformation.

void setRenderingStopped( bool stopped );

void setDistanceArea( const QgsDistanceArea distanceArea );
%Docstring
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
Will be used to convert meter distances to active MapUnit values for QgsUnitTypes.RenderMetersInMapUnits
.. versionadded:: 3.0
%End

void setScaleFactor( double factor );
%Docstring
Sets the scaling factor for the render to convert painter units
Expand Down Expand Up @@ -327,6 +341,15 @@ Gets segmentation tolerance type (maximum angle or maximum difference between cu
:rtype: float
%End

double convertMetersToMapUnits( double meters ) const;
%Docstring
Convert meter distances to active MapUnit values for QgsUnitTypes.RenderMetersInMapUnits
\note
When the sourceCrs() is geographic, the center of the Extent will be used
.. versionadded:: 3.0
:rtype: float
%End

};

QFlags<QgsRenderContext::Flag> operator|(QgsRenderContext::Flag f1, QFlags<QgsRenderContext::Flag> f2);
Expand Down
1 change: 1 addition & 0 deletions python/core/qgsunittypes.sip
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class QgsUnitTypes
RenderPoints,
RenderInches,
RenderUnknownUnit,
RenderMetersInMapUnits,
};

enum LayoutUnit
Expand Down
3 changes: 3 additions & 0 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2999,6 +2999,7 @@ void QgisApp::setupConnections()
connect( QgsProject::instance(), &QgsProject::crsChanged,
this, [ = ]
{
QgsDebugMsgLevel( QString( "QgisApp::setupConnections -1- : QgsProject::instance()->crs().description[%1]ellipsoid[%2]" ).arg( QgsProject::instance()->crs().description() ).arg( QgsProject::instance()->crs().ellipsoidAcronym() ), 3 );
mMapCanvas->setDestinationCrs( QgsProject::instance()->crs() );
} );

Expand Down Expand Up @@ -3259,6 +3260,7 @@ QgsMapCanvas *QgisApp::createNewMapCanvas( const QString &name )

dock->mapCanvas()->setLayers( mMapCanvas->layers() );
dock->mapCanvas()->setExtent( mMapCanvas->extent() );
QgsDebugMsgLevel( QString( "QgisApp::createNewMapCanvas -2- : QgsProject::instance()->crs().description[%1]ellipsoid[%2]" ).arg( QgsProject::instance()->crs().description() ).arg( QgsProject::instance()->crs().ellipsoidAcronym() ), 3 );
dock->mapCanvas()->setDestinationCrs( QgsProject::instance()->crs() );
dock->mapCanvas()->freeze( false );
return dock->mapCanvas();
Expand Down Expand Up @@ -9960,6 +9962,7 @@ void QgisApp::newMapCanvas()
{
dock->mapCanvas()->setLayers( mMapCanvas->layers() );
dock->mapCanvas()->setExtent( mMapCanvas->extent() );
QgsDebugMsgLevel( QString( "QgisApp::newMapCanvas() -4- : QgsProject::instance()->crs().description[%1] ellipsoid[%2]" ).arg( QgsProject::instance()->crs().description() ).arg( QgsProject::instance()->crs().ellipsoidAcronym() ), 3 );
dock->mapCanvas()->setDestinationCrs( QgsProject::instance()->crs() );
dock->mapCanvas()->freeze( false );
}
Expand Down
9 changes: 9 additions & 0 deletions src/app/qgsmaptooloffsetpointsymbol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,15 @@ QPointF QgsMapToolOffsetPointSymbol::calculateOffset( const QgsPointXY &startPoi
factor = 1.0;
break;

case QgsUnitTypes::RenderMetersInMapUnits:
{
QgsDistanceArea distanceArea;
distanceArea.setSourceCrs( mCanvas->mapSettings().destinationCrs() );
distanceArea.setEllipsoid( mCanvas->mapSettings().ellipsoid() );
// factor=1.0 / 1 meter in MapUnits
factor = 1.0 / distanceArea.measureLineProjected( startPoint );
}
break;
case QgsUnitTypes::RenderUnknownUnit:
case QgsUnitTypes::RenderPercentage:
//no sensible value
Expand Down
1 change: 1 addition & 0 deletions src/core/composer/qgscomposermap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ QgsMapSettings QgsComposerMap::mapSettings( const QgsRectangle &extent, QSizeF s
jobMapSettings.setOutputDpi( dpi );
jobMapSettings.setBackgroundColor( Qt::transparent );
jobMapSettings.setRotation( mEvaluatedMapRotation );
jobMapSettings.setEllipsoid( mComposition->project()->ellipsoid() );

//set layers to render
QList<QgsMapLayer *> layers = layersToRender( &expressionContext );
Expand Down
23 changes: 23 additions & 0 deletions src/core/qgsdistancearea.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
QgsDistanceArea::QgsDistanceArea()
{
// init with default settings
mSemiMajor = -1.0;
mSemiMinor = -1.0;
mInvFlattening = -1.0;
setSourceCrs( QgsCoordinateReferenceSystem::fromSrsId( GEOCRS_ID ) ); // WGS 84
setEllipsoid( GEO_NONE );
}
Expand Down Expand Up @@ -363,6 +366,19 @@ double QgsDistanceArea::measureLineProjected( const QgsPointXY &p1, double dista
}
p2 = p1.project( distance, azimuth );
}
QgsDebugMsgLevel( QString( "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]" )
.arg( QString::number( distance, 'f', 7 ) )
.arg( QgsUnitTypes::toString( QgsUnitTypes::DistanceMeters ) )
.arg( QString::number( result, 'f', 7 ) )
.arg( ( ( mCoordTransform.sourceCrs().isGeographic() ) == 1 ? QString( "Geographic" ) : QString( "Cartesian" ) ) )
.arg( QgsUnitTypes::toString( sourceCrs().mapUnits() ) )
.arg( azimuth )
.arg( p1.wellKnownText() )
.arg( p2.wellKnownText() )
.arg( sourceCrs().description() )
.arg( mEllipsoid )
.arg( sourceCrs().isGeographic() )
.arg( QString( "SemiMajor[%1] SemiMinor[%2] InvFlattening[%3] " ).arg( QString::number( mSemiMajor, 'f', 7 ) ).arg( QString::number( mSemiMinor, 'f', 7 ) ).arg( QString::number( mInvFlattening, 'f', 7 ) ) ), 4 );
if ( projectedPoint )
{
*projectedPoint = QgsPointXY( p2 );
Expand All @@ -384,6 +400,13 @@ QgsPointXY QgsDistanceArea::computeSpheroidProject(
double a = mSemiMajor;
double b = mSemiMinor;
double f = 1 / mInvFlattening;
if ( ( ( a < 0 ) && ( b < 0 ) ) ||
( ( p1.x() < -180.0 ) || ( p1.x() > 180.0 ) || ( p1.y() < -85.05115 ) || ( p1.y() > 85.05115 ) ) )
{
// latitudes outside these bounds cause the calculations to become unstable and can return invalid results
return QgsPoint( 0, 0 );

}
double radians_lat = DEG2RAD( p1.y() );
double radians_long = DEG2RAD( p1.x() );
double b2 = POW2( b ); // spheroid_mu2
Expand Down
3 changes: 3 additions & 0 deletions src/core/qgsdistancearea.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ class CORE_EXPORT QgsDistanceArea
* \note code (and documentation) taken from rttopo project
* https://git.osgeo.org/gogs/rttopo/librttopo
* - spheroid_project.spheroid_project(...)
* - Valid bounds checking for degrees (latitude=+- 85.05115) is based values used for
* -> 'WGS84 Web Mercator (Auxiliary Sphere)' calculations
* --> latitudes outside these bounds cause the calculations to become unstable and can return invalid results
* \since QGIS 3.0
* \param p1 - location of first geographic (latitude/longitude) point as degrees.
* \param distance - distance in meters.
Expand Down
2 changes: 0 additions & 2 deletions src/core/qgsmaprendererparalleljob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
QTime t;
t.start();
QgsDebugMsgLevel( QString( "job %1 start (layer %2)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.layer ? job.layer->id() : QString() ), 2 );

try
{
job.renderer->render();
Expand All @@ -270,7 +269,6 @@ void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
{
QgsDebugMsg( "Caught unhandled unknown exception" );
}

job.renderingTime = t.elapsed();
QgsDebugMsgLevel( QString( "job %1 end [%2 ms] (layer %3)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.renderingTime ).arg( job.layer ? job.layer->id() : QString() ), 2 );
}
Expand Down
15 changes: 14 additions & 1 deletion src/core/qgsmapsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,6 @@ void QgsMapSettings::setDestinationCrs( const QgsCoordinateReferenceSystem &crs
{
mDestCRS = crs;
mDatumTransformStore.setDestinationCrs( crs );

mScaleCalculator.setMapUnits( crs.mapUnits() );
// Since the map units have changed, force a recalculation of the scale.
updateDerived();
Expand All @@ -295,6 +294,20 @@ QgsCoordinateReferenceSystem QgsMapSettings::destinationCrs() const
return mDestCRS;
}

bool QgsMapSettings::setEllipsoid( const QString &ellipsoid )
{
QgsEllipsoidUtils::EllipsoidParameters params = QgsEllipsoidUtils::ellipsoidParameters( ellipsoid );
if ( !params.valid )
{
return false;
}
else
{
mEllipsoid = ellipsoid;
return true;
}
}

void QgsMapSettings::setFlags( QgsMapSettings::Flags flags )
{
mFlags = flags;
Expand Down
19 changes: 19 additions & 0 deletions src/core/qgsmapsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,23 @@ class CORE_EXPORT QgsMapSettings
//! Get units of map's geographical coordinates - used for scale calculation
QgsUnitTypes::DistanceUnit mapUnits() const;

/**
* Sets the \a ellipsoid by its acronym. Known ellipsoid acronyms can be
* retrieved using QgsEllipsoidUtils::acronyms().
* Calculations will only use the ellipsoid if a valid ellipsoid has been set.
* \returns true if ellipsoid was successfully set
* \since QGIS 3.0
* \see ellipsoid()
*/
bool setEllipsoid( const QString &ellipsoid );

/** Returns ellipsoid's acronym. Calculations will only use the
* ellipsoid if a valid ellipsoid has been set.
* \since QGIS 3.0
* \see setEllipsoid()
*/
QString ellipsoid() const { return mEllipsoid; }

//! Set the background color of the map
void setBackgroundColor( const QColor &color ) { mBackgroundColor = color; }
//! Get the background color of the map
Expand Down Expand Up @@ -332,6 +349,8 @@ class CORE_EXPORT QgsMapSettings
QgsExpressionContext mExpressionContext;

QgsCoordinateReferenceSystem mDestCRS;
//! ellipsoid acronym (from table tbl_ellipsoids)
QString mEllipsoid;
QgsDatumTransformStore mDatumTransformStore;

QColor mBackgroundColor;
Expand Down
58 changes: 57 additions & 1 deletion src/core/qgsrendercontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include "qgsexpression.h"
#include "qgsvectorlayer.h"
#include "qgsfeaturefilterprovider.h"
#include "qgslogger.h"
#include "qgspoint.h"

#define POINTS_TO_MM 2.83464567
#define INCH_TO_MM 25.4
Expand All @@ -30,12 +32,16 @@ QgsRenderContext::QgsRenderContext()
: mFlags( DrawEditingInfo | UseAdvancedEffects | DrawSelection | UseRenderingOptimization )
{
mVectorSimplifyMethod.setSimplifyHints( QgsVectorSimplifyMethod::NoSimplification );
// For RenderMetersInMapUnits support, when rendering in Degrees, the Ellipsoid must be set
// - for Previews/Icons the default Extent can be used
mDistanceArea.setEllipsoid( mDistanceArea.sourceCrs().ellipsoidAcronym() );
}

QgsRenderContext::QgsRenderContext( const QgsRenderContext &rh )
: mFlags( rh.mFlags )
, mPainter( rh.mPainter )
, mCoordTransform( rh.mCoordTransform )
, mDistanceArea( rh.mDistanceArea )
, mExtent( rh.mExtent )
, mMapToPixel( rh.mMapToPixel )
, mRenderingStopped( rh.mRenderingStopped )
Expand Down Expand Up @@ -70,6 +76,7 @@ QgsRenderContext &QgsRenderContext::operator=( const QgsRenderContext &rh )
mFeatureFilterProvider.reset( rh.mFeatureFilterProvider ? rh.mFeatureFilterProvider->clone() : nullptr );
mSegmentationTolerance = rh.mSegmentationTolerance;
mSegmentationToleranceType = rh.mSegmentationToleranceType;
mDistanceArea = rh.mDistanceArea;
return *this;
}

Expand Down Expand Up @@ -132,7 +139,8 @@ QgsRenderContext QgsRenderContext::fromMapSettings( const QgsMapSettings &mapSet
ctx.setExpressionContext( mapSettings.expressionContext() );
ctx.setSegmentationTolerance( mapSettings.segmentationTolerance() );
ctx.setSegmentationToleranceType( mapSettings.segmentationToleranceType() );

ctx.mDistanceArea.setSourceCrs( mapSettings.destinationCrs() );
ctx.mDistanceArea.setEllipsoid( mapSettings.ellipsoid() );
//this flag is only for stopping during the current rendering progress,
//so must be false at every new render operation
ctx.setRenderingStopped( false );
Expand Down Expand Up @@ -229,6 +237,13 @@ double QgsRenderContext::convertToPainterUnits( double size, QgsUnitTypes::Rende
conversionFactor = mScaleFactor * INCH_TO_MM;
break;

case QgsUnitTypes::RenderMetersInMapUnits:
{
size = convertMetersToMapUnits( size );
unit = QgsUnitTypes::RenderMapUnits;
// Fall through to RenderMapUnits with size in meters converted to size in MapUnits
FALLTHROUGH;
}
case QgsUnitTypes::RenderMapUnits:
{
double mup = scale.computeMapUnitsPerPixel( *this );
Expand Down Expand Up @@ -273,6 +288,12 @@ double QgsRenderContext::convertToMapUnits( double size, QgsUnitTypes::RenderUni

switch ( unit )
{
case QgsUnitTypes::RenderMetersInMapUnits:
{
size = convertMetersToMapUnits( size );
// Fall through to RenderMapUnits with values of meters converted to MapUnits
FALLTHROUGH;
}
case QgsUnitTypes::RenderMapUnits:
{
// check scale
Expand Down Expand Up @@ -331,6 +352,10 @@ double QgsRenderContext::convertFromMapUnits( double sizeInMapUnits, QgsUnitType

switch ( outputUnit )
{
case QgsUnitTypes::RenderMetersInMapUnits:
{
return sizeInMapUnits / convertMetersToMapUnits( 1.0 );
}
case QgsUnitTypes::RenderMapUnits:
{
return sizeInMapUnits;
Expand Down Expand Up @@ -359,3 +384,34 @@ double QgsRenderContext::convertFromMapUnits( double sizeInMapUnits, QgsUnitType
}
return 0.0;
}

double QgsRenderContext::convertMetersToMapUnits( double meters ) const
{
switch ( mDistanceArea.sourceCrs().mapUnits() )
{
case QgsUnitTypes::DistanceMeters:
return meters;
case QgsUnitTypes::DistanceDegrees:
{
QgsPointXY pointCenter = mExtent.center();
// The Extent is in the sourceCrs(), when different from destinationCrs()
// - the point must be transformed, since DistanceArea uses the destinationCrs()
// Note: the default QgsCoordinateTransform() : authid() will return an empty String
if ( !mCoordTransform.isShortCircuited() )
{
pointCenter = mCoordTransform.transform( pointCenter );
}
return mDistanceArea.measureLineProjected( pointCenter, meters );
}
case QgsUnitTypes::DistanceKilometers:
case QgsUnitTypes::DistanceFeet:
case QgsUnitTypes::DistanceNauticalMiles:
case QgsUnitTypes::DistanceYards:
case QgsUnitTypes::DistanceMiles:
case QgsUnitTypes::DistanceCentimeters:
case QgsUnitTypes::DistanceMillimeters:
case QgsUnitTypes::DistanceUnknownUnit:
return ( meters * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceMeters, mDistanceArea.sourceCrs().mapUnits() ) );
}
return meters;
}
Loading

0 comments on commit 172e809

Please sign in to comment.