Skip to content
Permalink
Browse files

[FEATURE] New unit type for rendering in map unit meters sizes

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 172e80918131bb8414c753a463a9b7abc069e0bf
@@ -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.
@@ -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
@@ -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
@@ -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
@@ -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);
@@ -79,6 +79,7 @@ class QgsUnitTypes
RenderPoints,
RenderInches,
RenderUnknownUnit,
RenderMetersInMapUnits,
};

enum LayoutUnit
@@ -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() );
} );

@@ -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();
@@ -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 );
}
@@ -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
@@ -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 );
@@ -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 );
}
@@ -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 );
@@ -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
@@ -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.
@@ -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();
@@ -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 );
}
@@ -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();
@@ -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;
@@ -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
@@ -332,6 +349,8 @@ class CORE_EXPORT QgsMapSettings
QgsExpressionContext mExpressionContext;

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

QColor mBackgroundColor;
@@ -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
@@ -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 )
@@ -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;
}

@@ -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 );
@@ -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 );
@@ -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
@@ -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;
@@ -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;
}

0 comments on commit 172e809

Please sign in to comment.
You can’t perform that action at this time.