20#include <unordered_set>
26QString QgsDbscanClusteringAlgorithm::name()
const
28 return QStringLiteral(
"dbscanclustering" );
31QString QgsDbscanClusteringAlgorithm::displayName()
const
33 return QObject::tr(
"DBSCAN clustering" );
36QString QgsDbscanClusteringAlgorithm::shortDescription()
const
38 return QObject::tr(
"Clusters point features using a density based scan algorithm." );
41QStringList QgsDbscanClusteringAlgorithm::tags()
const
43 return QObject::tr(
"clustering,clusters,density,based,points,distance" ).split(
',' );
46QString QgsDbscanClusteringAlgorithm::group()
const
48 return QObject::tr(
"Vector analysis" );
51QString QgsDbscanClusteringAlgorithm::groupId()
const
53 return QStringLiteral(
"vectoranalysis" );
56void QgsDbscanClusteringAlgorithm::initAlgorithm(
const QVariantMap & )
60 addParameter(
new QgsProcessingParameterDistance( QStringLiteral(
"EPS" ), QObject::tr(
"Maximum distance between clustered points" ), 1, QStringLiteral(
"INPUT" ),
false, 0 ) );
62 auto dbscanStarParam = std::make_unique<QgsProcessingParameterBoolean>( QStringLiteral(
"DBSCAN*" ), QObject::tr(
"Treat border points as noise (DBSCAN*)" ),
false );
64 addParameter( dbscanStarParam.release() );
66 auto fieldNameParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral(
"FIELD_NAME" ), QObject::tr(
"Cluster field name" ), QStringLiteral(
"CLUSTER_ID" ) );
68 addParameter( fieldNameParam.release() );
69 auto sizeFieldNameParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral(
"SIZE_FIELD_NAME" ), QObject::tr(
"Cluster size field name" ), QStringLiteral(
"CLUSTER_SIZE" ) );
71 addParameter( sizeFieldNameParam.release() );
78QString QgsDbscanClusteringAlgorithm::shortHelpString()
const
80 return QObject::tr(
"This algorithm clusters point features based on a 2D implementation of Density-based spatial clustering of applications with noise (DBSCAN) algorithm.\n\n"
81 "The algorithm requires two parameters, a minimum cluster size (“minPts”), and the maximum distance allowed between clustered points (“eps”)." );
84QgsDbscanClusteringAlgorithm *QgsDbscanClusteringAlgorithm::createInstance()
const
86 return new QgsDbscanClusteringAlgorithm();
89struct KDBushDataEqualById
91 bool operator()(
const QgsSpatialIndexKDBushData &a,
const QgsSpatialIndexKDBushData &b )
const
97struct KDBushDataHashById
99 std::size_t operator()(
const QgsSpatialIndexKDBushData &a )
const
101 return std::hash<QgsFeatureId> {}( a.
id );
107 std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, QStringLiteral(
"INPUT" ), context ) );
111 const std::size_t minSize =
static_cast<std::size_t
>( parameterAsInt( parameters, QStringLiteral(
"MIN_SIZE" ), context ) );
112 const double eps1 = parameterAsDouble( parameters, QStringLiteral(
"EPS" ), context );
113 const double eps2 = parameterAsDouble( parameters, QStringLiteral(
"EPS2" ), context );
114 const bool borderPointsAreNoise = parameterAsBoolean( parameters, QStringLiteral(
"DBSCAN*" ), context );
116 QgsFields outputFields = source->fields();
118 const QString clusterFieldName = parameterAsString( parameters, QStringLiteral(
"FIELD_NAME" ), context );
119 newFields.
append(
QgsField( clusterFieldName, QMetaType::Type::Int ) );
120 const QString clusterSizeFieldName = parameterAsString( parameters, QStringLiteral(
"SIZE_FIELD_NAME" ), context );
121 newFields.
append(
QgsField( clusterSizeFieldName, QMetaType::Type::Int ) );
125 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, QStringLiteral(
"OUTPUT" ), context, dest, outputFields, source->wkbType(), source->sourceCrs() ) );
131 std::unordered_map<QgsFeatureId, QDateTime> idToDateTime;
132 const QString dateTimeFieldName = parameterAsString( parameters, QStringLiteral(
"DATETIME_FIELD" ), context );
133 int dateTimefieldIndex = -1;
134 if ( !dateTimeFieldName.isEmpty() )
136 dateTimefieldIndex = source->fields().lookupField( dateTimeFieldName );
137 if ( dateTimefieldIndex == -1 )
148 feedback->
pushInfo( QObject::tr(
"Building spatial index" ) );
151 if ( dateTimefieldIndex >= 0 )
152 idToDateTime[ feature.
id() ] = feature.
attributes().at( dateTimefieldIndex ).toDateTime();
153 return true; }, feedback );
156 return QVariantMap();
159 feedback->
pushInfo( QObject::tr(
"Analysing clusters" ) );
160 std::unordered_map<QgsFeatureId, int> idToCluster;
161 idToCluster.reserve( index.size() );
162 const long featureCount = source->featureCount();
164 stdbscan( minSize, eps1, eps2, borderPointsAreNoise, featureCount, features, index, idToCluster, idToDateTime, feedback );
167 std::unordered_map<int, int> clusterSize;
168 std::for_each( idToCluster.begin(), idToCluster.end(), [&clusterSize]( std::pair<QgsFeatureId, int> idCluster ) { clusterSize[idCluster.second]++; } );
171 const double writeStep = featureCount > 0 ? 10.0 / featureCount : 1;
172 features = source->getFeatures();
185 const auto cluster = idToCluster.find( feat.
id() );
186 if ( cluster != idToCluster.end() )
188 attr << cluster->second << clusterSize[cluster->second];
192 attr << QVariant() << QVariant();
202 outputs.insert( QStringLiteral(
"OUTPUT" ), dest );
203 outputs.insert( QStringLiteral(
"NUM_CLUSTERS" ),
static_cast<unsigned int>( clusterSize.size() ) );
207void QgsDbscanClusteringAlgorithm::stdbscan(
const std::size_t minSize,
const double eps1,
const double eps2,
const bool borderPointsAreNoise,
const long featureCount,
QgsFeatureIterator features,
QgsSpatialIndexKDBush &index, std::unordered_map<QgsFeatureId, int> &idToCluster, std::unordered_map<QgsFeatureId, QDateTime> &idToDateTime,
QgsProcessingFeedback *feedback )
209 const double step = featureCount > 0 ? 90.0 / featureCount : 1;
211 std::unordered_set<QgsFeatureId> visited;
212 visited.reserve( index.
size() );
216 int clusterCount = 0;
231 if ( visited.find( feat.
id() ) != visited.end() )
248 if ( !idToDateTime.empty() && !idToDateTime[feat.
id()].isValid() )
256 std::unordered_set<QgsSpatialIndexKDBushData, KDBushDataHashById, KDBushDataEqualById> within;
261 if ( idToDateTime.empty() || ( idToDateTime[data.id].isValid() && std::abs( idToDateTime[pointId].msecsTo( idToDateTime[data.id] ) ) <= eps2 ) )
262 within.insert( data );
264 if ( within.size() < minSize )
267 visited.insert( feat.
id() );
277 idToCluster[feat.
id()] = clusterCount;
280 while ( !within.empty() )
288 within.erase( within.begin() );
290 if ( visited.find( j.
id ) != visited.end() )
296 visited.insert( j.
id );
302 std::unordered_set<QgsSpatialIndexKDBushData, KDBushDataHashById, KDBushDataEqualById> within2;
304 if ( idToDateTime.empty() || ( idToDateTime[data.id].isValid() && std::abs( idToDateTime[point2Id].msecsTo( idToDateTime[data.id] ) ) <= eps2 ) )
305 within2.insert( data );
308 if ( within2.size() >= minSize )
311 std::copy_if( within2.begin(), within2.end(), std::inserter( within, within.end() ), [&visited](
const QgsSpatialIndexKDBushData &needle ) {
312 return visited.find( needle.id ) == visited.end();
315 if ( !borderPointsAreNoise || within2.size() >= minSize )
317 idToCluster[j.
id] = clusterCount;
@ VectorPoint
Vector point layers.
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
bool hasGeometry() const
Returns true if the feature has an associated geometry.
bool isCanceled() const
Tells whether the operation has been canceled already.
void setProgress(double progress)
Sets the current progress for the feedback object.
Encapsulate a field in an attribute table or data source.
Container of fields for a vector layer.
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.).
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
A numeric output for processing algorithms.
A double numeric parameter for distance values.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
A numeric parameter for processing algorithms.
static QgsFields combineFields(const QgsFields &fieldsA, const QgsFields &fieldsB, const QString &fieldsBPrefix=QString())
Combines two field lists, avoiding duplicate field names (in a case-insensitive manner).
A container for data stored inside a QgsSpatialIndexKDBush index.
QgsFeatureId id
Feature ID.
QgsPointXY point() const
Returns the indexed point.
A very fast static spatial index for 2D points based on a flat KD-tree.
qgssize size() const
Returns the size of the index, i.e.
QList< QgsSpatialIndexKDBushData > within(const QgsPointXY &point, double radius) const
Returns the list of features which are within the given search radius of point.
static Q_INVOKABLE QString displayString(Qgis::WkbType type)
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QList< int > QgsAttributeList