Merge pull request #7777 from PeterPetrik/mesh_vector_on_grid

[mesh] [feature] allow render vectors/arrows on the user-defined grid
This commit is contained in:
Martin Dobias 2018-09-06 15:32:54 +02:00 committed by GitHub
commit 1839daaa18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 544 additions and 180 deletions

View File

@ -282,6 +282,31 @@ Returns ratio of the head length of the arrow (range 0-1)
void setArrowHeadLengthRatio( double arrowHeadLengthRatio );
%Docstring
Sets ratio of the head length of the arrow (range 0-1)
%End
bool isOnUserDefinedGrid() const;
%Docstring
Returns whether vectors are drawn on user-defined grid
%End
void setOnUserDefinedGrid( bool enabled );
%Docstring
Toggles drawing of vectors on user defined grid
%End
int userGridCellWidth() const;
%Docstring
Returns width in pixels of user grid cell
%End
void setUserGridCellWidth( int width );
%Docstring
Sets width of user grid cell (in pixels)
%End
int userGridCellHeight() const;
%Docstring
Returns height in pixels of user grid cell
%End
void setUserGridCellHeight( int height );
%Docstring
Sets height of user grid cell (in pixels)
%End
QDomElement writeXml( QDomDocument &doc ) const;

View File

@ -19,7 +19,6 @@
#include "qgsmeshlayer.h"
#include "qgsmessagelog.h"
QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidget *parent )
: QWidget( parent )
@ -37,6 +36,9 @@ QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidge
connect( mShaftLengthComboBox, qgis::overload<int>::of( &QComboBox::currentIndexChanged ),
mShaftOptionsStackedWidget, &QStackedWidget::setCurrentIndex );
connect( mDisplayVectorsOnGridGroupBox, &QGroupBox::toggled, this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
QVector<QLineEdit *> widgets;
widgets << mMinMagLineEdit << mMaxMagLineEdit
<< mHeadWidthLineEdit << mHeadLengthLineEdit
@ -47,6 +49,9 @@ QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidge
{
connect( widget, &QLineEdit::textChanged, this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
}
connect( mXSpacingSpinBox, qgis::overload<int>::of( &QgsSpinBox::valueChanged ), this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
connect( mYSpacingSpinBox, qgis::overload<int>::of( &QgsSpinBox::valueChanged ), this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
}
void QgsMeshRendererVectorSettingsWidget::setLayer( QgsMeshLayer *layer )
@ -76,6 +81,12 @@ QgsMeshRendererVectorSettings QgsMeshRendererVectorSettingsWidget::settings() co
val = filterValue( mHeadLengthLineEdit->text(), settings.arrowHeadLengthRatio() * 100.0 );
settings.setArrowHeadLengthRatio( val / 100.0 );
// user grid
bool enabled = mDisplayVectorsOnGridGroupBox->isChecked();
settings.setOnUserDefinedGrid( enabled );
settings.setUserGridCellWidth( mXSpacingSpinBox->value() );
settings.setUserGridCellHeight( mYSpacingSpinBox->value() );
// shaft length
auto method = static_cast<QgsMeshRendererVectorSettings::ArrowScalingMethod>( mShaftLengthComboBox->currentIndex() );
settings.setShaftLengthMethod( method );
@ -124,6 +135,11 @@ void QgsMeshRendererVectorSettingsWidget::syncToLayer( )
mHeadWidthLineEdit->setText( QString::number( settings.arrowHeadWidthRatio() * 100.0 ) );
mHeadLengthLineEdit->setText( QString::number( settings.arrowHeadLengthRatio() * 100.0 ) );
// user grid
mDisplayVectorsOnGridGroupBox->setChecked( settings.isOnUserDefinedGrid() );
mXSpacingSpinBox->setValue( settings.userGridCellWidth() );
mYSpacingSpinBox->setValue( settings.userGridCellHeight() );
// shaft length
mShaftLengthComboBox->setCurrentIndex( settings.shaftLengthMethod() );

View File

@ -25,7 +25,6 @@
#include "qgsmaplayerlegend.h"
#include "qgsmeshdataprovider.h"
#include "qgsmeshlayer.h"
#include "qgsmeshlayerinterpolator.h"
#include "qgsmeshlayerrenderer.h"
#include "qgsmeshlayerutils.h"
#include "qgsproviderregistry.h"
@ -144,11 +143,11 @@ QgsMeshDatasetValue QgsMeshLayer::datasetValue( const QgsMeshDatasetIndex &index
const QgsMeshDatasetValue val1 = dataProvider()->datasetValue( index, v1 );
const QgsMeshDatasetValue val2 = dataProvider()->datasetValue( index, v2 );
const QgsMeshDatasetValue val3 = dataProvider()->datasetValue( index, v3 );
const double x = QgsMeshLayerInterpolator::interpolateFromVerticesData( p1, p2, p3, val1.x(), val2.x(), val3.x(), point );
const double x = QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.x(), val2.x(), val3.x(), point );
double y = std::numeric_limits<double>::quiet_NaN();
bool isVector = dataProvider()->datasetGroupMetadata( index ).isVector();
if ( isVector )
y = QgsMeshLayerInterpolator::interpolateFromVerticesData( p1, p2, p3, val1.y(), val2.y(), val3.y(), point );
y = QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.y(), val2.y(), val3.y(), point );
value = QgsMeshDatasetValue( x, y );
}

View File

@ -26,86 +26,7 @@
#include "qgsvector.h"
#include "qgspoint.h"
#include "qgspointxy.h"
static void bbox2rect(
const QgsMapToPixel &mtp,
const QSize &outputSize,
const QgsRectangle &bbox,
int &leftLim, int &rightLim, int &topLim, int &bottomLim )
{
QgsPointXY ll = mtp.transform( bbox.xMinimum(), bbox.yMinimum() );
QgsPointXY ur = mtp.transform( bbox.xMaximum(), bbox.yMaximum() );
topLim = std::max( int( ur.y() ), 0 );
bottomLim = std::min( int( ll.y() ), outputSize.height() - 1 );
leftLim = std::max( int( ll.x() ), 0 );
rightLim = std::min( int( ur.x() ), outputSize.width() - 1 );
}
static void lamTol( double &lam )
{
const static double eps = 1e-6;
if ( ( lam < 0.0 ) && ( lam > -eps ) )
{
lam = 0.0;
}
}
static bool E3T_physicalToBarycentric( const QgsPointXY &pA, const QgsPointXY &pB, const QgsPointXY &pC, const QgsPointXY &pP,
double &lam1, double &lam2, double &lam3 )
{
if ( pA == pB || pA == pC || pB == pC )
return false; // this is not a valid triangle!
// Compute vectors
QgsVector v0( pC - pA );
QgsVector v1( pB - pA );
QgsVector v2( pP - pA );
// Compute dot products
double dot00 = v0 * v0;
double dot01 = v0 * v1;
double dot02 = v0 * v2;
double dot11 = v1 * v1;
double dot12 = v1 * v2;
// Compute barycentric coordinates
double invDenom = 1.0 / ( dot00 * dot11 - dot01 * dot01 );
lam1 = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
lam2 = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;
lam3 = 1.0 - lam1 - lam2;
// Apply some tolerance to lam so we can detect correctly border points
lamTol( lam1 );
lamTol( lam2 );
lamTol( lam3 );
// Return if POI is outside triangle
if ( ( lam1 < 0 ) || ( lam2 < 0 ) || ( lam3 < 0 ) )
{
return false;
}
return true;
}
double QgsMeshLayerInterpolator::interpolateFromVerticesData( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
double val1, double val2, double val3, const QgsPointXY &pt )
{
double lam1, lam2, lam3;
if ( !E3T_physicalToBarycentric( p1, p2, p3, pt, lam1, lam2, lam3 ) )
return std::numeric_limits<double>::quiet_NaN();
return lam1 * val3 + lam2 * val2 + lam3 * val1;
}
double interpolateFromFacesData( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3, double val, const QgsPointXY &pt )
{
double lam1, lam2, lam3;
if ( !E3T_physicalToBarycentric( p1, p2, p3, pt, lam1, lam2, lam3 ) )
return std::numeric_limits<double>::quiet_NaN();
return val;
}
#include "qgsmeshlayerutils.h"
QgsMeshLayerInterpolator::QgsMeshLayerInterpolator( const QgsTriangularMesh &m,
const QVector<double> &datasetValues, const QVector<bool> &activeFaceFlagValues,
@ -167,16 +88,13 @@ QgsRasterBlock *QgsMeshLayerInterpolator::block( int, const QgsRectangle &extent
if ( !isActive )
continue;
QgsRectangle bbox;
bbox.combineExtentWith( p1.x(), p1.y() );
bbox.combineExtentWith( p2.x(), p2.y() );
bbox.combineExtentWith( p3.x(), p3.y() );
QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
if ( !extent.intersects( bbox ) )
continue;
// Get the BBox of the element in pixels
int topLim, bottomLim, leftLim, rightLim;
bbox2rect( mContext.mapToPixel(), mOutputSize, bbox, leftLim, rightLim, topLim, bottomLim );
QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, leftLim, rightLim, topLim, bottomLim );
// interpolate in the bounding box of the face
for ( int j = topLim; j <= bottomLim; j++ )
@ -187,7 +105,7 @@ QgsRasterBlock *QgsMeshLayerInterpolator::block( int, const QgsRectangle &extent
double val;
const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( k, j );
if ( mDataOnVertices )
val = interpolateFromVerticesData(
val = QgsMeshLayerUtils::interpolateFromVerticesData(
p1,
p2,
p3,
@ -198,7 +116,7 @@ QgsRasterBlock *QgsMeshLayerInterpolator::block( int, const QgsRectangle &extent
else
{
int face = mTriangularMesh.trianglesToNativeFaces()[i];
val = interpolateFromFacesData(
val = QgsMeshLayerUtils::interpolateFromFacesData(
p1,
p2,
p3,

View File

@ -60,22 +60,6 @@ class QgsMeshLayerInterpolator : public QgsRasterInterface
int bandCount() const override;
QgsRasterBlock *block( int, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback = nullptr ) override;
/*
* Interpolates value based on known values on the vertices of a triangle
* \param p1 first vertex of the triangle
* \param p2 second vertex of the triangle
* \param p3 third vertex of the triangle
* \param val1 value on p1 of the triangle
* \param val2 value on p2 of the triangle
* \param val3 value on p3 of the triangle
* \param pt point where to calculate value
* \returns value on the point pt or NaN in case the point is outside the triangle
*/
static double interpolateFromVerticesData(
const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
double val1, double val2, double val3, const QgsPointXY &pt
);
private:
const QgsTriangularMesh &mTriangularMesh;
const QVector<double> &mDatasetValues;

View File

@ -116,4 +116,96 @@ void QgsMeshLayerUtils::calculateMinMaxForDataset( double &min, double &max, Qgs
}
void QgsMeshLayerUtils::boundingBoxToScreenRectangle( const QgsMapToPixel &mtp,
const QSize &outputSize,
const QgsRectangle &bbox,
int &leftLim,
int &rightLim,
int &topLim,
int &bottomLim )
{
QgsPointXY ll = mtp.transform( bbox.xMinimum(), bbox.yMinimum() );
QgsPointXY ur = mtp.transform( bbox.xMaximum(), bbox.yMaximum() );
topLim = std::max( int( ur.y() ), 0 );
bottomLim = std::min( int( ll.y() ), outputSize.height() - 1 );
leftLim = std::max( int( ll.x() ), 0 );
rightLim = std::min( int( ur.x() ), outputSize.width() - 1 );
}
static void lamTol( double &lam )
{
const static double eps = 1e-6;
if ( ( lam < 0.0 ) && ( lam > -eps ) )
{
lam = 0.0;
}
}
static bool E3T_physicalToBarycentric( const QgsPointXY &pA, const QgsPointXY &pB, const QgsPointXY &pC, const QgsPointXY &pP,
double &lam1, double &lam2, double &lam3 )
{
if ( pA == pB || pA == pC || pB == pC )
return false; // this is not a valid triangle!
// Compute vectors
QgsVector v0( pC - pA );
QgsVector v1( pB - pA );
QgsVector v2( pP - pA );
// Compute dot products
double dot00 = v0 * v0;
double dot01 = v0 * v1;
double dot02 = v0 * v2;
double dot11 = v1 * v1;
double dot12 = v1 * v2;
// Compute barycentric coordinates
double invDenom = 1.0 / ( dot00 * dot11 - dot01 * dot01 );
lam1 = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
lam2 = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;
lam3 = 1.0 - lam1 - lam2;
// Apply some tolerance to lam so we can detect correctly border points
lamTol( lam1 );
lamTol( lam2 );
lamTol( lam3 );
// Return if POI is outside triangle
if ( ( lam1 < 0 ) || ( lam2 < 0 ) || ( lam3 < 0 ) )
{
return false;
}
return true;
}
double QgsMeshLayerUtils::interpolateFromVerticesData( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
double val1, double val2, double val3, const QgsPointXY &pt )
{
double lam1, lam2, lam3;
if ( !E3T_physicalToBarycentric( p1, p2, p3, pt, lam1, lam2, lam3 ) )
return std::numeric_limits<double>::quiet_NaN();
return lam1 * val3 + lam2 * val2 + lam3 * val1;
}
double QgsMeshLayerUtils::interpolateFromFacesData( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
double val, const QgsPointXY &pt )
{
double lam1, lam2, lam3;
if ( !E3T_physicalToBarycentric( p1, p2, p3, pt, lam1, lam2, lam3 ) )
return std::numeric_limits<double>::quiet_NaN();
return val;
}
QgsRectangle QgsMeshLayerUtils::triangleBoundingBox( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3 )
{
QgsRectangle bbox;
bbox.combineExtentWith( p1.x(), p1.y() );
bbox.combineExtentWith( p2.x(), p2.y() );
bbox.combineExtentWith( p3.x(), p3.y() );
return bbox;
}
///@endcond

View File

@ -21,11 +21,14 @@
#define SIP_NO_FILE
#include "qgis_core.h"
#include "qgsrectangle.h"
#include "qgsmaptopixel.h"
class QgsMeshDataProvider;
class QgsMeshDatasetIndex;
#include <QVector>
#include <QSize>
///@cond PRIVATE
@ -57,6 +60,60 @@ class CORE_EXPORT QgsMeshLayerUtils
* Ignores any NaN values in the input. Returns NaN for min/max on error.
*/
static void calculateMinMaxForDataset( double &min, double &max, QgsMeshDataProvider *provider, QgsMeshDatasetIndex index );
/**
* Transformes the bounding box to rectangle in screen coordinates (in pixels)
* \param mtp actual renderer map to pixel
* \param outputSize actual renderer output size
* \param bbox bounding box in map coordinates
* \param leftLim minimum x coordinate in pixel
* \param rightLim maximum x coordinate in pixel
* \param topLim minimum y coordinate in pixel
* \param bottomLim maximum y coordinate in pixel
*/
static void boundingBoxToScreenRectangle(
const QgsMapToPixel &mtp,
const QSize &outputSize,
const QgsRectangle &bbox,
int &leftLim, int &rightLim, int &topLim, int &bottomLim );
/**
* Interpolates value based on known values on the vertices of a triangle
* \param p1 first vertex of the triangle
* \param p2 second vertex of the triangle
* \param p3 third vertex of the triangle
* \param val1 value on p1 of the triangle
* \param val2 value on p2 of the triangle
* \param val3 value on p3 of the triangle
* \param pt point where to calculate value
* \returns value on the point pt or NaN in case the point is outside the triangle
*/
static double interpolateFromVerticesData(
const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
double val1, double val2, double val3, const QgsPointXY &pt
);
/**
* Interpolate value based on known value on the face of a triangle
* \param p1 first vertex of the triangle
* \param p2 second vertex of the triangle
* \param p3 third vertex of the triangle
* \param val face value
* \param pt point where to calculate value
* \returns value on the point pt or NaN in case the point is outside the triangle
*/
static double interpolateFromFacesData(
const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
double val, const QgsPointXY &pt );
/**
* Calculates the bounding box of the triangle
* \param p1 first vertex of the triangle
* \param p2 second vertex of the triangle
* \param p3 third vertex of the triangle
* \returns bounding box of the triangle
*/
static QgsRectangle triangleBoundingBox( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3 );
};
///@endcond

View File

@ -16,7 +16,6 @@
***************************************************************************/
#include "qgsmeshrenderersettings.h"
#include "qgssymbollayerutils.h"
@ -52,18 +51,18 @@ void QgsMeshRendererMeshSettings::setColor( const QColor &color )
QDomElement QgsMeshRendererMeshSettings::writeXml( QDomDocument &doc ) const
{
QDomElement elem = doc.createElement( "mesh-settings" );
elem.setAttribute( "enabled", mEnabled ? "1" : "0" );
elem.setAttribute( "line-width", mLineWidth );
elem.setAttribute( "color", QgsSymbolLayerUtils::encodeColor( mColor ) );
QDomElement elem = doc.createElement( QStringLiteral( "mesh-settings" ) );
elem.setAttribute( QStringLiteral( "enabled" ), mEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
elem.setAttribute( QStringLiteral( "line-width" ), mLineWidth );
elem.setAttribute( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
return elem;
}
void QgsMeshRendererMeshSettings::readXml( const QDomElement &elem )
{
mEnabled = elem.attribute( "enabled" ).toInt();
mLineWidth = elem.attribute( "line-width" ).toDouble();
mColor = QgsSymbolLayerUtils::decodeColor( elem.attribute( "color" ) );
mEnabled = elem.attribute( QStringLiteral( "enabled" ) ).toInt();
mLineWidth = elem.attribute( QStringLiteral( "line-width" ) ).toDouble();
mColor = QgsSymbolLayerUtils::decodeColor( elem.attribute( QStringLiteral( "color" ) ) );
}
// ---------------------------------------------------------------------
@ -94,10 +93,10 @@ void QgsMeshRendererScalarSettings::setOpacity( double opacity ) { mOpacity = op
QDomElement QgsMeshRendererScalarSettings::writeXml( QDomDocument &doc ) const
{
QDomElement elem = doc.createElement( "scalar-settings" );
elem.setAttribute( "min-val", mClassificationMinimum );
elem.setAttribute( "max-val", mClassificationMaximum );
elem.setAttribute( "opacity", mOpacity );
QDomElement elem = doc.createElement( QStringLiteral( "scalar-settings" ) );
elem.setAttribute( QStringLiteral( "min-val" ), mClassificationMinimum );
elem.setAttribute( QStringLiteral( "max-val" ), mClassificationMaximum );
elem.setAttribute( QStringLiteral( "opacity" ), mOpacity );
QDomElement elemShader = mColorRampShader.writeXml( doc );
elem.appendChild( elemShader );
return elem;
@ -105,9 +104,9 @@ QDomElement QgsMeshRendererScalarSettings::writeXml( QDomDocument &doc ) const
void QgsMeshRendererScalarSettings::readXml( const QDomElement &elem )
{
mClassificationMinimum = elem.attribute( "min-val" ).toDouble();
mClassificationMaximum = elem.attribute( "max-val" ).toDouble();
mOpacity = elem.attribute( "opacity" ).toDouble();
mClassificationMinimum = elem.attribute( QStringLiteral( "min-val" ) ).toDouble();
mClassificationMaximum = elem.attribute( QStringLiteral( "max-val" ) ).toDouble();
mOpacity = elem.attribute( QStringLiteral( "opacity" ) ).toDouble();
QDomElement elemShader = elem.firstChildElement( QStringLiteral( "colorrampshader" ) );
mColorRampShader.readXml( elemShader );
}
@ -224,65 +223,101 @@ void QgsMeshRendererVectorSettings::setArrowHeadLengthRatio( double vectorHeadLe
mArrowHeadLengthRatio = vectorHeadLengthRatio;
}
bool QgsMeshRendererVectorSettings::isOnUserDefinedGrid() const
{
return mOnUserDefinedGrid;
}
void QgsMeshRendererVectorSettings::setOnUserDefinedGrid( bool enabled )
{
mOnUserDefinedGrid = enabled;
}
int QgsMeshRendererVectorSettings::userGridCellWidth() const
{
return mUserGridCellWidth;
}
void QgsMeshRendererVectorSettings::setUserGridCellWidth( int width )
{
mUserGridCellWidth = width;
}
int QgsMeshRendererVectorSettings::userGridCellHeight() const
{
return mUserGridCellHeight;
}
void QgsMeshRendererVectorSettings::setUserGridCellHeight( int height )
{
mUserGridCellHeight = height;
}
QDomElement QgsMeshRendererVectorSettings::writeXml( QDomDocument &doc ) const
{
QDomElement elem = doc.createElement( "vector-settings" );
elem.setAttribute( "line-width", mLineWidth );
elem.setAttribute( "color", QgsSymbolLayerUtils::encodeColor( mColor ) );
elem.setAttribute( "filter-min", mFilterMin );
elem.setAttribute( "filter-max", mFilterMax );
elem.setAttribute( "arrow-head-width-ratio", mArrowHeadWidthRatio );
elem.setAttribute( "arrow-head-length-ratio", mArrowHeadLengthRatio );
QDomElement elem = doc.createElement( QStringLiteral( "vector-settings" ) );
elem.setAttribute( QStringLiteral( "line-width" ), mLineWidth );
elem.setAttribute( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
elem.setAttribute( QStringLiteral( "filter-min" ), mFilterMin );
elem.setAttribute( QStringLiteral( "filter-max" ), mFilterMax );
elem.setAttribute( QStringLiteral( "arrow-head-width-ratio" ), mArrowHeadWidthRatio );
elem.setAttribute( QStringLiteral( "arrow-head-length-ratio" ), mArrowHeadLengthRatio );
elem.setAttribute( QStringLiteral( "user-grid-enabled" ), mOnUserDefinedGrid ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
elem.setAttribute( QStringLiteral( "user-grid-width" ), mUserGridCellWidth );
elem.setAttribute( QStringLiteral( "user-grid-height" ), mUserGridCellHeight );
QDomElement elemShaft = doc.createElement( "shaft-length" );
QDomElement elemShaft = doc.createElement( QStringLiteral( "shaft-length" ) );
QString methodTxt;
switch ( mShaftLengthMethod )
{
case MinMax:
methodTxt = "minmax";
elemShaft.setAttribute( "min", mMinShaftLength );
elemShaft.setAttribute( "max", mMaxShaftLength );
methodTxt = QStringLiteral( "minmax" );
elemShaft.setAttribute( QStringLiteral( "min" ), mMinShaftLength );
elemShaft.setAttribute( QStringLiteral( "max" ), mMaxShaftLength );
break;
case Scaled:
methodTxt = "scaled";
elemShaft.setAttribute( "scale-factor", mScaleFactor );
methodTxt = QStringLiteral( "scaled" );
elemShaft.setAttribute( QStringLiteral( "scale-factor" ), mScaleFactor );
break;
case Fixed:
methodTxt = "fixed";
elemShaft.setAttribute( "fixed-length", mFixedShaftLength );
methodTxt = QStringLiteral( "fixed" ) ;
elemShaft.setAttribute( QStringLiteral( "fixed-length" ), mFixedShaftLength );
break;
}
elemShaft.setAttribute( "method", methodTxt );
elemShaft.setAttribute( QStringLiteral( "method" ), methodTxt );
elem.appendChild( elemShaft );
return elem;
}
void QgsMeshRendererVectorSettings::readXml( const QDomElement &elem )
{
mLineWidth = elem.attribute( "line-width" ).toDouble();
mColor = QgsSymbolLayerUtils::decodeColor( elem.attribute( "color" ) );
mFilterMin = elem.attribute( "filter-min" ).toDouble();
mFilterMax = elem.attribute( "filter-max" ).toDouble();
mArrowHeadWidthRatio = elem.attribute( "arrow-head-width-ratio" ).toDouble();
mArrowHeadLengthRatio = elem.attribute( "arrow-head-length-ratio" ).toDouble();
mLineWidth = elem.attribute( QStringLiteral( "line-width" ) ).toDouble();
mColor = QgsSymbolLayerUtils::decodeColor( elem.attribute( QStringLiteral( "color" ) ) );
mFilterMin = elem.attribute( QStringLiteral( "filter-min" ) ).toDouble();
mFilterMax = elem.attribute( QStringLiteral( "filter-max" ) ).toDouble();
mArrowHeadWidthRatio = elem.attribute( QStringLiteral( "arrow-head-width-ratio" ) ).toDouble();
mArrowHeadLengthRatio = elem.attribute( QStringLiteral( "arrow-head-length-ratio" ) ).toDouble();
mOnUserDefinedGrid = elem.attribute( QStringLiteral( "user-grid-enabled" ) ).toInt(); //bool
mUserGridCellWidth = elem.attribute( QStringLiteral( "user-grid-width" ) ).toInt();
mUserGridCellHeight = elem.attribute( QStringLiteral( "user-grid-height" ) ).toInt();
QDomElement elemShaft = elem.firstChildElement( "shaft-length" );
QString methodTxt = elemShaft.attribute( "method" );
if ( methodTxt == "minmax" )
QDomElement elemShaft = elem.firstChildElement( QStringLiteral( "shaft-length" ) );
QString methodTxt = elemShaft.attribute( QStringLiteral( "method" ) );
if ( QStringLiteral( "minmax" ) == methodTxt )
{
mShaftLengthMethod = MinMax;
mMinShaftLength = elemShaft.attribute( "min" ).toDouble();
mMaxShaftLength = elemShaft.attribute( "max" ).toDouble();
mMinShaftLength = elemShaft.attribute( QStringLiteral( "min" ) ).toDouble();
mMaxShaftLength = elemShaft.attribute( QStringLiteral( "max" ) ).toDouble();
}
else if ( methodTxt == "scaled" )
else if ( QStringLiteral( "scaled" ) == methodTxt )
{
mShaftLengthMethod = Scaled;
mScaleFactor = elemShaft.attribute( "scale-factor" ).toDouble();
mScaleFactor = elemShaft.attribute( QStringLiteral( "scale-factor" ) ).toDouble();
}
else // fixed
{
mShaftLengthMethod = Fixed;
mFixedShaftLength = elemShaft.attribute( "fixed-length" ).toDouble();
mFixedShaftLength = elemShaft.attribute( QStringLiteral( "fixed-length" ) ).toDouble();
}
}
@ -290,20 +325,20 @@ void QgsMeshRendererVectorSettings::readXml( const QDomElement &elem )
QDomElement QgsMeshRendererSettings::writeXml( QDomDocument &doc ) const
{
QDomElement elem = doc.createElement( "mesh-renderer-settings" );
QDomElement elem = doc.createElement( QStringLiteral( "mesh-renderer-settings" ) );
QDomElement elemActiveDataset = doc.createElement( "active-dataset" );
QDomElement elemActiveDataset = doc.createElement( QStringLiteral( "active-dataset" ) );
if ( mActiveScalarDataset.isValid() )
elemActiveDataset.setAttribute( "scalar", QString( "%1,%2" ).arg( mActiveScalarDataset.group() ).arg( mActiveScalarDataset.dataset() ) );
elemActiveDataset.setAttribute( QStringLiteral( "scalar" ), QStringLiteral( "%1,%2" ).arg( mActiveScalarDataset.group() ).arg( mActiveScalarDataset.dataset() ) );
if ( mActiveVectorDataset.isValid() )
elemActiveDataset.setAttribute( "vector", QString( "%1,%2" ).arg( mActiveVectorDataset.group() ).arg( mActiveVectorDataset.dataset() ) );
elemActiveDataset.setAttribute( QStringLiteral( "vector" ), QStringLiteral( "%1,%2" ).arg( mActiveVectorDataset.group() ).arg( mActiveVectorDataset.dataset() ) );
elem.appendChild( elemActiveDataset );
for ( int groupIndex : mRendererScalarSettings.keys() )
{
const QgsMeshRendererScalarSettings &scalarSettings = mRendererScalarSettings[groupIndex];
QDomElement elemScalar = scalarSettings.writeXml( doc );
elemScalar.setAttribute( "group", groupIndex );
elemScalar.setAttribute( QStringLiteral( "group" ), groupIndex );
elem.appendChild( elemScalar );
}
@ -311,16 +346,16 @@ QDomElement QgsMeshRendererSettings::writeXml( QDomDocument &doc ) const
{
const QgsMeshRendererVectorSettings &vectorSettings = mRendererVectorSettings[groupIndex];
QDomElement elemVector = vectorSettings.writeXml( doc );
elemVector.setAttribute( "group", groupIndex );
elemVector.setAttribute( QStringLiteral( "group" ), groupIndex );
elem.appendChild( elemVector );
}
QDomElement elemNativeMesh = mRendererNativeMeshSettings.writeXml( doc );
elemNativeMesh.setTagName( "mesh-settings-native" );
elemNativeMesh.setTagName( QStringLiteral( "mesh-settings-native" ) );
elem.appendChild( elemNativeMesh );
QDomElement elemTriangularMesh = mRendererTriangularMeshSettings.writeXml( doc );
elemTriangularMesh.setTagName( "mesh-settings-triangular" );
elemTriangularMesh.setTagName( QStringLiteral( "mesh-settings-triangular" ) );
elem.appendChild( elemTriangularMesh );
return elem;
@ -331,45 +366,45 @@ void QgsMeshRendererSettings::readXml( const QDomElement &elem )
mRendererScalarSettings.clear();
mRendererVectorSettings.clear();
QDomElement elemActiveDataset = elem.firstChildElement( "active-dataset" );
if ( elemActiveDataset.hasAttribute( "scalar" ) )
QDomElement elemActiveDataset = elem.firstChildElement( QStringLiteral( "active-dataset" ) );
if ( elemActiveDataset.hasAttribute( QStringLiteral( "scalar" ) ) )
{
QStringList lst = elemActiveDataset.attribute( "scalar" ).split( QChar( ',' ) );
QStringList lst = elemActiveDataset.attribute( QStringLiteral( "scalar" ) ).split( QChar( ',' ) );
if ( lst.count() == 2 )
mActiveScalarDataset = QgsMeshDatasetIndex( lst[0].toInt(), lst[1].toInt() );
}
if ( elemActiveDataset.hasAttribute( "vector" ) )
if ( elemActiveDataset.hasAttribute( QStringLiteral( "vector" ) ) )
{
QStringList lst = elemActiveDataset.attribute( "vector" ).split( QChar( ',' ) );
QStringList lst = elemActiveDataset.attribute( QStringLiteral( "vector" ) ).split( QChar( ',' ) );
if ( lst.count() == 2 )
mActiveVectorDataset = QgsMeshDatasetIndex( lst[0].toInt(), lst[1].toInt() );
}
QDomElement elemScalar = elem.firstChildElement( "scalar-settings" );
QDomElement elemScalar = elem.firstChildElement( QStringLiteral( "scalar-settings" ) );
while ( !elemScalar.isNull() )
{
int groupIndex = elemScalar.attribute( "group" ).toInt();
int groupIndex = elemScalar.attribute( QStringLiteral( "group" ) ).toInt();
QgsMeshRendererScalarSettings scalarSettings;
scalarSettings.readXml( elemScalar );
mRendererScalarSettings.insert( groupIndex, scalarSettings );
elemScalar = elemScalar.nextSiblingElement( "scalar-settings" );
elemScalar = elemScalar.nextSiblingElement( QStringLiteral( "scalar-settings" ) );
}
QDomElement elemVector = elem.firstChildElement( "vector-settings" );
QDomElement elemVector = elem.firstChildElement( QStringLiteral( "vector-settings" ) );
while ( !elemVector.isNull() )
{
int groupIndex = elemVector.attribute( "group" ).toInt();
int groupIndex = elemVector.attribute( QStringLiteral( "group" ) ).toInt();
QgsMeshRendererVectorSettings vectorSettings;
vectorSettings.readXml( elemVector );
mRendererVectorSettings.insert( groupIndex, vectorSettings );
elemVector = elemVector.nextSiblingElement( "vector-settings" );
elemVector = elemVector.nextSiblingElement( QStringLiteral( "vector-settings" ) );
}
QDomElement elemNativeMesh = elem.firstChildElement( "mesh-settings-native" );
QDomElement elemNativeMesh = elem.firstChildElement( QStringLiteral( "mesh-settings-native" ) );
mRendererNativeMeshSettings.readXml( elemNativeMesh );
QDomElement elemTriangularMesh = elem.firstChildElement( "mesh-settings-triangular" );
QDomElement elemTriangularMesh = elem.firstChildElement( QStringLiteral( "mesh-settings-triangular" ) );
mRendererTriangularMeshSettings.readXml( elemTriangularMesh );
}

View File

@ -248,6 +248,19 @@ class CORE_EXPORT QgsMeshRendererVectorSettings
//! Sets ratio of the head length of the arrow (range 0-1)
void setArrowHeadLengthRatio( double arrowHeadLengthRatio );
//! Returns whether vectors are drawn on user-defined grid
bool isOnUserDefinedGrid() const;
//! Toggles drawing of vectors on user defined grid
void setOnUserDefinedGrid( bool enabled );
//! Returns width in pixels of user grid cell
int userGridCellWidth() const;
//! Sets width of user grid cell (in pixels)
void setUserGridCellWidth( int width );
//! Returns height in pixels of user grid cell
int userGridCellHeight() const;
//! Sets height of user grid cell (in pixels)
void setUserGridCellHeight( int height );
//! Writes configuration to a new DOM element
QDomElement writeXml( QDomDocument &doc ) const;
//! Reads configuration from the given DOM element
@ -265,6 +278,9 @@ class CORE_EXPORT QgsMeshRendererVectorSettings
double mFixedShaftLength = 20; //in milimeters
double mArrowHeadWidthRatio = 0.15;
double mArrowHeadLengthRatio = 0.40;
bool mOnUserDefinedGrid = false;
int mUserGridCellWidth = 10; // in pixels
int mUserGridCellHeight = 10; // in pixels
};

View File

@ -20,6 +20,7 @@
#include "qgscoordinatetransform.h"
#include "qgsmaptopixel.h"
#include "qgsunittypes.h"
#include "qgsmeshlayerutils.h"
#include <cstdlib>
#include <ctime>
@ -91,7 +92,9 @@ void QgsMeshVectorRenderer::draw()
pen.setColor( mCfg.color() );
painter->setPen( pen );
if ( mDataOnVertices )
if ( mCfg.isOnUserDefinedGrid() )
drawVectorDataOnGrid();
else if ( mDataOnVertices )
drawVectorDataOnVertices();
else
drawVectorDataOnFaces();
@ -234,6 +237,94 @@ void QgsMeshVectorRenderer::drawVectorDataOnFaces()
}
}
void QgsMeshVectorRenderer::drawVectorDataOnGrid()
{
int cellx = mCfg.userGridCellWidth();
int celly = mCfg.userGridCellHeight();
const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
for ( int i = 0; i < triangles.size(); i++ )
{
const QgsMeshFace &face = triangles[i];
const int v1 = face[0], v2 = face[1], v3 = face[2];
const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
if ( !mContext.extent().intersects( bbox ) )
continue;
// Get the BBox of the element in pixels
int left, right, top, bottom;
QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, left, right, top, bottom );
// Align rect to the grid (e.g. interval <13, 36> with grid cell 10 will be trimmed to <20,30>
if ( left % cellx != 0 )
left += cellx - ( left % cellx );
if ( right % cellx != 0 )
right -= ( right % cellx );
if ( top % celly != 0 )
top += celly - ( top % celly );
if ( bottom % celly != 0 )
bottom -= ( bottom % celly );
for ( int y = top; y <= bottom; y += celly )
{
for ( int x = left; x <= right; x += cellx )
{
QgsMeshDatasetValue val;
const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
if ( mDataOnVertices )
{
val.setX(
QgsMeshLayerUtils::interpolateFromVerticesData(
p1, p2, p3,
mDatasetValuesX[v1],
mDatasetValuesX[v2],
mDatasetValuesX[v3],
p )
);
val.setY(
QgsMeshLayerUtils::interpolateFromVerticesData(
p1, p2, p3,
mDatasetValuesY[v1],
mDatasetValuesY[v2],
mDatasetValuesY[v3],
p )
);
}
else
{
val.setX(
QgsMeshLayerUtils::interpolateFromFacesData(
p1, p2, p3,
mDatasetValuesX[nativeFaceIndex],
p
)
);
val.setY(
QgsMeshLayerUtils::interpolateFromFacesData(
p1, p2, p3,
mDatasetValuesY[nativeFaceIndex],
p
)
);
}
if ( nodataValue( val.x(), val.y() ) )
continue;
QgsPointXY lineStart( x, y );
drawVectorArrow( lineStart, val.x(), val.y(), val.scalar() );
}
}
}
}
void QgsMeshVectorRenderer::drawVectorArrow( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
{

View File

@ -68,6 +68,8 @@ class QgsMeshVectorRenderer
void drawVectorDataOnVertices();
//! Draws for data defined on face centers
void drawVectorDataOnFaces();
//! Draws data on user-defined grid
void drawVectorDataOnGrid();
//! Draws arrow from start point and vector data
void drawVectorArrow( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude );
//! Calculates the end point of the arrow based on start point and vector data

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>310</width>
<height>419</height>
<width>287</width>
<height>520</height>
</rect>
</property>
<property name="windowTitle">
@ -61,6 +61,90 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="mDisplayVectorsOnGridGroupBox">
<property name="title">
<string>Display Vectors on User Grid</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="xSpacingLabel">
<property name="text">
<string>X Spacing</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="ySpacingLabel">
<property name="text">
<string>Y Spacing</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QgsSpinBox" name="mXSpacingSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>8000</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="mYSpacingSpinBox">
<property name="suffix">
<string>px</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="headOptionsGroupBox">
<property name="title">
@ -216,16 +300,21 @@
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsColorButton</class>
<extends>QPushButton</extends>
<header>qgscolorbutton.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsDoubleSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>qgsdoublespinbox.h</header>
</customwidget>
<customwidget>
<class>QgsColorButton</class>
<extends>QFrame</extends>
<header>qgscolorbutton.h</header>
<container>1</container>
<class>QgsSpinBox</class>
<extends>QSpinBox</extends>
<header>qgsspinbox.h</header>
</customwidget>
</customwidgets>
<resources/>

View File

@ -73,6 +73,8 @@ class TestQgsMeshRenderer : public QObject
void test_face_scalar_dataset_rendering();
void test_face_vector_dataset_rendering();
void test_vertex_scalar_dataset_with_inactive_face_rendering();
void test_face_vector_on_user_grid();
void test_vertex_vector_on_user_grid();
void test_signals();
};
@ -266,6 +268,44 @@ void TestQgsMeshRenderer::test_vertex_scalar_dataset_with_inactive_face_renderin
QVERIFY( imageCheck( "quad_and_triangle_vertex_scalar_dataset_with_inactive_face", mMdalLayer ) );
}
void TestQgsMeshRenderer::test_face_vector_on_user_grid()
{
QgsMeshDatasetIndex ds( 3, 0 );
const QgsMeshDatasetGroupMetadata metadata = mMemoryLayer->dataProvider()->datasetGroupMetadata( ds );
QVERIFY( metadata.name() == "FaceVectorDataset" );
QgsMeshRendererSettings rendererSettings = mMemoryLayer->rendererSettings();
QgsMeshRendererVectorSettings settings = rendererSettings.vectorSettings( ds.group() );
settings.setOnUserDefinedGrid( true );
settings.setUserGridCellWidth( 30 );
settings.setUserGridCellHeight( 20 );
settings.setLineWidth( 0.8 );
rendererSettings.setVectorSettings( ds.group(), settings );
rendererSettings.setActiveVectorDataset( ds );
mMemoryLayer->setRendererSettings( rendererSettings );
QVERIFY( imageCheck( "quad_and_triangle_face_vector_user_grid_dataset", mMemoryLayer ) );
}
void TestQgsMeshRenderer::test_vertex_vector_on_user_grid()
{
QgsMeshDatasetIndex ds( 1, 0 );
const QgsMeshDatasetGroupMetadata metadata = mMemoryLayer->dataProvider()->datasetGroupMetadata( ds );
QVERIFY( metadata.name() == "VertexVectorDataset" );
QgsMeshRendererSettings rendererSettings = mMemoryLayer->rendererSettings();
QgsMeshRendererVectorSettings settings = rendererSettings.vectorSettings( ds.group() );
settings.setOnUserDefinedGrid( true );
settings.setUserGridCellWidth( 60 );
settings.setUserGridCellHeight( 40 );
settings.setLineWidth( 0.9 );
rendererSettings.setVectorSettings( ds.group(), settings );
rendererSettings.setActiveVectorDataset( ds );
mMemoryLayer->setRendererSettings( rendererSettings );
QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset", mMemoryLayer ) );
}
void TestQgsMeshRenderer::test_signals()
{
QSignalSpy spy1( mMemoryLayer, &QgsMapLayer::rendererChanged );