Skip to content
Permalink
Browse files

Merge pull request #7777 from PeterPetrik/mesh_vector_on_grid

[mesh] [feature] allow render vectors/arrows on the user-defined grid
  • Loading branch information
wonder-sk committed Sep 6, 2018
2 parents da670f9 + 20a5bce commit 1839daaa1891b4ecc94dcc7264440c3db656c20e
@@ -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;
@@ -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() );

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

0 comments on commit 1839daa

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