Rename QgsSpatialIndex::insertFeature to ::addFeature, for consistency

with other classes

And make QgsSpatialIndex a QgsFeatureSink
This commit is contained in:
Nyall Dawson 2018-09-28 13:21:01 +10:00
parent 63f597f934
commit 6110931f8a
21 changed files with 139 additions and 49 deletions

View File

@ -14,7 +14,7 @@
class QgsSpatialIndex class QgsSpatialIndex : QgsFeatureSink
{ {
%Docstring %Docstring
@ -75,18 +75,49 @@ Copy constructor
bool insertFeature( const QgsFeature &feature ); bool insertFeature( const QgsFeature &feature ) /Deprecated/;
%Docstring %Docstring
Adds a ``feature`` to the index. Adds a ``feature`` to the index.
.. deprecated:: Use addFeature() instead
%End %End
bool insertFeature( QgsFeatureId id, const QgsRectangle &bounds ); virtual bool addFeature( QgsFeature &feature, QgsFeatureSink::Flags flags = 0 );
%Docstring
Adds a ``feature`` to the index.
The ``flags`` argument is ignored.
.. versionadded:: 3.4
%End
virtual bool addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags = 0 );
%Docstring
Adds a list of ``features`` to the index.
The ``flags`` argument is ignored.
.. seealso:: :py:func:`addFeature`
%End
bool insertFeature( QgsFeatureId id, const QgsRectangle &bounds ) /Deprecated/;
%Docstring %Docstring
Add a feature ``id`` to the index with a specified bounding box. Add a feature ``id`` to the index with a specified bounding box.
:return: true if feature was successfully added to index. :return: true if feature was successfully added to index.
.. versionadded:: 3.0 .. deprecated:: Use addFeature() instead
%End
bool addFeature( QgsFeatureId id, const QgsRectangle &bounds );
%Docstring
Add a feature ``id`` to the index with a specified bounding box.
:return: true if feature was successfully added to index.
.. versionadded:: 3.4
%End %End
bool deleteFeature( const QgsFeature &feature ); bool deleteFeature( const QgsFeature &feature );

View File

@ -28,6 +28,7 @@ __revision__ = '$Format:%H$'
from qgis.core import (QgsFeatureRequest, from qgis.core import (QgsFeatureRequest,
QgsProcessingException, QgsProcessingException,
QgsFeatureSink, QgsFeatureSink,
QgsSpatialIndex,
QgsProcessingParameterFeatureSource, QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink) QgsProcessingParameterFeatureSink)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
@ -71,11 +72,13 @@ class DeleteDuplicateGeometries(QgisAlgorithm):
features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([])) features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]))
total = 100.0 / source.featureCount() if source.featureCount() else 0 total = 100.0 / source.featureCount() if source.featureCount() else 0
geoms = dict() geoms = dict()
index = QgsSpatialIndex()
for current, f in enumerate(features): for current, f in enumerate(features):
if feedback.isCanceled(): if feedback.isCanceled():
break break
geoms[f.id()] = f.geometry() geoms[f.id()] = f.geometry()
#index.insertFeature
feedback.setProgress(int(current * total)) feedback.setProgress(int(current * total))
cleaned = dict(geoms) cleaned = dict(geoms)

View File

@ -116,7 +116,7 @@ class PointsDisplacement(QgisAlgorithm):
other_features_within_radius = index.intersects(searchRect(point)) other_features_within_radius = index.intersects(searchRect(point))
if not other_features_within_radius: if not other_features_within_radius:
index.insertFeature(f) index.addFeature(f)
group = [f] group = [f]
clustered_groups.append(group) clustered_groups.append(group)
group_index[f.id()] = len(clustered_groups) - 1 group_index[f.id()] = len(clustered_groups) - 1

View File

@ -163,7 +163,7 @@ class RandomPointsAlongLines(QgisAlgorithm):
f.setAttribute('id', nPoints) f.setAttribute('id', nPoints)
f.setGeometry(geom) f.setGeometry(geom)
sink.addFeature(f, QgsFeatureSink.FastInsert) sink.addFeature(f, QgsFeatureSink.FastInsert)
index.insertFeature(f) index.addFeature(f)
points[nPoints] = p points[nPoints] = p
nPoints += 1 nPoints += 1
feedback.setProgress(int(nPoints * total)) feedback.setProgress(int(nPoints * total))

