20#include <unordered_set> 
   24QString QgsDbscanClusteringAlgorithm::name()
 const 
   26  return QStringLiteral( 
"dbscanclustering" );
 
   29QString QgsDbscanClusteringAlgorithm::displayName()
 const 
   31  return QObject::tr( 
"DBSCAN clustering" );
 
   34QString QgsDbscanClusteringAlgorithm::shortDescription()
 const 
   36  return QObject::tr( 
"Clusters point features using a density based scan algorithm." );
 
   39QStringList QgsDbscanClusteringAlgorithm::tags()
 const 
   41  return QObject::tr( 
"clustering,clusters,density,based,points,distance" ).split( 
',' );
 
   44QString QgsDbscanClusteringAlgorithm::group()
 const 
   46  return QObject::tr( 
"Vector analysis" );
 
   49QString QgsDbscanClusteringAlgorithm::groupId()
 const 
   51  return QStringLiteral( 
"vectoranalysis" );
 
   54void QgsDbscanClusteringAlgorithm::initAlgorithm( 
const QVariantMap & )
 
   58  addParameter( 
new QgsProcessingParameterDistance( QStringLiteral( 
"EPS" ), QObject::tr( 
"Maximum distance between clustered points" ), 1, QStringLiteral( 
"INPUT" ), 
false, 0 ) );
 
   60  auto dbscanStarParam = std::make_unique<QgsProcessingParameterBoolean>( QStringLiteral( 
"DBSCAN*" ), QObject::tr( 
"Treat border points as noise (DBSCAN*)" ), 
false );
 
   62  addParameter( dbscanStarParam.release() );
 
   64  auto fieldNameParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( 
"FIELD_NAME" ), QObject::tr( 
"Cluster field name" ), QStringLiteral( 
"CLUSTER_ID" ) );
 
   66  addParameter( fieldNameParam.release() );
 
   67  auto sizeFieldNameParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( 
"SIZE_FIELD_NAME" ), QObject::tr( 
"Cluster size field name" ), QStringLiteral( 
"CLUSTER_SIZE" ) );
 
   69  addParameter( sizeFieldNameParam.release() );
 
   76QString QgsDbscanClusteringAlgorithm::shortHelpString()
 const 
   78  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" 
   79                      "The algorithm requires two parameters, a minimum cluster size (“minPts”), and the maximum distance allowed between clustered points (“eps”)." );
 
   82QgsDbscanClusteringAlgorithm *QgsDbscanClusteringAlgorithm::createInstance()
 const 
   84  return new QgsDbscanClusteringAlgorithm();
 
   87struct KDBushDataEqualById
 
   95struct KDBushDataHashById
 
   99      return std::hash<QgsFeatureId> {}( a.
id );
 
  105  std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, QStringLiteral( 
"INPUT" ), context ) );
 
  109  const std::size_t minSize = 
static_cast<std::size_t
>( parameterAsInt( parameters, QStringLiteral( 
"MIN_SIZE" ), context ) );
 
  110  const double eps1 = parameterAsDouble( parameters, QStringLiteral( 
"EPS" ), context );
 
  111  const double eps2 = parameterAsDouble( parameters, QStringLiteral( 
"EPS2" ), context );
 
  112  const bool borderPointsAreNoise = parameterAsBoolean( parameters, QStringLiteral( 
"DBSCAN*" ), context );
 
  114  QgsFields outputFields = source->fields();
 
  116  const QString clusterFieldName = parameterAsString( parameters, QStringLiteral( 
"FIELD_NAME" ), context );
 
  117  newFields.
append( 
QgsField( clusterFieldName, QMetaType::Type::Int ) );
 
  118  const QString clusterSizeFieldName = parameterAsString( parameters, QStringLiteral( 
"SIZE_FIELD_NAME" ), context );
 
  119  newFields.
