Skip to content

Commit

Permalink
Instead of double-iterating over features caused by calling
Browse files Browse the repository at this point in the history
QgsVectorLayer::minimumValue and then QgsVectorLayer::maximumValue
when we need BOTH the min and max value for a field, add an
optimised QgsVectorLayer::minimumAndMaximumValue() method
which can calculate both min and max at the same time in
a single iteration.

Potentially halves the cost of calculating these values whenever
we are forced to do a full iteration to calculate them.
  • Loading branch information
nyalldawson committed Mar 27, 2021
1 parent bb76b6b commit 40ed793
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 67 deletions.
62 changes: 56 additions & 6 deletions python/core/auto_generated/vector/qgsvectorlayer.sip.in
Expand Up @@ -2411,28 +2411,78 @@ returned list).

%Docstring
Returns the minimum value for an attribute column or an invalid variant in case of error.
Note that in some circumstances when unsaved changes are present for the layer then the
returned value may be outdated (for instance when the attribute value in a saved feature has
been changed inside the edit buffer then the previous saved value may be returned as the minimum).

.. note::

In some circumstances when unsaved changes are present for the layer then the
returned value may be outdated (for instance when the attribute value in a saved feature has
been changed inside the edit buffer then the previous saved value may be returned as the minimum).

.. note::

If both the minimum and maximum value are required it is more efficient to call :py:func:`~QgsVectorLayer.minimumAndMaximumValue`
instead of separate calls to :py:func:`~QgsVectorLayer.minimumValue` and :py:func:`~QgsVectorLayer.maximumValue`.


.. seealso:: :py:func:`maximumValue`

.. seealso:: :py:func:`minimumAndMaximumValue`

.. seealso:: :py:func:`uniqueValues`
%End

virtual QVariant maximumValue( int index ) const ${SIP_FINAL};

%Docstring
Returns the maximum value for an attribute column or an invalid variant in case of error.
Note that in some circumstances when unsaved changes are present for the layer then the
returned value may be outdated (for instance when the attribute value in a saved feature has
been changed inside the edit buffer then the previous saved value may be returned as the maximum).

.. note::

In some circumstances when unsaved changes are present for the layer then the
returned value may be outdated (for instance when the attribute value in a saved feature has
been changed inside the edit buffer then the previous saved value may be returned as the maximum).

.. note::

If both the minimum and maximum value are required it is more efficient to call :py:func:`~QgsVectorLayer.minimumAndMaximumValue`
instead of separate calls to :py:func:`~QgsVectorLayer.minimumValue` and :py:func:`~QgsVectorLayer.maximumValue`.


.. seealso:: :py:func:`minimumValue`

.. seealso:: :py:func:`minimumAndMaximumValue`

.. seealso:: :py:func:`uniqueValues`
%End


void minimumAndMaximumValue( int index, QVariant &minimum /Out/, QVariant &maximum /Out/ ) const;
%Docstring
Calculates both the minimum and maximum value for an attribute column.

This is more efficient then calling both :py:func:`~QgsVectorLayer.minimumValue` and :py:func:`~QgsVectorLayer.maximumValue` when both the minimum
and maximum values are required.

:param index: index of field to calculate minimum and maximum value for.

.. note::

In some circumstances when unsaved changes are present for the layer then the
calculated values may be outdated (for instance when the attribute value in a saved feature has
been changed inside the edit buffer then the previous saved value may be returned as the maximum).


.. seealso:: :py:func:`minimumValue`

.. seealso:: :py:func:`maximumValue`


:return: - minimum: will be set to minimum attribute value or an invalid variant in case of error.
- maximum: will be set to maximum attribute value or an invalid variant in case of error.

.. versionadded:: 3.20
%End