View File

@ -142,7 +142,7 @@ class RandomPointsExtent(QgisAlgorithm):
f.setAttribute('id', nPoints) f.setAttribute('id', nPoints)
f.setGeometry(geom) f.setGeometry(geom)
sink.addFeature(f, QgsFeatureSink.FastInsert) sink.addFeature(f, QgsFeatureSink.FastInsert)
index.insertFeature(f) index.addFeature(f)
points[nPoints] = p points[nPoints] = p
nPoints += 1 nPoints += 1
feedback.setProgress(int(nPoints * total)) feedback.setProgress(int(nPoints * total))

View File

@ -151,7 +151,7 @@ class RandomPointsLayer(QgisAlgorithm):
f.setAttribute('id', nPoints) f.setAttribute('id', nPoints)
f.setGeometry(geom) f.setGeometry(geom)
sink.addFeature(f, QgsFeatureSink.FastInsert) sink.addFeature(f, QgsFeatureSink.FastInsert)
index.insertFeature(f) index.addFeature(f)
points[nPoints] = p points[nPoints] = p
nPoints += 1 nPoints += 1
feedback.setProgress(int(nPoints * total)) feedback.setProgress(int(nPoints * total))

View File

@ -197,7 +197,7 @@ class RandomPointsPolygons(QgisAlgorithm):
f.setAttribute('id', nPoints) f.setAttribute('id', nPoints)
f.setGeometry(geom) f.setGeometry(geom)
sink.addFeature(f, QgsFeatureSink.FastInsert) sink.addFeature(f, QgsFeatureSink.FastInsert)
index.insertFeature(f) index.addFeature(f)
points[nPoints] = p points[nPoints] = p
nPoints += 1 nPoints += 1
feedback.setProgress(current_progress + int(nPoints * feature_total)) feedback.setProgress(current_progress + int(nPoints * feature_total))

View File

@ -190,7 +190,7 @@ class TopoColor(QgisAlgorithm):
if id_graph: if id_graph:
id_graph.add_edge(f.id(), f2.id()) id_graph.add_edge(f.id(), f2.id())
index.insertFeature(f) index.addFeature(f)
i += 1 i += 1
feedback.setProgress(int(i * total)) feedback.setProgress(int(i * total))

View File

@ -118,7 +118,7 @@ QVariantMap QgsSplitWithLinesAlgorithm::processAlgorithm( const QVariantMap &par
} }
splitGeoms.insert( aSplitFeature.id(), aSplitFeature.geometry() ); splitGeoms.insert( aSplitFeature.id(), aSplitFeature.geometry() );
spatialIndex.insertFeature( aSplitFeature ); spatialIndex.addFeature( aSplitFeature );
} }
QgsFeature outFeat; QgsFeature outFeat;

View File

@ -298,7 +298,7 @@ void QgsOverlayUtils::resolveOverlaps( const QgsFeatureSource &source, QgsFeatur
std::unique_ptr< QgsGeometryEngine > g1engine; std::unique_ptr< QgsGeometryEngine > g1engine;
geometries.insert( fid1, g1 ); geometries.insert( fid1, g1 );
index.insertFeature( f ); index.addFeature( f );
QgsRectangle bbox( f.geometry().boundingBox() ); QgsRectangle bbox( f.geometry().boundingBox() );
const QList<QgsFeatureId> ids = index.intersects( bbox ); const QList<QgsFeatureId> ids = index.intersects( bbox );
@ -335,7 +335,7 @@ void QgsOverlayUtils::resolveOverlaps( const QgsFeatureSource &source, QgsFeatur
QgsFeature fx( newFid ); QgsFeature fx( newFid );
fx.setGeometry( geomIntersection ); fx.setGeometry( geomIntersection );
index.insertFeature( fx ); index.addFeature( fx );
// figure out which feature IDs belong to this intersection. Some of the IDs can be of the newly // figure out which feature IDs belong to this intersection. Some of the IDs can be of the newly
// created geometries - in such case we need to retrieve original IDs // created geometries - in such case we need to retrieve original IDs
@ -365,7 +365,7 @@ void QgsOverlayUtils::resolveOverlaps( const QgsFeatureSource &source, QgsFeatur
QgsFeature f1x( fid1 ); QgsFeature f1x( fid1 );
f1x.setGeometry( g12 ); f1x.setGeometry( g12 );
index.insertFeature( f1x ); index.addFeature( f1x );
} }
// //
@ -386,7 +386,7 @@ void QgsOverlayUtils::resolveOverlaps( const QgsFeatureSource &source, QgsFeatur
QgsFeature f2x( fid2 ); QgsFeature f2x( fid2 );
f2x.setGeometry( g21 ); f2x.setGeometry( g21 );
index.insertFeature( f2x ); index.addFeature( f2x );
} }
// update our temporary copy of the geometry to what is left from it // update our temporary copy of the geometry to what is left from it

