[MESH] Resampling from vertex values to face values (#35264)

[FEATURE] Implements resampling from values on vertices to values on faces with the neighbor average method. (note that resampling method for datasets defined on faces, e.g., the value on vertices is calculated from value on faces was added in the previous QGIS release)

The default method is set to "none" for resampling from vertices to faces and to "neighbor average" for resampling from faces to vertices. Then the default rendering is now always smooth.
This commit is contained in:
Vincent Cloarec 2020-03-24 05:03:54 -04:00 committed by GitHub
parent 20a7ed44ac
commit 34d44c08ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 164 additions and 63 deletions

View File

@ -36,7 +36,7 @@ Caches the native and triangular mesh from data provider
QgsGeometry exportLines( const QgsMeshDatasetIndex &index,
double value,
QgsMeshRendererScalarSettings::DataInterpolationMethod method,
QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback = 0 );
%Docstring
Exports multi line string containing the contour line for particular dataset and value
@ -52,7 +52,7 @@ Exports multi line string containing the contour line for particular dataset and
QgsGeometry exportPolygons( const QgsMeshDatasetIndex &index,
double min_value,
double max_value,
QgsMeshRendererScalarSettings::DataInterpolationMethod method,
QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback = 0 );
%Docstring
Exports multi polygons representing the areas with values in range for particular dataset

View File

@ -96,7 +96,8 @@ Represents a mesh renderer settings for scalar datasets
#include "qgsmeshrenderersettings.h"
%End
public:
enum DataInterpolationMethod
enum DataResamplingMethod
{
None,
@ -135,7 +136,7 @@ Returns opacity
Sets opacity
%End
DataInterpolationMethod dataInterpolationMethod() const;
DataResamplingMethod dataResamplingMethod() const;
%Docstring
Returns the type of interpolation to use to
convert face defined datasets to
@ -144,7 +145,7 @@ values on vertices
.. versionadded:: 3.12
%End
void setDataInterpolationMethod( const DataInterpolationMethod &dataInterpolationMethod );
void setDataResamplingMethod( const DataResamplingMethod &dataResamplingMethod );
%Docstring
Sets data interpolation method

View File

@ -107,7 +107,7 @@ std::shared_ptr<QgsMeshMemoryDatasetGroup> QgsMeshCalcUtils::create( const QStri
mMeshLayer->nativeMesh(),
mMeshLayer->triangularMesh(),
nullptr,
mMeshLayer->rendererSettings().scalarSettings( groupIndex ).dataInterpolationMethod()
mMeshLayer->rendererSettings().scalarSettings( groupIndex ).dataResamplingMethod()
);
Q_ASSERT( dataX.size() == resultCount );
QVector<double> dataY =
@ -116,7 +116,7 @@ std::shared_ptr<QgsMeshMemoryDatasetGroup> QgsMeshCalcUtils::create( const QStri
mMeshLayer->nativeMesh(),
mMeshLayer->triangularMesh(),
nullptr,
mMeshLayer->rendererSettings().scalarSettings( groupIndex ).dataInterpolationMethod()
mMeshLayer->rendererSettings().scalarSettings( groupIndex ).dataResamplingMethod()
);
Q_ASSERT( dataY.size() == resultCount );

View File

@ -58,7 +58,7 @@ QgsGeometry QgsMeshContours::exportPolygons(
const QgsMeshDatasetIndex &index,
double min_value,
double max_value,
QgsMeshRendererScalarSettings::DataInterpolationMethod method,
QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback
)
{
@ -254,7 +254,7 @@ QgsGeometry QgsMeshContours::exportPolygons(
QgsGeometry QgsMeshContours::exportLines( const QgsMeshDatasetIndex &index,
double value,
QgsMeshRendererScalarSettings::DataInterpolationMethod method,
QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback )
{
// Check if the layer/mesh is valid
@ -380,7 +380,7 @@ QgsGeometry QgsMeshContours::exportLines( const QgsMeshDatasetIndex &index,
}
}
void QgsMeshContours::populateCache( const QgsMeshDatasetIndex &index, QgsMeshRendererScalarSettings::DataInterpolationMethod method )
void QgsMeshContours::populateCache( const QgsMeshDatasetIndex &index, QgsMeshRendererScalarSettings::DataResamplingMethod method )
{
if ( mCachedIndex != index )
{

View File

@ -69,7 +69,7 @@ class ANALYSIS_EXPORT QgsMeshContours
*/
QgsGeometry exportLines( const QgsMeshDatasetIndex &index,
double value,
QgsMeshRendererScalarSettings::DataInterpolationMethod method,
QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback = nullptr );
/**
@ -84,13 +84,13 @@ class ANALYSIS_EXPORT QgsMeshContours
QgsGeometry exportPolygons( const QgsMeshDatasetIndex &index,
double min_value,
double max_value,
QgsMeshRendererScalarSettings::DataInterpolationMethod method,
QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback = nullptr );
private:
void populateCache(
const QgsMeshDatasetIndex &index,
QgsMeshRendererScalarSettings::DataInterpolationMethod method );
QgsMeshRendererScalarSettings::DataResamplingMethod method );
QgsMeshLayer *mMeshLayer = nullptr;

View File

@ -55,13 +55,13 @@ QgsMeshRendererScalarSettingsWidget::QgsMeshRendererScalarSettingsWidget( QWidge
void QgsMeshRendererScalarSettingsWidget::setLayer( QgsMeshLayer *layer )
{
mMeshLayer = layer;
mScalarInterpolationTypeComboBox->setEnabled( dataIsDefinedOnFaces() );
mScalarInterpolationTypeComboBox->setEnabled( !dataIsDefinedOnEdges() );
}
void QgsMeshRendererScalarSettingsWidget::setActiveDatasetGroup( int groupIndex )
{
mActiveDatasetGroup = groupIndex;
mScalarInterpolationTypeComboBox->setEnabled( dataIsDefinedOnFaces() );
mScalarInterpolationTypeComboBox->setEnabled( !dataIsDefinedOnEdges() );
}
QgsMeshRendererScalarSettings QgsMeshRendererScalarSettingsWidget::settings() const
@ -70,7 +70,7 @@ QgsMeshRendererScalarSettings QgsMeshRendererScalarSettingsWidget::settings() co
settings.setColorRampShader( mScalarColorRampShaderWidget->shader() );
settings.setClassificationMinimumMaximum( lineEditValue( mScalarMinLineEdit ), lineEditValue( mScalarMaxLineEdit ) );
settings.setOpacity( mOpacityWidget->opacity() );
settings.setDataInterpolationMethod( dataIntepolationMethod() );
settings.setDataResamplingMethod( dataIntepolationMethod() );
settings.setEdgeWidth( mScalarEdgeWidthSpinBox->value() );
settings.setEdgeWidthUnit( mScalarEdgeWidthUnitSelectionWidget->unit() );
return settings;
@ -98,7 +98,7 @@ void QgsMeshRendererScalarSettingsWidget::syncToLayer( )
whileBlocking( mScalarColorRampShaderWidget )->setFromShader( shader );
whileBlocking( mScalarColorRampShaderWidget )->setMinimumMaximum( min, max );
whileBlocking( mOpacityWidget )->setOpacity( settings.opacity() );
int index = mScalarInterpolationTypeComboBox->findData( settings.dataInterpolationMethod() );
int index = mScalarInterpolationTypeComboBox->findData( settings.dataResamplingMethod() );
whileBlocking( mScalarInterpolationTypeComboBox )->setCurrentIndex( index );
bool hasEdges = ( mMeshLayer->dataProvider() &&
@ -140,18 +140,11 @@ void QgsMeshRendererScalarSettingsWidget::recalculateMinMaxButtonClicked()
mScalarColorRampShaderWidget->setMinimumMaximumAndClassify( min, max );
}
QgsMeshRendererScalarSettings::DataInterpolationMethod QgsMeshRendererScalarSettingsWidget::dataIntepolationMethod() const
QgsMeshRendererScalarSettings::DataResamplingMethod QgsMeshRendererScalarSettingsWidget::dataIntepolationMethod() const
{
if ( dataIsDefinedOnFaces() )
{
const int data = mScalarInterpolationTypeComboBox->currentData().toInt();
const QgsMeshRendererScalarSettings::DataInterpolationMethod method = static_cast<QgsMeshRendererScalarSettings::DataInterpolationMethod>( data );
return method;
}
else
{
return QgsMeshRendererScalarSettings::None;
}
const int data = mScalarInterpolationTypeComboBox->currentData().toInt();
const QgsMeshRendererScalarSettings::DataResamplingMethod method = static_cast<QgsMeshRendererScalarSettings::DataResamplingMethod>( data );
return method;
}
bool QgsMeshRendererScalarSettingsWidget::dataIsDefinedOnFaces() const
@ -167,4 +160,17 @@ bool QgsMeshRendererScalarSettingsWidget::dataIsDefinedOnFaces() const
return onFaces;
}
bool QgsMeshRendererScalarSettingsWidget::dataIsDefinedOnEdges() const
{
if ( !mMeshLayer || !mMeshLayer->dataProvider() || !mMeshLayer->dataProvider()->isValid() )
return false;
if ( mActiveDatasetGroup < 0 )
return false;
QgsMeshDatasetGroupMetadata meta = mMeshLayer->dataProvider()->datasetGroupMetadata( mActiveDatasetGroup );
const bool onEdges = ( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnEdges );
return onEdges;
}

View File

@ -65,9 +65,10 @@ class APP_EXPORT QgsMeshRendererScalarSettingsWidget : public QWidget, private U
private:
double lineEditValue( const QLineEdit *lineEdit ) const;
QgsMeshRendererScalarSettings::DataInterpolationMethod dataIntepolationMethod() const;
QgsMeshRendererScalarSettings::DataResamplingMethod dataIntepolationMethod() const;
bool dataIsDefinedOnFaces() const;
bool dataIsDefinedOnEdges() const;
QgsMeshLayer *mMeshLayer = nullptr; // not owned
int mActiveDatasetGroup = -1;

View File

@ -70,6 +70,32 @@ void QgsMeshLayer::setDefaultRendererSettings()
meshSettings.setEnabled( true );
mRendererSettings.setNativeMeshSettings( meshSettings );
}
// Sets default resample method for scalar dataset
if ( !mDataProvider )
return;
for ( int i = 0; i < mDataProvider->datasetGroupCount(); ++i )
{
QgsMeshDatasetGroupMetadata meta = mDataProvider->datasetGroupMetadata( i );
if ( meta.isScalar() )
{
QgsMeshRendererScalarSettings scalarSettings = mRendererSettings.scalarSettings( i );
switch ( meta.dataType() )
{
case QgsMeshDatasetGroupMetadata::DataOnFaces:
case QgsMeshDatasetGroupMetadata::DataOnVolumes: // data on volumes are averaged to 2D data on faces
scalarSettings.setDataResamplingMethod( QgsMeshRendererScalarSettings::NeighbourAverage );
break;
case QgsMeshDatasetGroupMetadata::DataOnVertices:
scalarSettings.setDataResamplingMethod( QgsMeshRendererScalarSettings::None );
break;
case QgsMeshDatasetGroupMetadata::DataOnEdges:
break;
}
mRendererSettings.setScalarSettings( i, scalarSettings );
}
}
}
void QgsMeshLayer::createSimplifiedMeshes()

View File

@ -106,7 +106,7 @@ void QgsMeshLayerRenderer::copyScalarDatasetValues( QgsMeshLayer *layer )
// Find out if we can use cache up to date. If yes, use it and return
const int datasetGroupCount = layer->dataProvider()->datasetGroupCount();
const QgsMeshRendererScalarSettings::DataInterpolationMethod method = mRendererSettings.scalarSettings( datasetIndex.group() ).dataInterpolationMethod();
const QgsMeshRendererScalarSettings::DataResamplingMethod method = mRendererSettings.scalarSettings( datasetIndex.group() ).dataResamplingMethod();
QgsMeshLayerRendererCache *cache = layer->rendererCache();
if ( ( cache->mDatasetGroupsCount == datasetGroupCount ) &&
( cache->mActiveScalarDatasetIndex == datasetIndex ) &&
@ -153,16 +153,31 @@ void QgsMeshLayerRenderer::copyScalarDatasetValues( QgsMeshLayer *layer )
mNativeMesh.faces.count() );
// for data on faces, there could be request to interpolate the data to vertices
if ( ( mScalarDataType == QgsMeshDatasetGroupMetadata::DataType::DataOnFaces ) && ( method != QgsMeshRendererScalarSettings::None ) )
if ( method != QgsMeshRendererScalarSettings::None )
{
mScalarDataType = QgsMeshDatasetGroupMetadata::DataType::DataOnVertices;
mScalarDatasetValues = QgsMeshLayerUtils::interpolateFromFacesData(
mScalarDatasetValues,
&mNativeMesh,
&mTriangularMesh,
&mScalarActiveFaceFlagValues,
method
);
if ( mScalarDataType == QgsMeshDatasetGroupMetadata::DataType::DataOnFaces )
{
mScalarDataType = QgsMeshDatasetGroupMetadata::DataType::DataOnVertices;
mScalarDatasetValues = QgsMeshLayerUtils::interpolateFromFacesData(
mScalarDatasetValues,
&mNativeMesh,
&mTriangularMesh,
&mScalarActiveFaceFlagValues,
method
);
}
else if ( mScalarDataType == QgsMeshDatasetGroupMetadata::DataType::DataOnVertices )
{
mScalarDataType = QgsMeshDatasetGroupMetadata::DataType::DataOnFaces;
mScalarDatasetValues = QgsMeshLayerUtils::resampleFromVerticesToFaces(
mScalarDatasetValues,
&mNativeMesh,
&mTriangularMesh,
&mScalarActiveFaceFlagValues,
method
);
}
}
const QgsMeshDatasetMetadata datasetMetadata = layer->dataProvider()->datasetMetadata( datasetIndex );
@ -182,6 +197,7 @@ void QgsMeshLayerRenderer::copyScalarDatasetValues( QgsMeshLayer *layer )
cache->mScalarAveragingMethod.reset( mRendererSettings.averagingMethod() ? mRendererSettings.averagingMethod()->clone() : nullptr );
}
void QgsMeshLayerRenderer::copyVectorDatasetValues( QgsMeshLayer *layer )
{
const QgsMeshDatasetIndex datasetIndex = mRendererSettings.activeVectorDataset();

View File

@ -63,7 +63,7 @@ struct CORE_NO_EXPORT QgsMeshLayerRendererCache
QgsMeshDatasetGroupMetadata::DataType mScalarDataType = QgsMeshDatasetGroupMetadata::DataType::DataOnVertices;
double mScalarDatasetMinimum = std::numeric_limits<double>::quiet_NaN();
double mScalarDatasetMaximum = std::numeric_limits<double>::quiet_NaN();
QgsMeshRendererScalarSettings::DataInterpolationMethod mDataInterpolationMethod = QgsMeshRendererScalarSettings::None;
QgsMeshRendererScalarSettings::DataResamplingMethod mDataInterpolationMethod = QgsMeshRendererScalarSettings::None;
std::unique_ptr<QgsMesh3dAveragingMethod> mScalarAveragingMethod;
// vector dataset

View File

@ -305,11 +305,8 @@ QVector<double> QgsMeshLayerUtils::interpolateFromFacesData(
const QgsMesh *nativeMesh,
const QgsTriangularMesh *triangularMesh,
QgsMeshDataBlock *active,
QgsMeshRendererScalarSettings::DataInterpolationMethod method )
QgsMeshRendererScalarSettings::DataResamplingMethod method )
{
Q_UNUSED( triangularMesh )
Q_UNUSED( method )
assert( nativeMesh );
assert( method == QgsMeshRendererScalarSettings::NeighbourAverage );
@ -355,10 +352,42 @@ QVector<double> QgsMeshLayerUtils::interpolateFromFacesData(
return res;
}
QVector<double> QgsMeshLayerUtils::resampleFromVerticesToFaces(
const QVector<double> valuesOnVertices,
const QgsMesh *nativeMesh,
const QgsTriangularMesh *triangularMesh,
const QgsMeshDataBlock *active,
QgsMeshRendererScalarSettings::DataResamplingMethod method )
{
assert( nativeMesh );
assert( method == QgsMeshRendererScalarSettings::NeighbourAverage );
// assuming that native vertex count = triangular vertex count
assert( nativeMesh->vertices.size() == triangularMesh->vertices().size() );
QVector<double> ret( nativeMesh->faceCount(), std::numeric_limits<double>::quiet_NaN() );
for ( int i = 0; i < nativeMesh->faces.size(); ++i )
{
const QgsMeshFace face = nativeMesh->face( i );
if ( active->active( i ) && face.count() > 2 )
{
double value = 0;
for ( int j = 0; j < face.count(); ++j )
{
value += valuesOnVertices.at( face.at( j ) );
}
ret[i] = value / face.count();
}
}
return ret;
}
QVector<double> QgsMeshLayerUtils::calculateMagnitudeOnVertices( const QgsMeshLayer *meshLayer,
const QgsMeshDatasetIndex index,
QgsMeshDataBlock *activeFaceFlagValues,
const QgsMeshRendererScalarSettings::DataInterpolationMethod method )
const QgsMeshRendererScalarSettings::DataResamplingMethod method )
{
QVector<double> ret;

View File

@ -209,7 +209,20 @@ class CORE_EXPORT QgsMeshLayerUtils
const QgsMesh *nativeMesh,
const QgsTriangularMesh *triangularMesh,
QgsMeshDataBlock *active,
QgsMeshRendererScalarSettings::DataInterpolationMethod method
QgsMeshRendererScalarSettings::DataResamplingMethod method
);
/**
* Resamples values on vertices to values on faces
*
* \since QGIS 3.14
*/
static QVector<double> resampleFromVerticesToFaces(
const QVector<double> valuesOnVertices,
const QgsMesh *nativeMesh,
const QgsTriangularMesh *triangularMesh,
const QgsMeshDataBlock *active,
QgsMeshRendererScalarSettings::DataResamplingMethod method
);
/**
@ -226,7 +239,7 @@ class CORE_EXPORT QgsMeshLayerUtils
const QgsMeshLayer *meshLayer,
const QgsMeshDatasetIndex index,
QgsMeshDataBlock *activeFaceFlagValues,
const QgsMeshRendererScalarSettings::DataInterpolationMethod method = QgsMeshRendererScalarSettings::NeighbourAverage );
const QgsMeshRendererScalarSettings::DataResamplingMethod method = QgsMeshRendererScalarSettings::NeighbourAverage );
/**
* Calculates the bounding box of the triangle

View File

@ -102,14 +102,14 @@ double QgsMeshRendererScalarSettings::opacity() const { return mOpacity; }
void QgsMeshRendererScalarSettings::setOpacity( double opacity ) { mOpacity = opacity; }
QgsMeshRendererScalarSettings::DataInterpolationMethod QgsMeshRendererScalarSettings::dataInterpolationMethod() const
QgsMeshRendererScalarSettings::DataResamplingMethod QgsMeshRendererScalarSettings::dataResamplingMethod() const
{
return mDataInterpolationMethod;
return mDataResamplingMethod;
}
void QgsMeshRendererScalarSettings::setDataInterpolationMethod( const QgsMeshRendererScalarSettings::DataInterpolationMethod &dataInterpolationMethod )
void QgsMeshRendererScalarSettings::setDataResamplingMethod( const QgsMeshRendererScalarSettings::DataResamplingMethod &dataInterpolationMethod )
{
mDataInterpolationMethod = dataInterpolationMethod;
mDataResamplingMethod = dataInterpolationMethod;
}
QDomElement QgsMeshRendererScalarSettings::writeXml( QDomDocument &doc ) const
@ -122,7 +122,7 @@ QDomElement QgsMeshRendererScalarSettings::writeXml( QDomDocument &doc ) const
elem.setAttribute( QStringLiteral( "edge-width-unit" ), QgsUnitTypes::encodeUnit( mEdgeWidthUnit ) );
QString methodTxt;
switch ( mDataInterpolationMethod )
switch ( mDataResamplingMethod )
{
case None:
methodTxt = QStringLiteral( "none" );
@ -148,11 +148,11 @@ void QgsMeshRendererScalarSettings::readXml( const QDomElement &elem )
QString methodTxt = elem.attribute( QStringLiteral( "interpolation-method" ) );
if ( QStringLiteral( "neighbour-average" ) == methodTxt )
{
mDataInterpolationMethod = DataInterpolationMethod::NeighbourAverage;
mDataResamplingMethod = DataResamplingMethod::NeighbourAverage;
}
else
{
mDataInterpolationMethod = DataInterpolationMethod::None;
mDataResamplingMethod = DataResamplingMethod::None;
}
QDomElement elemShader = elem.firstChildElement( QStringLiteral( "colorrampshader" ) );
mColorRampShader.readXml( elemShader );

View File

@ -93,18 +93,24 @@ class CORE_EXPORT QgsMeshRendererMeshSettings
class CORE_EXPORT QgsMeshRendererScalarSettings
{
public:
//! Interpolation of value defined on vertices from datasets with data defined on faces
enum DataInterpolationMethod
/**
* Resampling of value from dataset
*
* - for vertices : does a resampling from values defined on surrounding faces
* - for faces : does a resampling from values defined on surrounding vertices
* - for edges : not supported.
*/
enum DataResamplingMethod
{
/**
* Use data defined on face centers, do not interpolate to vertices
* Does not use resampling
*/
None = 0,
/**
* For each vertex does a simple average of values defined for all faces that contains
* given vertex
* Does a simple average of values defined for all surrounding faces/vertices
*/
NeighbourAverage,
};
@ -133,14 +139,14 @@ class CORE_EXPORT QgsMeshRendererScalarSettings
*
* \since QGIS 3.12
*/
DataInterpolationMethod dataInterpolationMethod() const;
DataResamplingMethod dataResamplingMethod() const;
/**
* Sets data interpolation method
*
* \since QGIS 3.12
*/
void setDataInterpolationMethod( const DataInterpolationMethod &dataInterpolationMethod );
void setDataResamplingMethod( const DataResamplingMethod &dataResamplingMethod );
//! Writes configuration to a new DOM element
QDomElement writeXml( QDomDocument &doc ) const;
@ -177,7 +183,7 @@ class CORE_EXPORT QgsMeshRendererScalarSettings
private:
QgsColorRampShader mColorRampShader;
DataInterpolationMethod mDataInterpolationMethod = DataInterpolationMethod::None;
DataResamplingMethod mDataResamplingMethod = DataResamplingMethod::None;
double mClassificationMinimum = 0;
double mClassificationMaximum = 0;
double mOpacity = 1;

View File

@ -401,7 +401,7 @@ void TestQgsMeshRenderer::test_face_scalar_dataset_interpolated_neighbour_averag
QgsMeshRendererSettings rendererSettings = mMemoryLayer->rendererSettings();
rendererSettings.setActiveScalarDataset( ds );
auto scalarRendererSettings = rendererSettings.scalarSettings( 2 );
scalarRendererSettings.setDataInterpolationMethod( QgsMeshRendererScalarSettings::NeighbourAverage );
scalarRendererSettings.setDataResamplingMethod( QgsMeshRendererScalarSettings::NeighbourAverage );
rendererSettings.setScalarSettings( 2, scalarRendererSettings );
mMemoryLayer->setRendererSettings( rendererSettings );
@ -616,6 +616,9 @@ void TestQgsMeshRenderer::test_stacked_3d_mesh_single_level_averaging()
QVERIFY( metadata.name() == "temperature" );
QVERIFY( metadata.maximumVerticalLevelsCount() == 10 );
rendererSettings.setActiveScalarDataset( ds );
QgsMeshRendererScalarSettings scalarSettings = rendererSettings.scalarSettings( ds.group() );
scalarSettings.setDataResamplingMethod( QgsMeshRendererScalarSettings::None );
rendererSettings.setScalarSettings( ds.group(), scalarSettings );
// want to set active vector dataset one defined on 3d mesh
ds = QgsMeshDatasetIndex( 6, 3 );
metadata = mMdal3DLayer->dataProvider()->datasetGroupMetadata( ds );