Skip to content

Commit 1839daa

Browse files
authoredSep 6, 2018
Merge pull request #7777 from PeterPetrik/mesh_vector_on_grid
[mesh] [feature] allow render vectors/arrows on the user-defined grid
2 parents da670f9 + 20a5bce commit 1839daa

15 files changed

+546
-182
lines changed
 

‎python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,31 @@ Returns ratio of the head length of the arrow (range 0-1)
282282
void setArrowHeadLengthRatio( double arrowHeadLengthRatio );
283283
%Docstring
284284
Sets ratio of the head length of the arrow (range 0-1)
285+
%End
286+
287+
bool isOnUserDefinedGrid() const;
288+
%Docstring
289+
Returns whether vectors are drawn on user-defined grid
290+
%End
291+
void setOnUserDefinedGrid( bool enabled );
292+
%Docstring
293+
Toggles drawing of vectors on user defined grid
294+
%End
295+
int userGridCellWidth() const;
296+
%Docstring
297+
Returns width in pixels of user grid cell
298+
%End
299+
void setUserGridCellWidth( int width );
300+
%Docstring
301+
Sets width of user grid cell (in pixels)
302+
%End
303+
int userGridCellHeight() const;
304+
%Docstring
305+
Returns height in pixels of user grid cell
306+
%End
307+
void setUserGridCellHeight( int height );
308+
%Docstring
309+
Sets height of user grid cell (in pixels)
285310
%End
286311

287312
QDomElement writeXml( QDomDocument &doc ) const;

‎src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#include "qgsmeshlayer.h"
2020
#include "qgsmessagelog.h"
2121

22-
2322
QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidget *parent )
2423
: QWidget( parent )
2524

@@ -37,6 +36,9 @@ QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidge
3736