View File

@ -57,7 +57,7 @@ bool QgsFeaturePool::getFeature( QgsFeatureId id, QgsFeature &feature )
} }
locker.changeMode( QgsReadWriteLocker::Write ); locker.changeMode( QgsReadWriteLocker::Write );
mFeatureCache.insert( id, new QgsFeature( feature ) ); mFeatureCache.insert( id, new QgsFeature( feature ) );
mIndex.insertFeature( feature ); mIndex.addFeature( feature );
} }
return true; return true;
} }
@ -90,7 +90,8 @@ void QgsFeaturePool::insertFeature( const QgsFeature &feature )
{ {
QgsReadWriteLocker locker( mCacheLock, QgsReadWriteLocker::Write ); QgsReadWriteLocker locker( mCacheLock, QgsReadWriteLocker::Write );
mFeatureCache.insert( feature.id(), new QgsFeature( feature ) ); mFeatureCache.insert( feature.id(), new QgsFeature( feature ) );
mIndex.insertFeature( feature ); QgsFeature indexFeature( feature );
mIndex.addFeature( indexFeature );
} }
void QgsFeaturePool::refreshCache( const QgsFeature &feature ) void QgsFeaturePool::refreshCache( const QgsFeature &feature )

View File

@ -768,7 +768,7 @@ QgsGeometry QgsInternalGeometrySnapper::snapFeature( const QgsFeature &feature )
} }
} }
mProcessedGeometries.insert( feat.id(), geometry ); mProcessedGeometries.insert( feat.id(), geometry );
mProcessedIndex.insertFeature( feat ); mProcessedIndex.addFeature( feat );
mFirstFeature = false; mFirstFeature = false;
return geometry; return geometry;
} }

View File

@ -70,7 +70,7 @@ static void buildSnapIndex( QgsFeatureIterator &fi, QgsSpatialIndex &index, QVec
if ( ids.isEmpty() ) if ( ids.isEmpty() )
{ {
// add to tree and to structure // add to tree and to structure
index.insertFeature( pntId, pt.boundingBox() ); index.addFeature( pntId, pt.boundingBox() );
AnchorPoint xp; AnchorPoint xp;
xp.x = pt.x(); xp.x = pt.x();

View File

@ -395,7 +395,7 @@ bool QgsMemoryProvider::addFeatures( QgsFeatureList &flist, Flags )
// update spatial index // update spatial index
if ( mSpatialIndex ) if ( mSpatialIndex )
mSpatialIndex->insertFeature( *it ); mSpatialIndex->addFeature( *it );
} }
mNextFeatureId++; mNextFeatureId++;
@ -541,7 +541,7 @@ bool QgsMemoryProvider::changeGeometryValues( const QgsGeometryMap &geometry_map
// update spatial index // update spatial index
if ( mSpatialIndex ) if ( mSpatialIndex )
mSpatialIndex->insertFeature( *fit ); mSpatialIndex->addFeature( *fit );
} }
updateExtents(); updateExtents();
@ -583,9 +583,9 @@ bool QgsMemoryProvider::createSpatialIndex()
mSpatialIndex = new QgsSpatialIndex(); mSpatialIndex = new QgsSpatialIndex();
// add existing features to index // add existing features to index
for ( QgsFeatureMap::const_iterator it = mFeatures.constBegin(); it != mFeatures.constEnd(); ++it ) for ( QgsFeatureMap::iterator it = mFeatures.begin(); it != mFeatures.end(); ++it )
{ {
mSpatialIndex->insertFeature( *it ); mSpatialIndex->addFeature( *it );
} }
} }
return true; return true;