append( 
QgsField( clusterSizeFieldName, QMetaType::Type::Int ) );
 
  123  std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, QStringLiteral( 
"OUTPUT" ), context, dest, outputFields, source->wkbType(), source->sourceCrs() ) );
 
  129  std::unordered_map<QgsFeatureId, QDateTime> idToDateTime;
 
  130  const QString dateTimeFieldName = parameterAsString( parameters, QStringLiteral( 
"DATETIME_FIELD" ), context );
 
  131  int dateTimefieldIndex = -1;
 
  132  if ( !dateTimeFieldName.isEmpty() )
 
  134    dateTimefieldIndex = source->fields().lookupField( dateTimeFieldName );
 
  135    if ( dateTimefieldIndex == -1 )
 
  146  feedback->
pushInfo( QObject::tr( 
"Building spatial index" ) );
 
  149    if ( dateTimefieldIndex >= 0 )
 
  150      idToDateTime[ feature.
id() ] = feature.
attributes().at( dateTimefieldIndex ).toDateTime();
 
  151    return true; }, feedback );
 
  154    return QVariantMap();
 
  157  feedback->
pushInfo( QObject::tr( 
"Analysing clusters" ) );
 
  158  std::unordered_map<QgsFeatureId, int> idToCluster;
 
  159  idToCluster.reserve( index.size() );
 
  160  const long featureCount = source->featureCount();
 
  162  stdbscan( minSize, eps1, eps2, borderPointsAreNoise, featureCount, features, index, idToCluster, idToDateTime, feedback );
 
  165  std::unordered_map<int, int> clusterSize;
 
  166  std::for_each( idToCluster.begin(), idToCluster.end(), [&clusterSize]( std::pair<QgsFeatureId, int> idCluster ) { clusterSize[idCluster.second]++; } );
 
  169  const double writeStep = featureCount > 0 ? 10.0 / featureCount : 1;
 
  170  features = source->getFeatures();
 
  183    const auto cluster = idToCluster.find( feat.
id() );
 
  184    if ( cluster != idToCluster.end() )
 
  186      attr << cluster->second << clusterSize[cluster->second];
 
  190      attr << QVariant() << QVariant();
 
  200  outputs.insert( QStringLiteral( 
"OUTPUT" ), dest );
 
  201  outputs.insert( QStringLiteral( 
"NUM_CLUSTERS" ), 
static_cast<unsigned int>( clusterSize.size() ) );
 
  205void 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 )
 
  207  const double step = featureCount > 0 ? 90.0 / featureCount : 1;
 
  209  std::unordered_set<QgsFeatureId> visited;
 
  210  visited.reserve( index.
size() );
 
  214  int clusterCount = 0;
 
  229    if ( visited.find( feat.
id() ) != visited.end() )
 
  246    if ( !idToDateTime.empty() && !idToDateTime[feat.
id()].isValid() )
 
  254    std::unordered_set<QgsSpatialIndexKDBushData, KDBushDataHashById, KDBushDataEqualById> within;
 
  259        if ( idToDateTime.empty() || ( idToDateTime[data.id].isValid() && std::abs( idToDateTime[pointId].msecsTo( idToDateTime[data.id] ) ) <= eps2 ) )
 
  260          within.insert( data );
 
  262      if ( within.size() < minSize )
 
  265      visited.insert( feat.
id() );
 
  275    idToCluster[feat.
id()] = clusterCount;
 
  278    while ( !within.empty() )
 
  286      within.erase( within.begin() );
 
  288      if ( visited.find( j.
id ) != visited.end() )
 
  294      visited.insert( j.
id );
 
  300      std::unordered_set<QgsSpatialIndexKDBushData, KDBushDataHashById, KDBushDataEqualById> within2;
 
  302        if ( idToDateTime.empty() || ( idToDateTime[data.id].isValid() && std::abs( idToDateTime[point2Id].msecsTo( idToDateTime[data.id] ) ) <= eps2 ) )
 
  303          within2.insert( data );
 
  306      if ( within2.size() >= minSize )
 
  309        std::copy_if( within2.begin(), within2.end(), std::inserter( within, within.end() ), [&visited]( 
const QgsSpatialIndexKDBushData &needle ) {
 
  310          return visited.find( needle.id ) == visited.end();
 
  313      if ( !borderPointsAreNoise || within2.size() >= minSize )
 
  315        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.
 
QList< int > QgsAttributeList