20#include <unordered_set>
26using namespace Qt::StringLiterals;
30QString QgsDbscanClusteringAlgorithm::name()
const
32 return u
"dbscanclustering"_s;
35QString QgsDbscanClusteringAlgorithm::displayName()
const
37 return QObject::tr(
"DBSCAN clustering" );
40QString QgsDbscanClusteringAlgorithm::shortDescription()
const
42 return QObject::tr(
"Clusters point features using a density based scan algorithm." );
45QStringList QgsDbscanClusteringAlgorithm::tags()
const
47 return QObject::tr(
"clustering,clusters,density,based,points,distance" ).split(
',' );
50QString QgsDbscanClusteringAlgorithm::group()
const
52 return QObject::tr(
"Vector analysis" );
55QString QgsDbscanClusteringAlgorithm::groupId()
const
57 return u
"vectoranalysis"_s;
60void QgsDbscanClusteringAlgorithm::initAlgorithm(
const QVariantMap & )
64 addParameter(
new QgsProcessingParameterDistance( u
"EPS"_s, QObject::tr(
"Maximum distance between clustered points" ), 1, u
"INPUT"_s,
false, 0 ) );
66 auto dbscanStarParam = std::make_unique<QgsProcessingParameterBoolean>( u
"DBSCAN*"_s, QObject::tr(
"Treat border points as noise (DBSCAN*)" ),
false );
68 addParameter( dbscanStarParam.release() );
70 auto fieldNameParam = std::make_unique<QgsProcessingParameterString>( u
"FIELD_NAME"_s, QObject::tr(
"Cluster field name" ), u
"CLUSTER_ID"_s );
72 addParameter( fieldNameParam.release() );
73 auto sizeFieldNameParam = std::make_unique<QgsProcessingParameterString>( u
"SIZE_FIELD_NAME"_s, QObject::tr(
"Cluster size field name" ), u
"CLUSTER_SIZE"_s );
75 addParameter( sizeFieldNameParam.release() );
82QString QgsDbscanClusteringAlgorithm::shortHelpString()
const
85 "This algorithm clusters point features based on a 2D implementation of Density-based spatial clustering of applications with noise (DBSCAN) algorithm.\n\n"
86 "The algorithm requires two parameters, a minimum cluster size (“minPts”), and the maximum distance allowed between clustered points (“eps”)."
90QgsDbscanClusteringAlgorithm *QgsDbscanClusteringAlgorithm::createInstance()
const
92 return new QgsDbscanClusteringAlgorithm();
95struct KDBushDataEqualById
97 bool operator()(
const QgsSpatialIndexKDBushData &a,
const QgsSpatialIndexKDBushData &b )
const {
return a.
id == b.
id; }
100struct KDBushDataHashById
102 std::size_t operator()(
const QgsSpatialIndexKDBushData &a )
const {
return std::hash<QgsFeatureId> {}( a.
id ); }
107 QGS_MARK_ALGORITHM_SOURCE
109 std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, u
"INPUT"_s, context ) );
113 const std::size_t minSize =
static_cast<std::size_t
>( parameterAsInt( parameters, u
"MIN_SIZE"_s, context ) );
114 const double eps1 = parameterAsDouble( parameters, u
"EPS"_s, context );
115 const double eps2 = parameterAsDouble( parameters, u
"EPS2"_s, context );
116 const bool borderPointsAreNoise = parameterAsBoolean( parameters, u
"DBSCAN*"_s, context );
118 QgsFields outputFields = source->fields();
120 const QString clusterFieldName = parameterAsString( parameters, u
"FIELD_NAME"_s, context );
121 newFields.
append(
QgsField( clusterFieldName, QMetaType::Type::Int ) );
122 const QString clusterSizeFieldName = parameterAsString( parameters, u
"SIZE_FIELD_NAME"_s, context );
123 newFields.
append(
QgsField( clusterSizeFieldName, QMetaType::Type::Int ) );
127 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u
"OUTPUT"_s, context, dest, outputFields, source->wkbType(), source->sourceCrs() ) );
133 std::unordered_map<QgsFeatureId, QDateTime> idToDateTime;
134 const QString dateTimeFieldName = parameterAsString( parameters, u
"DATETIME_FIELD"_s, context );
135 int dateTimefieldIndex = -1;
136 if ( !dateTimeFieldName.isEmpty() )
138 dateTimefieldIndex = source->fields().lookupField( dateTimeFieldName );
139 if ( dateTimefieldIndex == -1 )
150 feedback->
pushInfo( QObject::tr(
"Building spatial index" ) );
154 [&idToDateTime, dateTimefieldIndex](
const QgsFeature &feature ) ->
bool {
155 if ( dateTimefieldIndex >= 0 )
156 idToDateTime[feature.
id()] = feature.
attributes().at( dateTimefieldIndex ).toDateTime();
163 return QVariantMap();
166 feedback->
pushInfo( QObject::tr(
"Analysing clusters" ) );
167 std::unordered_map<QgsFeatureId, int> idToCluster;
168 idToCluster.reserve( index.size() );
169 const long featureCount = source->featureCount();
171 stdbscan( minSize, eps1, eps2, borderPointsAreNoise, featureCount, features, index, idToCluster, idToDateTime, feedback );
174 std::unordered_map<int, int> clusterSize;
175 std::for_each( idToCluster.begin(), idToCluster.end(), [&clusterSize]( std::pair<QgsFeatureId, int> idCluster ) { clusterSize[idCluster.second]++; } );
178 const double writeStep = featureCount > 0 ? 10.0 / featureCount : 1;
179 features = source->getFeatures();
192 const auto cluster = idToCluster.find( feat.
id() );
193 if ( cluster != idToCluster.end() )
195 attr << cluster->second << clusterSize[cluster->second];
199 attr << QVariant() << QVariant();
212 outputs.insert( u
"OUTPUT"_s, dest );
213 outputs.insert( u
"NUM_CLUSTERS"_s,
static_cast<unsigned int>( clusterSize.size() ) );
217void QgsDbscanClusteringAlgorithm::stdbscan(
218 const std::size_t minSize,
221 const bool borderPointsAreNoise,
222 const long featureCount,
225 std::unordered_map<QgsFeatureId, int> &idToCluster,
226 std::unordered_map<QgsFeatureId, QDateTime> &idToDateTime,
230 const double step = featureCount > 0 ? 90.0 / featureCount : 1;
232 std::unordered_set<QgsFeatureId> visited;
233 visited.reserve( index.
size() );
237 int clusterCount = 0;
252 if ( visited.find( feat.
id() ) != visited.end() )
269 if ( !idToDateTime.empty() && !idToDateTime[feat.
id()].isValid() )
277 std::unordered_set<QgsSpatialIndexKDBushData, KDBushDataHashById, KDBushDataEqualById> within;
282 if ( idToDateTime.empty() || ( idToDateTime[data.id].isValid() && std::abs( idToDateTime[pointId].msecsTo( idToDateTime[data.id] ) ) <= eps2 ) )
283 within.insert( data );
285 if ( within.size() < minSize )
288 visited.insert( feat.
id() );
298 idToCluster[feat.
id()] = clusterCount;
301 while ( !within.empty() )
309 within.erase( within.begin() );
311 if ( visited.find( j.
id ) != visited.end() )
317 visited.insert( j.
id );
323 std::unordered_set<QgsSpatialIndexKDBushData, KDBushDataHashById, KDBushDataEqualById> within2;
325 if ( idToDateTime.empty() || ( idToDateTime[data.id].isValid() && std::abs( idToDateTime[point2Id].msecsTo( idToDateTime[data.id] ) ) <= eps2 ) )
326 within2.insert( data );
329 if ( within2.size() >= minSize )
332 std::copy_if( within2.begin(), within2.end(), std::inserter( within, within.end() ), [&visited](
const QgsSpatialIndexKDBushData &needle ) {
333 return visited.find( needle.id ) == visited.end();
336 if ( !borderPointsAreNoise || within2.size() >= minSize )
338 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.
void featureAddedToSink(const QString &output)
Reports that a feature was added to the the sink associated with the specified algorithm output.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
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