View File

@ -299,19 +299,41 @@ bool QgsSpatialIndex::featureInfo( const QgsFeature &f, QgsRectangle &rect, QgsF
return true; return true;
} }
bool QgsSpatialIndex::insertFeature( const QgsFeature &f ) bool QgsSpatialIndex::addFeature( QgsFeature &feature, QgsFeatureSink::Flags )
{ {
QgsRectangle rect; QgsRectangle rect;
QgsFeatureId id; QgsFeatureId id;
if ( !featureInfo( f, rect, id ) ) if ( !featureInfo( feature, rect, id ) )
return false; return false;
return insertFeature( id, rect ); return addFeature( id, rect );
} }
bool QgsSpatialIndex::insertFeature( QgsFeatureId id, const QgsRectangle &rect ) bool QgsSpatialIndex::addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags )
{ {
SpatialIndex::Region r( rectToRegion( rect ) ); QgsFeatureList::iterator fIt = features.begin();
bool result = true;
for ( ; fIt != features.end(); ++fIt )
{
result = result && addFeature( *fIt, flags );
}
return result;
}
bool QgsSpatialIndex::insertFeature( const QgsFeature &f )
{
QgsFeature feature( f );
return addFeature( feature );
}
bool QgsSpatialIndex::insertFeature( QgsFeatureId id, const QgsRectangle &bounds )
{
return addFeature( id, bounds );
}
bool QgsSpatialIndex::addFeature( QgsFeatureId id, const QgsRectangle &bounds )
{
SpatialIndex::Region r( rectToRegion( bounds ) );
QMutexLocker locker( &d->mMutex ); QMutexLocker locker( &d->mMutex );
@ -324,16 +346,16 @@ bool QgsSpatialIndex::insertFeature( QgsFeatureId id, const QgsRectangle &rect )
catch ( Tools::Exception &e ) catch ( Tools::Exception &e )
{ {
Q_UNUSED( e ); Q_UNUSED( e );
QgsDebugMsg( QString( "Tools::Exception caught: " ).arg( e.what().c_str() ) ); QgsDebugMsg( QStringLiteral( "Tools::Exception caught: " ).arg( e.what().c_str() ) );
} }
catch ( const std::exception &e ) catch ( const std::exception &e )
{ {
Q_UNUSED( e ); Q_UNUSED( e );
QgsDebugMsg( QString( "std::exception caught: " ).arg( e.what() ) ); QgsDebugMsg( QStringLiteral( "std::exception caught: " ).arg( e.what() ) );
} }
catch ( ... ) catch ( ... )
{ {
QgsDebugMsg( "unknown spatial index exception caught" ); QgsDebugMsg( QStringLiteral( "unknown spatial index exception caught" ) );
} }
return false; return false;

View File

@ -40,6 +40,7 @@ class QgsPointXY;
#include "qgis_core.h" #include "qgis_core.h"
#include "qgis_sip.h" #include "qgis_sip.h"
#include "qgsfeaturesink.h"
#include <QList> #include <QList>
#include <QSharedDataPointer> #include <QSharedDataPointer>
@ -63,7 +64,7 @@ class QgsFeatureSource;
* *
* \see QgsSpatialIndexKDBush, which is an optimised non-mutable index for point geometries only. * \see QgsSpatialIndexKDBush, which is an optimised non-mutable index for point geometries only.
*/ */
class CORE_EXPORT QgsSpatialIndex class CORE_EXPORT QgsSpatialIndex : public QgsFeatureSink
{ {
public: public:
@ -104,7 +105,7 @@ class CORE_EXPORT QgsSpatialIndex
QgsSpatialIndex( const QgsSpatialIndex &other ); QgsSpatialIndex( const QgsSpatialIndex &other );
//! Destructor finalizes work with spatial index //! Destructor finalizes work with spatial index
~QgsSpatialIndex(); ~QgsSpatialIndex() override;
//! Implement assignment operator //! Implement assignment operator
QgsSpatialIndex &operator=( const QgsSpatialIndex &other ); QgsSpatialIndex &operator=( const QgsSpatialIndex &other );
@ -113,15 +114,41 @@ class CORE_EXPORT QgsSpatialIndex
/** /**
* Adds a \a feature to the index. * Adds a \a feature to the index.
* \deprecated Use addFeature() instead
*/ */
bool insertFeature( const QgsFeature &feature ); Q_DECL_DEPRECATED bool insertFeature( const QgsFeature &feature ) SIP_DEPRECATED;
/**
* Adds a \a feature to the index.
*
* The \a flags argument is ignored.
*
* \since QGIS 3.4
*/
bool addFeature( QgsFeature &feature, QgsFeatureSink::Flags flags = nullptr ) override;
/**
* Adds a list of \a features to the index.
*
* The \a flags argument is ignored.
*
* \see addFeature()
*/
bool addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags = nullptr ) override;
/** /**
* Add a feature \a id to the index with a specified bounding box. * Add a feature \a id to the index with a specified bounding box.
* \returns true if feature was successfully added to index. * \returns true if feature was successfully added to index.
* \since QGIS 3.0 * \deprecated Use addFeature() instead
*/ */
bool insertFeature( QgsFeatureId id, const QgsRectangle &bounds ); Q_DECL_DEPRECATED bool insertFeature( QgsFeatureId id, const QgsRectangle &bounds ) SIP_DEPRECATED;
/**
* Add a feature \a id to the index with a specified bounding box.
* \returns true if feature was successfully added to index.
* \since QGIS 3.4
*/
bool addFeature( QgsFeatureId id, const QgsRectangle &bounds );
/** /**
* Removes a \a feature from the index. * Removes a \a feature from the index.

View File

@ -94,7 +94,7 @@ bool QgsPointDistanceRenderer::renderFeature( const QgsFeature &feature, QgsRend
QList<QgsFeatureId> intersectList = mSpatialIndex->intersects( searchRect( point, searchDistance ) ); QList<QgsFeatureId> intersectList = mSpatialIndex->intersects( searchRect( point, searchDistance ) );
if ( intersectList.empty() ) if ( intersectList.empty() )
{ {
mSpatialIndex->insertFeature( transformedFeature ); mSpatialIndex->addFeature( transformedFeature );
// create new group // create new group
ClusteredGroup newGroup; ClusteredGroup newGroup;
newGroup << GroupedFeature( transformedFeature, symbol->clone(), selected, label ); newGroup << GroupedFeature( transformedFeature, symbol->clone(), selected, label );

View File

@ -1369,7 +1369,7 @@ QgsSpatialIndex *topolTest::createIndex( QgsVectorLayer *layer, const QgsRectang
if ( f.hasGeometry() ) if ( f.hasGeometry() )
{ {
index->insertFeature( f ); index->addFeature( f );
mFeatureMap2[f.id()] = FeatureLayer( layer, f ); mFeatureMap2[f.id()] = FeatureLayer( layer, f );
} }
} }

View File

@ -446,7 +446,7 @@ void QgsDelimitedTextProvider::scanFile( bool buildIndexes )
QgsFeature f; QgsFeature f;
f.setId( mFile->recordId() ); f.setId( mFile->recordId() );
f.setGeometry( geom ); f.setGeometry( geom );
mSpatialIndex->insertFeature( f ); mSpatialIndex->addFeature( f );
} }
} }
else else
@ -501,7 +501,7 @@ void QgsDelimitedTextProvider::scanFile( bool buildIndexes )
QgsFeature f; QgsFeature f;
f.setId( mFile->recordId() ); f.setId( mFile->recordId() );
f.setGeometry( QgsGeometry::fromPointXY( pt ) ); f.setGeometry( QgsGeometry::fromPointXY( pt ) );
mSpatialIndex->insertFeature( f ); mSpatialIndex->addFeature( f );
} }
} }
else else
@ -770,7 +770,7 @@ void QgsDelimitedTextProvider::rescanFile() const
mExtent.combineExtentWith( bbox ); mExtent.combineExtentWith( bbox );
} }
if ( buildSpatialIndex ) if ( buildSpatialIndex )
mSpatialIndex->insertFeature( f ); mSpatialIndex->addFeature( f );
} }
if ( buildSubsetIndex ) if ( buildSubsetIndex )
mSubsetIndex.append( ( quintptr ) f.id() ); mSubsetIndex.append( ( quintptr ) f.id() );

View File

@ -1037,7 +1037,7 @@ void QgsWFSSharedData::endOfDownload( bool success, int featureCount,
f.initAttributes( 1 ); f.initAttributes( 1 );
f.setAttribute( 0, QVariant( bDownloadLimit ) ); f.setAttribute( 0, QVariant( bDownloadLimit ) );
mRegions.push_back( f ); mRegions.push_back( f );
mCachedRegions.insertFeature( f ); mCachedRegions.addFeature( f );
} }
} }

View File

@ -70,7 +70,10 @@ class TestQgsSpatialIndex : public QObject
{ {
QgsSpatialIndex index; QgsSpatialIndex index;
Q_FOREACH ( const QgsFeature &f, _pointFeatures() ) Q_FOREACH ( const QgsFeature &f, _pointFeatures() )
index.insertFeature( f ); {
QgsFeature indexFeature( f );
index.addFeature( indexFeature );
}
QList<QgsFeatureId> fids = index.intersects( QgsRectangle( 0, 0, 10, 10 ) ); QList<QgsFeatureId> fids = index.intersects( QgsRectangle( 0, 0, 10, 10 ) );
QVERIFY( fids.count() == 1 ); QVERIFY( fids.count() == 1 );
@ -85,9 +88,9 @@ class TestQgsSpatialIndex : public QObject
void testQueryManualInsert() void testQueryManualInsert()
{ {
QgsSpatialIndex index; QgsSpatialIndex index;
index.insertFeature( 1, QgsRectangle( 2, 3, 2, 3 ) ); index.addFeature( 1, QgsRectangle( 2, 3, 2, 3 ) );
index.insertFeature( 2, QgsRectangle( 12, 13, 12, 13 ) ); index.addFeature( 2, QgsRectangle( 12, 13, 12, 13 ) );
index.insertFeature( 3, QgsRectangle( 14, 13, 14, 13 ) ); index.addFeature( 3, QgsRectangle( 14, 13, 14, 13 ) );
QList<QgsFeatureId> fids = index.intersects( QgsRectangle( 1, 2, 3, 4 ) ); QList<QgsFeatureId> fids = index.intersects( QgsRectangle( 1, 2, 3, 4 ) );
QVERIFY( fids.count() == 1 ); QVERIFY( fids.count() == 1 );
@ -110,7 +113,10 @@ class TestQgsSpatialIndex : public QObject
{ {
QgsSpatialIndex *index = new QgsSpatialIndex; QgsSpatialIndex *index = new QgsSpatialIndex;
Q_FOREACH ( const QgsFeature &f, _pointFeatures() ) Q_FOREACH ( const QgsFeature &f, _pointFeatures() )
index->insertFeature( f ); {
QgsFeature indexFeature( f );
index->addFeature( indexFeature );
}
// create copy of the index // create copy of the index
QgsSpatialIndex indexCopy( *index ); QgsSpatialIndex indexCopy( *index );
@ -154,7 +160,7 @@ class TestQgsSpatialIndex : public QObject
QgsFeature f( i * 1000 + k ); QgsFeature f( i * 1000 + k );
QgsGeometry g = QgsGeometry::fromPointXY( QgsPointXY( i / 10, i % 10 ) ); QgsGeometry g = QgsGeometry::fromPointXY( QgsPointXY( i / 10, i % 10 ) );
f.setGeometry( g ); f.setGeometry( g );
index.insertFeature( f ); index.addFeature( f );
} }
} }
@ -207,7 +213,7 @@ class TestQgsSpatialIndex : public QObject
QgsFeature f; QgsFeature f;
indexInsert = new QgsSpatialIndex; indexInsert = new QgsSpatialIndex;
while ( fi.nextFeature( f ) ) while ( fi.nextFeature( f ) )
indexInsert->insertFeature( f ); indexInsert->addFeature( f );
} }
qDebug( "insert: %d ms", t.elapsed() ); qDebug( "insert: %d ms", t.elapsed() );