3837
connect( mShaftLengthComboBox, qgis::overload<int>::of( &QComboBox::currentIndexChanged ),
3938
mShaftOptionsStackedWidget, &QStackedWidget::setCurrentIndex );
39+
40+
connect( mDisplayVectorsOnGridGroupBox, &QGroupBox::toggled, this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
41+
4042
QVector<QLineEdit *> widgets;
4143
widgets << mMinMagLineEdit << mMaxMagLineEdit
4244
<< mHeadWidthLineEdit << mHeadLengthLineEdit
@@ -47,6 +49,9 @@ QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidge
4749
{
4850
connect( widget, &QLineEdit::textChanged, this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
4951
}
52+
53+
connect( mXSpacingSpinBox, qgis::overload<int>::of( &QgsSpinBox::valueChanged ), this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
54+
connect( mYSpacingSpinBox, qgis::overload<int>::of( &QgsSpinBox::valueChanged ), this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
5055
}
5156

5257
void QgsMeshRendererVectorSettingsWidget::setLayer( QgsMeshLayer *layer )
@@ -76,6 +81,12 @@ QgsMeshRendererVectorSettings QgsMeshRendererVectorSettingsWidget::settings() co
7681
val = filterValue( mHeadLengthLineEdit->text(), settings.arrowHeadLengthRatio() * 100.0 );
7782
settings.setArrowHeadLengthRatio( val / 100.0 );
7883

84+
// user grid
85+
bool enabled = mDisplayVectorsOnGridGroupBox->isChecked();
86+
settings.setOnUserDefinedGrid( enabled );
87+
settings.setUserGridCellWidth( mXSpacingSpinBox->value() );
88+
settings.setUserGridCellHeight( mYSpacingSpinBox->value() );
89+
7990
// shaft length
8091
auto method = static_cast<QgsMeshRendererVectorSettings::ArrowScalingMethod>( mShaftLengthComboBox->currentIndex() );
8192
settings.setShaftLengthMethod( method );
@@ -124,6 +135,11 @@ void QgsMeshRendererVectorSettingsWidget::syncToLayer( )
124135
mHeadWidthLineEdit->setText( QString::number( settings.arrowHeadWidthRatio() * 100.0 ) );
125136
mHeadLengthLineEdit->setText( QString::number( settings.arrowHeadLengthRatio() * 100.0 ) );
126137

138+
// user grid
139+
mDisplayVectorsOnGridGroupBox->setChecked( settings.isOnUserDefinedGrid() );
140+
mXSpacingSpinBox->setValue( settings.userGridCellWidth() );
141+
mYSpacingSpinBox->setValue( settings.userGridCellHeight() );
142+
127143
// shaft length
128144
mShaftLengthComboBox->setCurrentIndex( settings.shaftLengthMethod() );
129145

‎src/core/mesh/qgsmeshlayer.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
#include "qgsmaplayerlegend.h"
2626
#include "qgsmeshdataprovider.h"
2727
#include "qgsmeshlayer.h"
28-
#include "qgsmeshlayerinterpolator.h"
2928
#include "qgsmeshlayerrenderer.h"
3029
#include "qgsmeshlayerutils.h"
3130
#include "qgsproviderregistry.h"
@@ -144,11 +143,11 @@ QgsMeshDatasetValue QgsMeshLayer::datasetValue( const QgsMeshDatasetIndex &index
144143
const QgsMeshDatasetValue val1 = dataProvider()->datasetValue( index, v1 );
145144
const QgsMeshDatasetValue val2 = dataProvider()->datasetValue( index, v2 );
146145
const QgsMeshDatasetValue val3 = dataProvider()->datasetValue( index, v3 );
147-
const double x = QgsMeshLayerInterpolator::interpolateFromVerticesData( p1, p2, p3, val1.x(), val2.x(), val3.x(), point );
146+
const double x = QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.x(), val2.x(), val3.x(), point );
148147
double y = std::numeric_limits<double>::quiet_NaN();
149148
bool isVector = dataProvider()->datasetGroupMetadata( index ).isVector();
150149
if ( isVector )
151-
y = QgsMeshLayerInterpolator::interpolateFromVerticesData( p1, p2, p3, val1.y(), val2.y(), val3.y(), point );
150+
y = QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.y(), val2.y(), val3.y(), point );
152151

153152
value = QgsMeshDatasetValue( x, y );
154153
}

‎src/core/mesh/qgsmeshlayerinterpolator.cpp

Lines changed: 5 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -26,86 +26,7 @@
2626
#include "qgsvector.h"
2727
#include "qgspoint.h"
2828
#include "qgspointxy.h"
29-
30-
static void bbox2rect(
31-
const QgsMapToPixel &mtp,
32-
const QSize &outputSize,
33-
const QgsRectangle &bbox,
34-
int &leftLim, int &rightLim, int &topLim, int &bottomLim )
35-
{
36-
QgsPointXY ll = mtp.transform( bbox.xMinimum(), bbox.yMinimum() );
37-
QgsPointXY ur = mtp.transform( bbox.xMaximum(), bbox.yMaximum() );
38-
topLim = std::max( int( ur.y() ), 0 );
39-
bottomLim = std::min( int( ll.y() ), outputSize.height() - 1 );
40-
leftLim = std::max( int( ll.x() ), 0 );
41-
rightLim = std::min( int( ur.x() ), outputSize.width() - 1 );
42-
}
43-
44-
static void lamTol( double &lam )
45-
{
46-
const static double eps = 1e-6;
47-
if ( ( lam < 0.0 ) && ( lam > -eps ) )
48-
{
49-
lam = 0.0;
50-
}
51-
}
52-
53-
static bool E3T_physicalToBarycentric( const QgsPointXY &pA, const QgsPointXY &pB, const QgsPointXY &pC, const QgsPointXY &pP,
54-
double &lam1, double &lam2, double &lam3 )
55-
{
56-
if ( pA == pB || pA == pC || pB == pC )
57-
return false; // this is not a valid triangle!
58-
59-
// Compute vectors
60-
QgsVector v0( pC - pA );
61-
QgsVector v1( pB - pA );
62-
QgsVector v2( pP - pA );
63-
64-
// Compute dot products
65-
double dot00 = v0 * v0;
66-
double dot01 = v0 * v1;
67-
double dot02 = v0 * v2;
68-
double dot11 = v1 * v1;
69-
double dot12 = v1 * v2;
70-
71-
// Compute barycentric coordinates
72-
double invDenom = 1.0 / ( dot00 * dot11 - dot01 * dot01 );
73-
lam1 = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
74-
lam2 = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;
75-
lam3 = 1.0 - lam1 - lam2;
76-
77-
// Apply some tolerance to lam so we can detect correctly border points
78-
lamTol( lam1 );
79-
lamTol( lam2 );
80-
lamTol( lam3 );
81-
82-
// Return if POI is outside triangle
83-
if ( ( lam1 < 0 ) || ( lam2 < 0 ) || ( lam3 < 0 ) )
84-
{
85-
return false;
86-
}
87-
88-
return true;
89-
}
90-
91-
double QgsMeshLayerInterpolator::interpolateFromVerticesData( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
92-
double val1, double val2, double val3, const QgsPointXY &pt )
93-
{
94-
double lam1, lam2, lam3;
95-
if ( !E3T_physicalToBarycentric( p1, p2, p3, pt, lam1, lam2, lam3 ) )
96-
return std::numeric_limits<double>::quiet_NaN();
97-
98-
return lam1 * val3 + lam2 * val2 + lam3 * val1;
99-
}
100-
101-
double interpolateFromFacesData( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3, double val, const QgsPointXY &pt )
102-
{
103-
double lam1, lam2, lam3;
104-
if ( !E3T_physicalToBarycentric( p1, p2, p3, pt, lam1, lam2, lam3 ) )
105-
return std::numeric_limits<double>::quiet_NaN();
106-
107-
return val;
108-
}
29+
#include "qgsmeshlayerutils.h"
10930

11031
QgsMeshLayerInterpolator::QgsMeshLayerInterpolator( const QgsTriangularMesh &m,
11132
const QVector<double> &datasetValues, const QVector<bool> &activeFaceFlagValues,
@@ -167,16 +88,13 @@ QgsRasterBlock *QgsMeshLayerInterpolator::block( int, const QgsRectangle &extent
16788
if ( !isActive )
16889
continue;
16990

170-
QgsRectangle bbox;
171-
bbox.combineExtentWith( p1.x(), p1.y() );
172-
bbox.combineExtentWith( p2.x(), p2.y() );
173-
bbox.combineExtentWith( p3.x(), p3.y() );
91+
QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
17492
if ( !extent.intersects( bbox ) )
17593
continue;
17694

17795
// Get the BBox of the element in pixels
17896
int topLim, bottomLim, leftLim, rightLim;
179-
bbox2rect( mContext.mapToPixel(), mOutputSize, bbox, leftLim, rightLim, topLim, bottomLim );
97+
QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, leftLim, rightLim, topLim, bottomLim );
18098

18199
// interpolate in the bounding box of the face
182100
for ( int j = topLim; j <= bottomLim; j++ )
@@ -187,7 +105,7 @@ QgsRasterBlock *QgsMeshLayerInterpolator::block( int, const QgsRectangle &extent
187105
double val;
188106
const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( k, j );
189107
if ( mDataOnVertices )
190-
val = interpolateFromVerticesData(
108+
val = QgsMeshLayerUtils::interpolateFromVerticesData(
191109
p1,
192110
p2,
193111
p3,
@@ -198,7 +116,7 @@ QgsRasterBlock *QgsMeshLayerInterpolator::block( int, const QgsRectangle &extent
198116
else
199117
{
200118
int face = mTriangularMesh.trianglesToNativeFaces()[i];
201-
val = interpolateFromFacesData(
119+
val = QgsMeshLayerUtils::interpolateFromFacesData(
202120
p1,
203121
p2,
204122
p3,

‎src/core/mesh/qgsmeshlayerinterpolator.h

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,6 @@ class QgsMeshLayerInterpolator : public QgsRasterInterface
6060
int bandCount() const override;
6161
QgsRasterBlock *block( int, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback = nullptr ) override;
6262

63-
/*
64-
* Interpolates value based on known values on the vertices of a triangle
65-
* \param p1 first vertex of the triangle
66-
* \param p2 second vertex of the triangle
67-
* \param p3 third vertex of the triangle
68-
* \param val1 value on p1 of the triangle
69-
* \param val2 value on p2 of the triangle
70-
* \param val3 value on p3 of the triangle
71-
* \param pt point where to calculate value
72-
* \returns value on the point pt or NaN in case the point is outside the triangle
73-
*/
74-
static double interpolateFromVerticesData(
75-
const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
76-
double val1, double val2, double val3, const QgsPointXY &pt
77-
);
78-
7963
private:
8064
const QgsTriangularMesh &mTriangularMesh;
8165
const QVector<double> &mDatasetValues;

‎src/core/mesh/qgsmeshlayerutils.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,96 @@ void QgsMeshLayerUtils::calculateMinMaxForDataset( double &min, double &max, Qgs
116116

117117
}
118118

119+
void QgsMeshLayerUtils::boundingBoxToScreenRectangle( const QgsMapToPixel &mtp,
120+
const QSize &outputSize,
121+
const QgsRectangle &bbox,
122+
int &leftLim,
123+
int &rightLim,
124+
int &topLim,
125+
int &bottomLim )
126+
{
127+
QgsPointXY ll = mtp.transform( bbox.xMinimum(), bbox.yMinimum() );
128+
QgsPointXY ur = mtp.transform( bbox.xMaximum(), bbox.yMaximum() );
129+
topLim = std::max( int( ur.y() ), 0 );
130+
bottomLim = std::min( int( ll.y() ), outputSize.height() - 1 );
131+
leftLim = std::max( int( ll.x() ), 0 );
132+
rightLim = std::min( int( ur.x() ), outputSize.width() - 1 );
133+
}
134+
135+
static void lamTol( double &lam )
136+
{
137+
const static double eps = 1e-6;
138+
if ( ( lam < 0.0 ) && ( lam > -eps ) )
139+
{
140+
lam = 0.0;
141+
}
142+
}
143+
144+
static bool E3T_physicalToBarycentric( const QgsPointXY &pA, const QgsPointXY &pB, const QgsPointXY &pC, const QgsPointXY &pP,
145+
double &lam1, double &lam2, double &lam3 )
146+
{
147+
if ( pA == pB || pA == pC || pB == pC )
148+
return false; // this is not a valid triangle!
149+
150+
// Compute vectors
151+
QgsVector v0( pC - pA );
152+
QgsVector v1( pB - pA );
153+
QgsVector v2( pP - pA );
154+
155+
// Compute dot products
156+
double dot00 = v0 * v0;
157+
double dot01 = v0 * v1;
158+
double dot02 = v0 * v2;
159+
double dot11 = v1 * v1;
160+
double dot12 = v1 * v2;
161+
162+
// Compute barycentric coordinates
163+
double invDenom = 1.0 / ( dot00 * dot11 - dot01 * dot01 );
164+
lam1 = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
165+
lam2 = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;
166+
lam3 = 1.0 - lam1 - lam2;
167+
168+
// Apply some tolerance to lam so we can detect correctly border points
169+
lamTol( lam1 );
170+
lamTol( lam2 );
171+
lamTol( lam3 );
172+
173+
// Return if POI is outside triangle
174+
if ( ( lam1 < 0 ) || ( lam2 < 0 ) || ( lam3 < 0 ) )
175+
{
176+
return false;
177+
}
178+
179+
return true;
180+
}
181+
182+
double QgsMeshLayerUtils::interpolateFromVerticesData( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
183+
double val1, double val2, double val3, const QgsPointXY &pt )
184+
{
185+
double lam1, lam2, lam3;
186+
if ( !E3T_physicalToBarycentric( p1, p2, p3, pt, lam1, lam2, lam3 ) )
187+
return std::numeric_limits<double>::quiet_NaN();
188+
189+
return lam1 * val3 + lam2 * val2 + lam3 * val1;
190+
}
191+
192+
double QgsMeshLayerUtils::interpolateFromFacesData( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
193+
double val, const QgsPointXY &pt )
194+
{
195+
double lam1, lam2, lam3;
196+
if ( !E3T_physicalToBarycentric( p1, p2, p3, pt, lam1, lam2, lam3 ) )
197+
return std::numeric_limits<double>::quiet_NaN();
198+
199+
return val;
200+
}
201+
202+
QgsRectangle QgsMeshLayerUtils::triangleBoundingBox( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3 )
203+
{
204+
QgsRectangle bbox;
205+
bbox.combineExtentWith( p1.x(), p1.y() );
206+
bbox.combineExtentWith( p2.x(), p2.y() );
207+
bbox.combineExtentWith( p3.x(), p3.y() );
208+
return bbox;
209+
}
210+
119211
///@endcond

‎src/core/mesh/qgsmeshlayerutils.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
#define SIP_NO_FILE
2222

2323
#include "qgis_core.h"
24+
#include "qgsrectangle.h"
25+
#include "qgsmaptopixel.h"
2426

2527
class QgsMeshDataProvider;
2628
class QgsMeshDatasetIndex;
2729

2830
#include <QVector>
31+
#include <QSize>
2932

3033
///@cond PRIVATE
3134

@@ -57,6 +60,60 @@ class CORE_EXPORT QgsMeshLayerUtils
5760
* Ignores any NaN values in the input. Returns NaN for min/max on error.
5861
*/
5962
static void calculateMinMaxForDataset( double &min, double &max, QgsMeshDataProvider *provider, QgsMeshDatasetIndex index );
63+
64+
/**
65+
* Transformes the bounding box to rectangle in screen coordinates (in pixels)
66+
* \param mtp actual renderer map to pixel
67+
* \param outputSize actual renderer output size
68+
* \param bbox bounding box in map coordinates
69+
* \param leftLim minimum x coordinate in pixel
70+
* \param rightLim maximum x coordinate in pixel
71+
* \param topLim minimum y coordinate in pixel
72+
* \param bottomLim maximum y coordinate in pixel
73+
*/
74+
static void boundingBoxToScreenRectangle(
75+
const QgsMapToPixel &mtp,
76+
const QSize &outputSize,
77+
const QgsRectangle &bbox,
78+
int &leftLim, int &rightLim, int &topLim, int &bottomLim );
79+
80+
/**
81+
* Interpolates value based on known values on the vertices of a triangle
82+
* \param p1 first vertex of the triangle
83+
* \param p2 second vertex of the triangle
84+
* \param p3 third vertex of the triangle
85+
* \param val1 value on p1 of the triangle
86+
* \param val2 value on p2 of the triangle
87+
* \param val3 value on p3 of the triangle
88+
* \param pt point where to calculate value
89+
* \returns value on the point pt or NaN in case the point is outside the triangle
90+
*/
91+
static double interpolateFromVerticesData(
92+
const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
93+
double val1, double val2, double val3, const QgsPointXY &pt
94+
);
95+
96+
/**
97+
* Interpolate value based on known value on the face of a triangle
98+
* \param p1 first vertex of the triangle
99+
* \param p2 second vertex of the triangle
100+
* \param p3 third vertex of the triangle
101+
* \param val face value
102+
* \param pt point where to calculate value
103+
* \returns value on the point pt or NaN in case the point is outside the triangle
104+
*/
105+
static double interpolateFromFacesData(
106+
const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
107+
double val, const QgsPointXY &pt );
108+
109+
/**
110+
* Calculates the bounding box of the triangle
111+
* \param p1 first vertex of the triangle
112+
* \param p2 second vertex of the triangle
113+
* \param p3 third vertex of the triangle
114+
* \returns bounding box of the triangle
115+
*/
116+
static QgsRectangle triangleBoundingBox( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3 );
60117
};
61118

62119
///@endcond

‎src/core/mesh/qgsmeshrenderersettings.cpp

Lines changed: 103 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
***************************************************************************/
1717

1818
#include "qgsmeshrenderersettings.h"
19-
2019
#include "qgssymbollayerutils.h"
2120

2221

@@ -52,18 +51,18 @@ void QgsMeshRendererMeshSettings::setColor( const QColor &color )
5251

5352
QDomElement QgsMeshRendererMeshSettings::writeXml( QDomDocument &doc ) const
5453
{
55-
QDomElement elem = doc.createElement( "mesh-settings" );
56-
elem.setAttribute( "enabled", mEnabled ? "1" : "0" );
57-
elem.setAttribute( "line-width", mLineWidth );
58-
elem.setAttribute( "color", QgsSymbolLayerUtils::encodeColor( mColor ) );
54+
QDomElement elem = doc.createElement( QStringLiteral( "mesh-settings" ) );
55+
elem.setAttribute( QStringLiteral( "enabled" ), mEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
56+
elem.setAttribute( QStringLiteral( "line-width" ), mLineWidth );
57+
elem.setAttribute( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
5958
return elem;
6059
}
6160

6261
void QgsMeshRendererMeshSettings::readXml( const QDomElement &elem )
6362
{
64-
mEnabled = elem.attribute( "enabled" ).toInt();
65-
mLineWidth = elem.attribute( "line-width" ).toDouble();
66-
mColor = QgsSymbolLayerUtils::decodeColor( elem.attribute( "color" ) );
63+
mEnabled = elem.attribute( QStringLiteral( "enabled" ) ).toInt();
64+
mLineWidth = elem.attribute( QStringLiteral( "line-width" ) ).toDouble();
65+
mColor = QgsSymbolLayerUtils::decodeColor( elem.attribute( QStringLiteral( "color" ) ) );
6766
}
6867

6968
// ---------------------------------------------------------------------
@@ -94,20 +93,20 @@ void QgsMeshRendererScalarSettings::setOpacity( double opacity ) { mOpacity = op
9493

9594
QDomElement QgsMeshRendererScalarSettings::writeXml( QDomDocument &doc ) const
9695
{
97-
QDomElement elem = doc.createElement( "scalar-settings" );
98-
elem.setAttribute( "min-val", mClassificationMinimum );
99-
elem.setAttribute( "max-val", mClassificationMaximum );
100-
elem.setAttribute( "opacity", mOpacity );
96+
QDomElement elem = doc.createElement( QStringLiteral( "scalar-settings" ) );
97+
elem.setAttribute( QStringLiteral( "min-val" ), mClassificationMinimum );
98+
elem.setAttribute( QStringLiteral( "max-val" ), mClassificationMaximum );
99+
elem.setAttribute( QStringLiteral( "opacity" ), mOpacity );
101100
QDomElement elemShader = mColorRampShader.writeXml( doc );
102101
elem.appendChild( elemShader );
103102
return elem;
104103
}
105104

106105
void QgsMeshRendererScalarSettings::readXml( const QDomElement &elem )
107106
{
108-
mClassificationMinimum = elem.attribute( "min-val" ).toDouble();
109-
mClassificationMaximum = elem.attribute( "max-val" ).toDouble();
110-
mOpacity = elem.attribute( "opacity" ).toDouble();
107+
mClassificationMinimum = elem.attribute( QStringLiteral( "min-val" ) ).toDouble();
108+
mClassificationMaximum = elem.attribute( QStringLiteral( "max-val" ) ).toDouble();
109+
mOpacity = elem.attribute( QStringLiteral( "opacity" ) ).toDouble();
111110
QDomElement elemShader = elem.firstChildElement( QStringLiteral( "colorrampshader" ) );
112111
mColorRampShader.readXml( elemShader );
113112
}
@@ -224,103 +223,139 @@ void QgsMeshRendererVectorSettings::setArrowHeadLengthRatio( double vectorHeadLe
224223
mArrowHeadLengthRatio = vectorHeadLengthRatio;
225224
}
226225

227-
QDomElement QgsMeshRendererVectorSettings::writeXml( QDomDocument &doc ) const
226+
bool QgsMeshRendererVectorSettings::isOnUserDefinedGrid() const
227+
{
228+
return mOnUserDefinedGrid;
229+
}
230+
231+
void QgsMeshRendererVectorSettings::setOnUserDefinedGrid( bool enabled )
228232
{
229-
QDomElement elem = doc.createElement( "vector-settings" );
230-
elem.setAttribute( "line-width", mLineWidth );
231-
elem.setAttribute( "color", QgsSymbolLayerUtils::encodeColor( mColor ) );
232-
elem.setAttribute( "filter-min", mFilterMin );
233-
elem.setAttribute( "filter-max", mFilterMax );
234-
elem.setAttribute( "arrow-head-width-ratio", mArrowHeadWidthRatio );
235-
elem.setAttribute( "arrow-head-length-ratio", mArrowHeadLengthRatio );
233+
mOnUserDefinedGrid = enabled;
234+
}
236235

237-
QDomElement elemShaft = doc.createElement( "shaft-length" );
236+
int QgsMeshRendererVectorSettings::userGridCellWidth() const
237+
{
238+
return mUserGridCellWidth;
239+
}
240+
241+
void QgsMeshRendererVectorSettings::setUserGridCellWidth( int width )
242+
{
243+
mUserGridCellWidth = width;
244+
}
245+
246+
int QgsMeshRendererVectorSettings::userGridCellHeight() const
247+
{
248+
return mUserGridCellHeight;
249+
}
250+
251+
void QgsMeshRendererVectorSettings::setUserGridCellHeight( int height )
252+
{
253+
mUserGridCellHeight = height;
254+
}
255+
256+
QDomElement QgsMeshRendererVectorSettings::writeXml( QDomDocument &doc ) const
257+
{
258+
QDomElement elem = doc.createElement( QStringLiteral( "vector-settings" ) );
259+
elem.setAttribute( QStringLiteral( "line-width" ), mLineWidth );
260+
elem.setAttribute( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
261+
elem.setAttribute( QStringLiteral( "filter-min" ), mFilterMin );
262+
elem.setAttribute( QStringLiteral( "filter-max" ), mFilterMax );
263+
elem.setAttribute( QStringLiteral( "arrow-head-width-ratio" ), mArrowHeadWidthRatio );
264+
elem.setAttribute( QStringLiteral( "arrow-head-length-ratio" ), mArrowHeadLengthRatio );
265+
elem.setAttribute( QStringLiteral( "user-grid-enabled" ), mOnUserDefinedGrid ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
266+
elem.setAttribute( QStringLiteral( "user-grid-width" ), mUserGridCellWidth );
267+
elem.setAttribute( QStringLiteral( "user-grid-height" ), mUserGridCellHeight );
268+
269+
QDomElement elemShaft = doc.createElement( QStringLiteral( "shaft-length" ) );
238270
QString methodTxt;
239271
switch ( mShaftLengthMethod )
240272
{
241273
case MinMax:
242-
methodTxt = "minmax";
243-
elemShaft.setAttribute( "min", mMinShaftLength );
244-
elemShaft.setAttribute( "max", mMaxShaftLength );
274+
methodTxt = QStringLiteral( "minmax" );
275+
elemShaft.setAttribute( QStringLiteral( "min" ), mMinShaftLength );
276+
elemShaft.setAttribute( QStringLiteral( "max" ), mMaxShaftLength );
245277
break;
246278
case Scaled:
247-
methodTxt = "scaled";
248-
elemShaft.setAttribute( "scale-factor", mScaleFactor );
279+
methodTxt = QStringLiteral( "scaled" );
280+
elemShaft.setAttribute( QStringLiteral( "scale-factor" ), mScaleFactor );
249281
break;
250282
case Fixed:
251-
methodTxt = "fixed";
252-
elemShaft.setAttribute( "fixed-length", mFixedShaftLength );
283+
methodTxt = QStringLiteral( "fixed" ) ;
284+
elemShaft.setAttribute( QStringLiteral( "fixed-length" ), mFixedShaftLength );
253285
break;
254286
}
255-
elemShaft.setAttribute( "method", methodTxt );
287+
elemShaft.setAttribute( QStringLiteral( "method" ), methodTxt );
256288
elem.appendChild( elemShaft );
257289
return elem;
258290
}
259291

260292
void QgsMeshRendererVectorSettings::readXml( const QDomElement &elem )
261293
{
262-
mLineWidth = elem.attribute( "line-width" ).toDouble();
263-
mColor = QgsSymbolLayerUtils::decodeColor( elem.attribute( "color" ) );
264-
mFilterMin = elem.attribute( "filter-min" ).toDouble();
265-
mFilterMax = elem.attribute( "filter-max" ).toDouble();
266-
mArrowHeadWidthRatio = elem.attribute( "arrow-head-width-ratio" ).toDouble();
267-
mArrowHeadLengthRatio = elem.attribute( "arrow-head-length-ratio" ).toDouble();
268-
269-
QDomElement elemShaft = elem.firstChildElement( "shaft-length" );
270-
QString methodTxt = elemShaft.attribute( "method" );
271-
if ( methodTxt == "minmax" )
294+
mLineWidth = elem.attribute( QStringLiteral( "line-width" ) ).toDouble();
295+
mColor = QgsSymbolLayerUtils::decodeColor( elem.attribute( QStringLiteral( "color" ) ) );
296+
mFilterMin = elem.attribute( QStringLiteral( "filter-min" ) ).toDouble();
297+
mFilterMax = elem.attribute( QStringLiteral( "filter-max" ) ).toDouble();
298+
mArrowHeadWidthRatio = elem.attribute( QStringLiteral( "arrow-head-width-ratio" ) ).toDouble();
299+
mArrowHeadLengthRatio = elem.attribute( QStringLiteral( "arrow-head-length-ratio" ) ).toDouble();
300+
mOnUserDefinedGrid = elem.attribute( QStringLiteral( "user-grid-enabled" ) ).toInt(); //bool
301+
mUserGridCellWidth = elem.attribute( QStringLiteral( "user-grid-width" ) ).toInt();
302+
mUserGridCellHeight = elem.attribute( QStringLiteral( "user-grid-height" ) ).toInt();
303+
304+
QDomElement elemShaft = elem.firstChildElement( QStringLiteral( "shaft-length" ) );
305+
QString methodTxt = elemShaft.attribute( QStringLiteral( "method" ) );
306+
if ( QStringLiteral( "minmax" ) == methodTxt )
272307
{
273308
mShaftLengthMethod = MinMax;
274-
mMinShaftLength = elemShaft.attribute( "min" ).toDouble();
275-
mMaxShaftLength = elemShaft.attribute( "max" ).toDouble();
309+
mMinShaftLength = elemShaft.attribute( QStringLiteral( "min" ) ).toDouble();
310+
mMaxShaftLength = elemShaft.attribute( QStringLiteral( "max" ) ).toDouble();
276311
}
277-
else if ( methodTxt == "scaled" )
312+
else if ( QStringLiteral( "scaled" ) == methodTxt )
278313
{
279314
mShaftLengthMethod = Scaled;
280-
mScaleFactor = elemShaft.attribute( "scale-factor" ).toDouble();
315+
mScaleFactor = elemShaft.attribute( QStringLiteral( "scale-factor" ) ).toDouble();
281316
}
282317
else // fixed
283318
{
284319
mShaftLengthMethod = Fixed;
285-
mFixedShaftLength = elemShaft.attribute( "fixed-length" ).toDouble();
320+
mFixedShaftLength = elemShaft.attribute( QStringLiteral( "fixed-length" ) ).toDouble();
286321
}
287322
}
288323

289324
// ---------------------------------------------------------------------
290325

291326
QDomElement QgsMeshRendererSettings::writeXml( QDomDocument &doc ) const
292327
{
293-
QDomElement elem = doc.createElement( "mesh-renderer-settings" );
328+
QDomElement elem = doc.createElement( QStringLiteral( "mesh-renderer-settings" ) );
294329

295-
QDomElement elemActiveDataset = doc.createElement( "active-dataset" );
330+
QDomElement elemActiveDataset = doc.createElement( QStringLiteral( "active-dataset" ) );
296331
if ( mActiveScalarDataset.isValid() )
297-
elemActiveDataset.setAttribute( "scalar", QString( "%1,%2" ).arg( mActiveScalarDataset.group() ).arg( mActiveScalarDataset.dataset() ) );
332+
elemActiveDataset.setAttribute( QStringLiteral( "scalar" ), QStringLiteral( "%1,%2" ).arg( mActiveScalarDataset.group() ).arg( mActiveScalarDataset.dataset() ) );
298333
if ( mActiveVectorDataset.isValid() )
299-
elemActiveDataset.setAttribute( "vector", QString( "%1,%2" ).arg( mActiveVectorDataset.group() ).arg( mActiveVectorDataset.dataset() ) );
334+
elemActiveDataset.setAttribute( QStringLiteral( "vector" ), QStringLiteral( "%1,%2" ).arg( mActiveVectorDataset.group() ).arg( mActiveVectorDataset.dataset() ) );
300335
elem.appendChild( elemActiveDataset );
301336

302337
for ( int groupIndex : mRendererScalarSettings.keys() )
303338
{
304339
const QgsMeshRendererScalarSettings &scalarSettings = mRendererScalarSettings[groupIndex];
305340
QDomElement elemScalar = scalarSettings.writeXml( doc );
306-
elemScalar.setAttribute( "group", groupIndex );
341+
elemScalar.setAttribute( QStringLiteral( "group" ), groupIndex );
307342
elem.appendChild( elemScalar );
308343
}
309344

310345
for ( int groupIndex : mRendererVectorSettings.keys() )
311346
{
312347
const QgsMeshRendererVectorSettings &vectorSettings = mRendererVectorSettings[groupIndex];
313348
QDomElement elemVector = vectorSettings.writeXml( doc );
314-
elemVector.setAttribute( "group", groupIndex );
349+
elemVector.setAttribute( QStringLiteral( "group" ), groupIndex );
315350
elem.appendChild( elemVector );
316351
}
317352

318353
QDomElement elemNativeMesh = mRendererNativeMeshSettings.writeXml( doc );
319-
elemNativeMesh.setTagName( "mesh-settings-native" );
354+
elemNativeMesh.setTagName( QStringLiteral( "mesh-settings-native" ) );
320355
elem.appendChild( elemNativeMesh );
321356

322357
QDomElement elemTriangularMesh = mRendererTriangularMeshSettings.writeXml( doc );
323-
elemTriangularMesh.setTagName( "mesh-settings-triangular" );
358+
elemTriangularMesh.setTagName( QStringLiteral( "mesh-settings-triangular" ) );
324359
elem.appendChild( elemTriangularMesh );
325360

326361
return elem;
@@ -331,45 +366,45 @@ void QgsMeshRendererSettings::readXml( const QDomElement &elem )
331366
mRendererScalarSettings.clear();
332367
mRendererVectorSettings.clear();
333368

334-
QDomElement elemActiveDataset = elem.firstChildElement( "active-dataset" );
335-
if ( elemActiveDataset.hasAttribute( "scalar" ) )
369+
QDomElement elemActiveDataset = elem.firstChildElement( QStringLiteral( "active-dataset" ) );
370+
if ( elemActiveDataset.hasAttribute( QStringLiteral( "scalar" ) ) )
336371
{
337-
QStringList lst = elemActiveDataset.attribute( "scalar" ).split( QChar( ',' ) );
372+
QStringList lst = elemActiveDataset.attribute( QStringLiteral( "scalar" ) ).split( QChar( ',' ) );
338373
if ( lst.count() == 2 )
339374
mActiveScalarDataset = QgsMeshDatasetIndex( lst[0].toInt(), lst[1].toInt() );
340375
}
341-
if ( elemActiveDataset.hasAttribute( "vector" ) )
376+
if ( elemActiveDataset.hasAttribute( QStringLiteral( "vector" ) ) )
342377
{
343-
QStringList lst = elemActiveDataset.attribute( "vector" ).split( QChar( ',' ) );
378+
QStringList lst = elemActiveDataset.attribute( QStringLiteral( "vector" ) ).split( QChar( ',' ) );
344379
if ( lst.count() == 2 )
345380
mActiveVectorDataset = QgsMeshDatasetIndex( lst[0].toInt(), lst[1].toInt() );
346381
}
347382

348-
QDomElement elemScalar = elem.firstChildElement( "scalar-settings" );
383+
QDomElement elemScalar = elem.firstChildElement( QStringLiteral( "scalar-settings" ) );
349384
while ( !elemScalar.isNull() )
350385
{
351-
int groupIndex = elemScalar.attribute( "group" ).toInt();
386+
int groupIndex = elemScalar.attribute( QStringLiteral( "group" ) ).toInt();
352387
QgsMeshRendererScalarSettings scalarSettings;
353388
scalarSettings.readXml( elemScalar );
354389
mRendererScalarSettings.insert( groupIndex, scalarSettings );
355390

356-
elemScalar = elemScalar.nextSiblingElement( "scalar-settings" );
391+
elemScalar = elemScalar.nextSiblingElement( QStringLiteral( "scalar-settings" ) );
357392
}
358393

359-
QDomElement elemVector = elem.firstChildElement( "vector-settings" );
394+
QDomElement elemVector = elem.firstChildElement( QStringLiteral( "vector-settings" ) );
360395
while ( !elemVector.isNull() )
361396
{
362-
int groupIndex = elemVector.attribute( "group" ).toInt();
397+
int groupIndex = elemVector.attribute( QStringLiteral( "group" ) ).toInt();
363398
QgsMeshRendererVectorSettings vectorSettings;
364399
vectorSettings.readXml( elemVector );
365400
mRendererVectorSettings.insert( groupIndex, vectorSettings );
366401

367-
elemVector = elemVector.nextSiblingElement( "vector-settings" );
402+
elemVector = elemVector.nextSiblingElement( QStringLiteral( "vector-settings" ) );
368403
}
369404

370-
QDomElement elemNativeMesh = elem.firstChildElement( "mesh-settings-native" );
405+
QDomElement elemNativeMesh = elem.firstChildElement( QStringLiteral( "mesh-settings-native" ) );
371406
mRendererNativeMeshSettings.readXml( elemNativeMesh );
372407

373-
QDomElement elemTriangularMesh = elem.firstChildElement( "mesh-settings-triangular" );
408+
QDomElement elemTriangularMesh = elem.firstChildElement( QStringLiteral( "mesh-settings-triangular" ) );
374409
mRendererTriangularMeshSettings.readXml( elemTriangularMesh );
375410
}

‎src/core/mesh/qgsmeshrenderersettings.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,19 @@ class CORE_EXPORT QgsMeshRendererVectorSettings
248248
//! Sets ratio of the head length of the arrow (range 0-1)
249249
void setArrowHeadLengthRatio( double arrowHeadLengthRatio );
250250

251+
//! Returns whether vectors are drawn on user-defined grid
252+
bool isOnUserDefinedGrid() const;
253+
//! Toggles drawing of vectors on user defined grid
254+
void setOnUserDefinedGrid( bool enabled );
255+
//! Returns width in pixels of user grid cell
256+
int userGridCellWidth() const;
257+
//! Sets width of user grid cell (in pixels)
258+
void setUserGridCellWidth( int width );
259+
//! Returns height in pixels of user grid cell
260+
int userGridCellHeight() const;
261+
//! Sets height of user grid cell (in pixels)
262+
void setUserGridCellHeight( int height );
263+
251264
//! Writes configuration to a new DOM element
252265
QDomElement writeXml( QDomDocument &doc ) const;
253266
//! Reads configuration from the given DOM element
@@ -265,6 +278,9 @@ class CORE_EXPORT QgsMeshRendererVectorSettings
265278
double mFixedShaftLength = 20; //in milimeters
266279
double mArrowHeadWidthRatio = 0.15;
267280
double mArrowHeadLengthRatio = 0.40;
281+
bool mOnUserDefinedGrid = false;
282+
int mUserGridCellWidth = 10; // in pixels
283+
int mUserGridCellHeight = 10; // in pixels
268284
};
269285

270286

‎src/core/mesh/qgsmeshvectorrenderer.cpp

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "qgscoordinatetransform.h"
2121
#include "qgsmaptopixel.h"
2222
#include "qgsunittypes.h"
23+
#include "qgsmeshlayerutils.h"
2324

2425
#include <cstdlib>
2526
#include <ctime>
@@ -91,7 +92,9 @@ void QgsMeshVectorRenderer::draw()
9192
pen.setColor( mCfg.color() );
9293
painter->setPen( pen );
9394

94-
if ( mDataOnVertices )
95+
if ( mCfg.isOnUserDefinedGrid() )
96+
drawVectorDataOnGrid();
97+
else if ( mDataOnVertices )
9598
drawVectorDataOnVertices();
9699
else
97100
drawVectorDataOnFaces();
@@ -234,6 +237,94 @@ void QgsMeshVectorRenderer::drawVectorDataOnFaces()
234237
}
235238
}
236239

240+
void QgsMeshVectorRenderer::drawVectorDataOnGrid()
241+
{
242+
int cellx = mCfg.userGridCellWidth();
243+
int celly = mCfg.userGridCellHeight();
244+
245+
const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
246+
const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
247+
248+
for ( int i = 0; i < triangles.size(); i++ )
249+
{
250+
const QgsMeshFace &face = triangles[i];
251+
252+
const int v1 = face[0], v2 = face[1], v3 = face[2];
253+
const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
254+
255+
const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
256+
257+
QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
258+
if ( !mContext.extent().intersects( bbox ) )
259+
continue;
260+
261+
// Get the BBox of the element in pixels
262+
int left, right, top, bottom;
263+
QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, left, right, top, bottom );
264+
265+
// Align rect to the grid (e.g. interval <13, 36> with grid cell 10 will be trimmed to <20,30>
266+
if ( left % cellx != 0 )
267+
left += cellx - ( left % cellx );
268+
if ( right % cellx != 0 )
269+
right -= ( right % cellx );
270+
if ( top % celly != 0 )
271+
top += celly - ( top % celly );
272+
if ( bottom % celly != 0 )
273+
bottom -= ( bottom % celly );
274+
275+
for ( int y = top; y <= bottom; y += celly )
276+
{
277+
for ( int x = left; x <= right; x += cellx )
278+
{
279+
QgsMeshDatasetValue val;
280+
const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
281+
282+
if ( mDataOnVertices )
283+
{
284+
val.setX(
285+
QgsMeshLayerUtils::interpolateFromVerticesData(
286+
p1, p2, p3,
287+
mDatasetValuesX[v1],
288+
mDatasetValuesX[v2],
289+
mDatasetValuesX[v3],
290+
p )
291+
);
292+
val.setY(
293+
QgsMeshLayerUtils::interpolateFromVerticesData(
294+
p1, p2, p3,
295+
mDatasetValuesY[v1],
296+
mDatasetValuesY[v2],
297+
mDatasetValuesY[v3],
298+
p )
299+
);
300+
}
301+
else
302+
{
303+
val.setX(
304+
QgsMeshLayerUtils::interpolateFromFacesData(
305+
p1, p2, p3,
306+
mDatasetValuesX[nativeFaceIndex],
307+
p
308+
)
309+
);
310+
val.setY(
311+
QgsMeshLayerUtils::interpolateFromFacesData(
312+
p1, p2, p3,
313+
mDatasetValuesY[nativeFaceIndex],
314+
p
315+
)
316+
);
317+
}
318+
319+
if ( nodataValue( val.x(), val.y() ) )
320+
continue;
321+
322+
QgsPointXY lineStart( x, y );
323+
drawVectorArrow( lineStart, val.x(), val.y(), val.scalar() );
324+
}
325+
}
326+
}
327+
}
237328

238329
void QgsMeshVectorRenderer::drawVectorArrow( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
239330
{

‎src/core/mesh/qgsmeshvectorrenderer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ class QgsMeshVectorRenderer
6868
void drawVectorDataOnVertices();
6969
//! Draws for data defined on face centers
7070
void drawVectorDataOnFaces();
71+
//! Draws data on user-defined grid
72+
void drawVectorDataOnGrid();
7173
//! Draws arrow from start point and vector data
7274
void drawVectorArrow( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude );
7375
//! Calculates the end point of the arrow based on start point and vector data

‎src/ui/mesh/qgsmeshrenderervectorsettingswidgetbase.ui

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
<rect>
77
<x>0</x>
88
<y>0</y>
9-
<width>310</width>
10-
<height>419</height>
9+
<width>287</width>
10+
<height>520</height>
1111
</rect>
1212
</property>
1313
<property name="windowTitle">
@@ -61,6 +61,90 @@
6161
</layout>
6262
</widget>
6363
</item>
64+
<item>
65+
<widget class="QGroupBox" name="mDisplayVectorsOnGridGroupBox">
66+
<property name="title">
67+
<string>Display Vectors on User Grid</string>
68+
</property>
69+
<property name="checkable">
70+
<bool>true</bool>
71+
</property>
72+
<property name="checked">
73+
<bool>false</bool>
74+
</property>
75+
<layout class="QGridLayout" name="gridLayout_2">
76+
<item row="0" column="0">
77+
<layout class="QGridLayout" name="gridLayout">
78+
<item row="0" column="0">
79+
<widget class="QLabel" name="xSpacingLabel">
80+
<property name="text">
81+
<string>X Spacing</string>
82+
</property>
83+
</widget>
84+
</item>
85+
<item row="1" column="0">
86+
<widget class="QLabel" name="ySpacingLabel">
87+
<property name="text">
88+
<string>Y Spacing</string>
89+
</property>
90+
</widget>
91+
</item>
92+
<item row="0" column="1">
93+
<widget class="QgsSpinBox" name="mXSpacingSpinBox">
94+
<property name="suffix">
95+
<string> px</string>
96+
</property>
97+
<property name="minimum">
98+
<number>1</number>
99+
</property>
100+
<property name="maximum">
101+
<number>8000</number>
102+
</property>
103+
<property name="singleStep">
104+
<number>10</number>
105+
</property>
106+
<property name="value">
107+
<number>10</number>
108+
</property>
109+
</widget>
110+
</item>
111+
<item row="1" column="1">
112+
<widget class="QSpinBox" name="mYSpacingSpinBox">
113+
<property name="suffix">
114+
<string>px</string>
115+
</property>
116+
<property name="minimum">
117+
<number>1</number>
118+
</property>
119+
<property name="maximum">
120+
<number>5000</number>
121+
</property>
122+
<property name="singleStep">
123+
<number>10</number>
124+
</property>
125+
<property name="value">
126+
<number>10</number>
127+
</property>
128+
</widget>
129+
</item>
130+
</layout>
131+
</item>
132+
<item row="2" column="0">
133+
<spacer name="verticalSpacer_2">
134+
<property name="orientation">
135+
<enum>Qt::Vertical</enum>
136+
</property>
137+
<property name="sizeHint" stdset="0">
138+
<size>
139+
<width>20</width>
140+
<height>40</height>
141+
</size>
142+
</property>
143+
</spacer>
144+
</item>
145+
</layout>
146+
</widget>
147+
</item>
64148
<item>
65149
<widget class="QGroupBox" name="headOptionsGroupBox">
66150
<property name="title">
@@ -216,16 +300,21 @@
216300
</layout>
217301
</widget>
218302
<customwidgets>
303+
<customwidget>
304+
<class>QgsColorButton</class>
305+
<extends>QPushButton</extends>
306+
<header>qgscolorbutton.h</header>
307+
<container>1</container>
308+
</customwidget>
219309
<customwidget>
220310
<class>QgsDoubleSpinBox</class>
221311
<extends>QDoubleSpinBox</extends>
222312
<header>qgsdoublespinbox.h</header>
223313
</customwidget>
224314
<customwidget>
225-
<class>QgsColorButton</class>
226-
<extends>QFrame</extends>
227-
<header>qgscolorbutton.h</header>
228-
<container>1</container>
315+
<class>QgsSpinBox</class>
316+
<extends>QSpinBox</extends>
317+
<header>qgsspinbox.h</header>
229318
</customwidget>
230319
</customwidgets>
231320
<resources/>

‎tests/src/core/testqgsmeshlayerrenderer.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ class TestQgsMeshRenderer : public QObject
7373
void test_face_scalar_dataset_rendering();
7474
void test_face_vector_dataset_rendering();
7575
void test_vertex_scalar_dataset_with_inactive_face_rendering();
76+
void test_face_vector_on_user_grid();
77+
void test_vertex_vector_on_user_grid();
7678

7779
void test_signals();
7880
};
@@ -266,6 +268,44 @@ void TestQgsMeshRenderer::test_vertex_scalar_dataset_with_inactive_face_renderin
266268
QVERIFY( imageCheck( "quad_and_triangle_vertex_scalar_dataset_with_inactive_face", mMdalLayer ) );
267269
}
268270

271+
void TestQgsMeshRenderer::test_face_vector_on_user_grid()
272+
{
273+
QgsMeshDatasetIndex ds( 3, 0 );
274+
const QgsMeshDatasetGroupMetadata metadata = mMemoryLayer->dataProvider()->datasetGroupMetadata( ds );
275+
QVERIFY( metadata.name() == "FaceVectorDataset" );
276+
277+
QgsMeshRendererSettings rendererSettings = mMemoryLayer->rendererSettings();
278+
QgsMeshRendererVectorSettings settings = rendererSettings.vectorSettings( ds.group() );
279+
settings.setOnUserDefinedGrid( true );
280+
settings.setUserGridCellWidth( 30 );
281+
settings.setUserGridCellHeight( 20 );
282+
settings.setLineWidth( 0.8 );
283+
rendererSettings.setVectorSettings( ds.group(), settings );
284+
rendererSettings.setActiveVectorDataset( ds );
285+
mMemoryLayer->setRendererSettings( rendererSettings );
286+
287+
QVERIFY( imageCheck( "quad_and_triangle_face_vector_user_grid_dataset", mMemoryLayer ) );
288+
}
289+
290+
void TestQgsMeshRenderer::test_vertex_vector_on_user_grid()
291+
{
292+
QgsMeshDatasetIndex ds( 1, 0 );
293+
const QgsMeshDatasetGroupMetadata metadata = mMemoryLayer->dataProvider()->datasetGroupMetadata( ds );
294+
QVERIFY( metadata.name() == "VertexVectorDataset" );
295+
296+
QgsMeshRendererSettings rendererSettings = mMemoryLayer->rendererSettings();
297+
QgsMeshRendererVectorSettings settings = rendererSettings.vectorSettings( ds.group() );
298+
settings.setOnUserDefinedGrid( true );
299+
settings.setUserGridCellWidth( 60 );
300+
settings.setUserGridCellHeight( 40 );
301+
settings.setLineWidth( 0.9 );
302+
rendererSettings.setVectorSettings( ds.group(), settings );
303+
rendererSettings.setActiveVectorDataset( ds );
304+
mMemoryLayer->setRendererSettings( rendererSettings );
305+
306+
QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset", mMemoryLayer ) );
307+
}
308+
269309
void TestQgsMeshRenderer::test_signals()
270310
{
271311
QSignalSpy spy1( mMemoryLayer, &QgsMapLayer::rendererChanged );

0 commit comments

Comments
 (0)
Please sign in to comment.