QVariant aggregate( QgsAggregateCalculator::Aggregate aggregate,
const QString &fieldOrExpression,
const QgsAggregateCalculator::AggregateParameters &parameters = QgsAggregateCalculator::AggregateParameters(),
Expand Down
7 changes: 5 additions & 2 deletions src/core/classification/qgsclassificationmethod.cpp
Expand Up @@ -234,8 +234,11 @@ QList<QgsClassificationRange> QgsClassificationMethod::classes( const QgsVectorL
}
else
{
minimum = layer->minimumValue( fieldIndex ).toDouble();
maximum = layer->maximumValue( fieldIndex ).toDouble();
QVariant minVal;
QVariant maxVal;
layer->minimumAndMaximumValue( fieldIndex, minVal, maxVal );
minimum = minVal.toDouble();
maximum = maxVal.toDouble();
}

// get the breaks, minimum and maximum might be updated by implementation
Expand Down
5 changes: 3 additions & 2 deletions src/core/qgsvectorfilewriter.cpp
Expand Up @@ -2898,8 +2898,9 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::prepareWriteAsVectorFormat
{
if ( details.outputFields.at( i ).type() == QVariant::LongLong )
{
QVariant min = layer->minimumValue( i );
QVariant max = layer->maximumValue( i );
QVariant min;
QVariant max;
layer->minimumAndMaximumValue( i, min, max );
if ( std::max( std::llabs( min.toLongLong() ), std::llabs( max.toLongLong() ) ) < std::numeric_limits<int>::max() )
{
details.outputFields[i].setType( QVariant::Int );
Expand Down
84 changes: 52 additions & 32 deletions src/core/vector/qgsvectorlayer.cpp
Expand Up @@ -4234,59 +4234,75 @@ QStringList QgsVectorLayer::uniqueStringsMatching( int index, const QString &sub

QVariant QgsVectorLayer::minimumValue( int index ) const
{
return minimumOrMaximumValue( index, true );
QVariant minimum;
QVariant maximum;
minimumOrMaximumValue( index, minimum, maximum, true, false );
return minimum;
}

QVariant QgsVectorLayer::maximumValue( int index ) const
{
return minimumOrMaximumValue( index, false );
QVariant minimum;
QVariant maximum;
minimumOrMaximumValue( index, minimum, maximum, false, true );
return maximum;
}

QVariant QgsVectorLayer::minimumOrMaximumValue( int index, bool minimum ) const
void QgsVectorLayer::minimumAndMaximumValue( int index, QVariant &minimum, QVariant &maximum ) const
{
minimumOrMaximumValue( index, minimum, maximum, true, true );
}

void QgsVectorLayer::minimumOrMaximumValue( int index, QVariant &minimum, QVariant &maximum, bool calculateMinimum, bool calculateMaximum ) const
{
if ( !mDataProvider )
{
return QVariant();
minimum = QVariant();
maximum = QVariant();
return;
}

QgsFields::FieldOrigin origin = mFields.fieldOrigin( index );

switch ( origin )
{
case QgsFields::OriginUnknown:
return QVariant();
{
minimum = QVariant();
maximum = QVariant();
return;
}

case QgsFields::OriginProvider: //a provider field
{
QVariant val = minimum ? mDataProvider->minimumValue( index ) : mDataProvider->maximumValue( index );
minimum = calculateMinimum ? mDataProvider->minimumValue( index ) : QVariant();
maximum = calculateMaximum ? mDataProvider->maximumValue( index ) : QVariant();
if ( mEditBuffer && ! mDataProvider->transaction() )
{
QgsFeatureMap added = mEditBuffer->addedFeatures();
const QgsFeatureMap added = mEditBuffer->addedFeatures();
QMapIterator< QgsFeatureId, QgsFeature > addedIt( added );
while ( addedIt.hasNext() )
{
addedIt.next();
QVariant v = addedIt.value().attribute( index );
if ( ( v.isValid() && minimum && qgsVariantLessThan( v, val ) )
|| ( v.isValid() && !minimum && qgsVariantGreaterThan( v, val ) ) )
{
val = v;
}
const QVariant v = addedIt.value().attribute( index );
if ( calculateMinimum && v.isValid() && qgsVariantLessThan( v, minimum ) )
minimum = v;
if ( calculateMaximum && v.isValid() && qgsVariantGreaterThan( v, maximum ) )
maximum = v;
}

QMapIterator< QgsFeatureId, QgsAttributeMap > it( mEditBuffer->changedAttributeValues() );
while ( it.hasNext() )
{
it.next();
QVariant v = it.value().value( index );
if ( ( v.isValid() && minimum && qgsVariantLessThan( v, val ) )
|| ( v.isValid() && !minimum && qgsVariantGreaterThan( v, val ) ) )
{
val = v;
}
const QVariant v = it.value().value( index );
if ( calculateMinimum && v.isValid() && qgsVariantLessThan( v, minimum ) )
minimum = v;
if ( calculateMaximum && v.isValid() && qgsVariantGreaterThan( v, maximum ) )
maximum = v;
}
}
return val;
return;
}

case QgsFields::OriginEdit:
Expand All @@ -4297,14 +4313,19 @@ QVariant QgsVectorLayer::minimumOrMaximumValue( int index, bool minimum ) const
!mEditBuffer->deletedAttributeIds().contains( index ) &&
mEditBuffer->changedAttributeValues().isEmpty() ) )
{
return minimum ? mDataProvider->minimumValue( index ) : mDataProvider->maximumValue( index );
minimum = calculateMinimum ? mDataProvider->minimumValue( index ) : QVariant();
maximum = calculateMaximum ? mDataProvider->maximumValue( index ) : QVariant();
return;
}
}
FALLTHROUGH
// no choice but to go through all features
case QgsFields::OriginExpression:
case QgsFields::OriginJoin:
{
minimum = QVariant();
maximum = QVariant();

// we need to go through each feature
QgsAttributeList attList;
attList << index;
Expand All @@ -4314,33 +4335,32 @@ QVariant QgsVectorLayer::minimumOrMaximumValue( int index, bool minimum ) const
.setSubsetOfAttributes( attList ) );

QgsFeature f;
QVariant value;
QVariant currentValue;
bool firstValue = true;
while ( fit.nextFeature( f ) )
{
currentValue = f.attribute( index );
const QVariant currentValue = f.attribute( index );
if ( currentValue.isNull() )
continue;

if ( firstValue )
{
value = currentValue;
minimum = currentValue;
maximum = currentValue;
firstValue = false;
}
else
{
if ( ( minimum && qgsVariantLessThan( currentValue, value ) ) || ( !minimum && qgsVariantGreaterThan( currentValue, value ) ) )
{
value = currentValue;
}
if ( calculateMinimum && currentValue.isValid() && qgsVariantLessThan( currentValue, minimum ) )
minimum = currentValue;
if ( calculateMaximum && currentValue.isValid() && qgsVariantGreaterThan( currentValue, maximum ) )
maximum = currentValue;
}
}
return value;
return;
}
}

Q_ASSERT_X( false, "QgsVectorLayer::minimumOrMaximum()", "Unknown source of the field!" );
return QVariant();
Q_ASSERT_X( false, "QgsVectorLayer::minimumOrMaximumValue()", "Unknown source of the field!" );
}

QVariant QgsVectorLayer::aggregate( QgsAggregateCalculator::Aggregate aggregate, const QString &fieldOrExpression,
Expand Down
40 changes: 37 additions & 3 deletions src/core/vector/qgsvectorlayer.h
Expand Up @@ -2234,24 +2234,58 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte

/**
* Returns the minimum value for an attribute column or an invalid variant in case of error.
* Note that in some circumstances when unsaved changes are present for the layer then the
*
* \note In some circumstances when unsaved changes are present for the layer then the
* returned value may be outdated (for instance when the attribute value in a saved feature has
* been changed inside the edit buffer then the previous saved value may be returned as the minimum).
*
* \note If both the minimum and maximum value are required it is more efficient to call minimumAndMaximumValue()
* instead of separate calls to minimumValue() and maximumValue().
*
* \see maximumValue()
* \see minimumAndMaximumValue()
* \see uniqueValues()
*/
QVariant minimumValue( int index ) const FINAL;

/**
* Returns the maximum value for an attribute column or an invalid variant in case of error.
* Note that in some circumstances when unsaved changes are present for the layer then the
*
* \note In some circumstances when unsaved changes are present for the layer then the
* returned value may be outdated (for instance when the attribute value in a saved feature has
* been changed inside the edit buffer then the previous saved value may be returned as the maximum).
*
* \note If both the minimum and maximum value are required it is more efficient to call minimumAndMaximumValue()
* instead of separate calls to minimumValue() and maximumValue().
*
* \see minimumValue()
* \see minimumAndMaximumValue()
* \see uniqueValues()
*/
QVariant maximumValue( int index ) const FINAL;


/**
* Calculates both the minimum and maximum value for an attribute column.
*
* This is more efficient then calling both minimumValue() and maximumValue() when both the minimum
* and maximum values are required.
*
* \param index index of field to calculate minimum and maximum value for.
* \param minimum will be set to minimum attribute value or an invalid variant in case of error.
* \param maximum will be set to maximum attribute value or an invalid variant in case of error.
*
* \note In some circumstances when unsaved changes are present for the layer then the
* calculated values may be outdated (for instance when the attribute value in a saved feature has
* been changed inside the edit buffer then the previous saved value may be returned as the maximum).
*
* \see minimumValue()
* \see maximumValue()
*
* \since QGIS 3.20
*/
void minimumAndMaximumValue( int index, QVariant &minimum SIP_OUT, QVariant &maximum SIP_OUT ) const;

/**
* Calculates an aggregated value from the layer's features.
* Currently any filtering expression provided will override filters in the FeatureRequest.
Expand Down Expand Up @@ -2834,7 +2868,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
QgsVectorLayer( const QgsVectorLayer &rhs );
#endif
//! Returns the minimum or maximum value
QVariant minimumOrMaximumValue( int index, bool minimum ) const;
void minimumOrMaximumValue( int index, QVariant &minimum, QVariant &maximum, bool calculateMinimum, bool calculateMaximum ) const;

private: // Private attributes
QgsConditionalLayerStyles *mConditionalStyles = nullptr;
Expand Down
37 changes: 27 additions & 10 deletions src/core/vector/qgsvectorlayertemporalproperties.cpp
Expand Up @@ -63,8 +63,12 @@ QgsDateTimeRange QgsVectorLayerTemporalProperties::calculateTemporalExtent( QgsM
const int fieldIndex = vectorLayer->fields().lookupField( mStartFieldName );
if ( fieldIndex >= 0 )
{
const QDateTime min = vectorLayer->minimumValue( fieldIndex ).toDateTime();
const QDateTime maxStartTime = vectorLayer->maximumValue( fieldIndex ).toDateTime();
QVariant minVal;
QVariant maxVal;
vectorLayer->minimumAndMaximumValue( fieldIndex, minVal, maxVal );

const QDateTime min = minVal.toDateTime();
const QDateTime maxStartTime = maxVal.toDateTime();
const QgsInterval eventDuration = QgsInterval( mFixedDuration, mDurationUnit );
return QgsDateTimeRange( min, maxStartTime + eventDuration );
}
Expand Down Expand Up @@ -109,20 +113,33 @@ QgsDateTimeRange QgsVectorLayerTemporalProperties::calculateTemporalExtent( QgsM
const int endFieldIndex = vectorLayer->fields().lookupField( mEndFieldName );
if ( startFieldIndex >= 0 && endFieldIndex >= 0 )
{
return QgsDateTimeRange( std::min( vectorLayer->minimumValue( startFieldIndex ).toDateTime(),
vectorLayer->minimumValue( endFieldIndex ).toDateTime() ),
std::max( vectorLayer->maximumValue( startFieldIndex ).toDateTime(),
vectorLayer->maximumValue( endFieldIndex ).toDateTime() ) );
QVariant startMinVal;
QVariant startMaxVal;
vectorLayer->minimumAndMaximumValue( startFieldIndex, startMinVal, startMaxVal );
QVariant endMinVal;
QVariant endMaxVal;
vectorLayer->minimumAndMaximumValue( endFieldIndex, endMinVal, endMaxVal );

return QgsDateTimeRange( std::min( startMinVal.toDateTime(),
endMinVal.toDateTime() ),
std::max( startMaxVal.toDateTime(),
endMaxVal.toDateTime() ) );
}
else if ( startFieldIndex >= 0 )
{
return QgsDateTimeRange( vectorLayer->minimumValue( startFieldIndex ).toDateTime(),
vectorLayer->maximumValue( startFieldIndex ).toDateTime() );
QVariant startMinVal;
QVariant startMaxVal;
vectorLayer->minimumAndMaximumValue( startFieldIndex, startMinVal, startMaxVal );
return QgsDateTimeRange( startMinVal.toDateTime(),
startMaxVal.toDateTime() );
}
else if ( endFieldIndex >= 0 )
{
return QgsDateTimeRange( vectorLayer->minimumValue( endFieldIndex ).toDateTime(),
vectorLayer->maximumValue( endFieldIndex ).toDateTime() );
QVariant endMinVal;
QVariant endMaxVal;
vectorLayer->minimumAndMaximumValue( endFieldIndex, endMinVal, endMaxVal );
return QgsDateTimeRange( endMinVal.toDateTime(),
endMaxVal.toDateTime() );
}
break;
}
Expand Down

0 comments on commit 40ed793

Please sign in to comment.