Example #1
0
bool operator==( const QgsGeometry &g1, const QgsGeometry &g2 )
{
  if ( g1.isNull() && g2.isNull() )
    return true;
  else
    return g1.isGeosEqual( g2 );
}
QgsFeatureList QgsConvexHullAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback *feedback )
{
  QgsFeature f = feature;
  if ( f.hasGeometry() )
  {
    QgsGeometry outputGeometry;
    if ( QgsWkbTypes::flatType( f.geometry().wkbType() ) == QgsWkbTypes::Point )
    {
      feedback->reportError( QObject::tr( "Cannot calculate convex hull for a single Point feature (try 'Minimum bounding geometry' algorithm instead)." ) );
      f.clearGeometry();
    }
    else
    {
      outputGeometry = f.geometry().convexHull();
      if ( outputGeometry.isNull() )
        feedback->reportError( outputGeometry.lastError() );
      f.setGeometry( outputGeometry );
    }
    if ( !outputGeometry.isNull() )
    {
      QgsAttributes attrs = f.attributes();
      attrs << outputGeometry.constGet()->area()
            << outputGeometry.constGet()->perimeter();
      f.setAttributes( attrs );
    }
    else
    {
      QgsAttributes attrs = f.attributes();
      attrs << QVariant()
            << QVariant();
      f.setAttributes( attrs );
    }
  }
  return QgsFeatureList() << f;
}
Example #3
0
void QgsZonalStatistics::statisticsFromPreciseIntersection( const QgsGeometry &poly, int pixelOffsetX,
    int pixelOffsetY, int nCellsX, int nCellsY, double cellSizeX, double cellSizeY, const QgsRectangle &rasterBBox, FeatureStats &stats )
{
  stats.reset();

  double currentY = rasterBBox.yMaximum() - pixelOffsetY * cellSizeY - cellSizeY / 2;
  QgsGeometry pixelRectGeometry;

  double hCellSizeX = cellSizeX / 2.0;
  double hCellSizeY = cellSizeY / 2.0;
  double pixelArea = cellSizeX * cellSizeY;
  double weight = 0;

  QgsRectangle featureBBox = poly.boundingBox().intersect( &rasterBBox );
  QgsRectangle intersectBBox = rasterBBox.intersect( &featureBBox );

  QgsRasterBlock *block = mRasterProvider->block( mRasterBand, intersectBBox, nCellsX, nCellsY );
  for ( int i = 0; i < nCellsY; ++i )
  {
    double currentX = rasterBBox.xMinimum() + cellSizeX / 2.0 + pixelOffsetX * cellSizeX;
    for ( int j = 0; j < nCellsX; ++j )
    {
      if ( !validPixel( block->value( i, j ) ) )
      {
        continue;
      }

      pixelRectGeometry = QgsGeometry::fromRect( QgsRectangle( currentX - hCellSizeX, currentY - hCellSizeY, currentX + hCellSizeX, currentY + hCellSizeY ) );
      if ( !pixelRectGeometry.isNull() )
      {
        //intersection
        QgsGeometry intersectGeometry = pixelRectGeometry.intersection( poly );
        if ( !intersectGeometry.isNull() )
        {
          double intersectionArea = intersectGeometry.area();
          if ( intersectionArea >= 0.0 )
          {
            weight = intersectionArea / pixelArea;
            stats.addValue( block->value( i, j ), weight );
          }
        }
        pixelRectGeometry = QgsGeometry();
      }
      currentX += cellSizeX;
    }
    currentY -= cellSizeY;
  }
  delete block;
}
Example #4
0
bool QgsOverlayUtils::sanitizeIntersectionResult( QgsGeometry &geom, QgsWkbTypes::GeometryType geometryType )
{
  if ( geom.isNull() )
  {
    // TODO: not sure if this ever happens - if it does, that means GEOS failed badly - would be good to have a test for such situation
    throw QgsProcessingException( QStringLiteral( "%1\n\n%2" ).arg( QObject::tr( "GEOS geoprocessing error: intersection failed." ), geom.lastError() ) );
  }

  // Intersection of geometries may give use also geometries we do not want in our results.
  // For example, two square polygons touching at the corner have a point as the intersection, but no area.
  // In other cases we may get a mixture of geometries in the output - we want to keep only the expected types.
  if ( QgsWkbTypes::flatType( geom.wkbType() ) == QgsWkbTypes::GeometryCollection )
  {
    // try to filter out irrelevant parts with different geometry type than what we want
    geom.convertGeometryCollectionToSubclass( geometryType );
    if ( geom.isEmpty() )
      return false;
  }

  if ( QgsWkbTypes::geometryType( geom.wkbType() ) != geometryType )
  {
    // we can't make use of this resulting geometry
    return false;
  }

  // some data providers are picky about the geometries we pass to them: we can't add single-part geometries
  // when we promised multi-part geometries, so ensure we have the right type
  geom.convertToMultiType();

  return true;
}
Example #5
0
bool QgsMapToolReshape::isBindingLine( QgsVectorLayer *vlayer, const QgsRectangle &bbox ) const
{
  if ( vlayer->geometryType() != QgsWkbTypes::LineGeometry )
    return false;

  bool begin = false;
  bool end = false;
  const QgsPointXY beginPoint = points().first();
  const QgsPointXY endPoint = points().last();

  QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
  QgsFeature f;

  // check that extremities of the new line are contained by features
  while ( fit.nextFeature( f ) )
  {
    const QgsGeometry geom = f.geometry();
    if ( !geom.isNull() )
    {
      const QgsPolylineXY line = geom.asPolyline();

      if ( line.contains( beginPoint ) )
        begin = true;
      else if ( line.contains( endPoint ) )
        end = true;
    }
  }

  return end && begin;
}
Example #6
0
ErrorList topolTest::checkMultipart( QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent )
{
  Q_UNUSED( layer2 );
  Q_UNUSED( layer1 );
  Q_UNUSED( isExtent );

  int i = 0;
  ErrorList errorList;
  QList<FeatureLayer>::Iterator it;
  for ( it = mFeatureList1.begin(); it != mFeatureList1.end(); ++it )
  {
    if ( !( ++i % 100 ) )
      emit progress( ++i );
    if ( testCanceled() )
      break;
    QgsGeometry g = it->feature.geometry();
    if ( g.isNull() )
    {
      QgsMessageLog::logMessage( tr( "Missing geometry in multipart check." ), tr( "Topology plugin" ) );
      continue;
    }
    if ( !_canExportToGeos( g ) )
      continue;
    if ( g.isMultipart() )
    {
      QgsRectangle r = g.boundingBox();
      QList<FeatureLayer> fls;
      fls << *it << *it;
      TopolErroMultiPart *err = new TopolErroMultiPart( r, g, fls );
      errorList << err;
    }
  }
  return errorList;
}
Example #7
0
QgsGeometry QgsGeometryAnalyzer::locateBetweenMeasures( double fromMeasure, double toMeasure, const QgsGeometry &lineGeom )
{
  if ( lineGeom.isNull() )
  {
    return QgsGeometry();
  }

  QgsMultiPolyline resultGeom;

  //need to go with WKB and z coordinate until QgsGeometry supports M values
  QByteArray wkb( lineGeom.exportToWkb() );
  QgsConstWkbPtr wkbPtr( wkb );
  wkbPtr.readHeader();

  QgsWkbTypes::Type wkbType = lineGeom.wkbType();
  if ( wkbType != QgsWkbTypes::LineString25D && wkbType != QgsWkbTypes::MultiLineString25D )
  {
    return QgsGeometry();
  }

  if ( wkbType == QgsWkbTypes::LineString25D )
  {
    locateBetweenWkbString( wkbPtr, resultGeom, fromMeasure, toMeasure );
  }
  else if ( wkbType == QgsWkbTypes::MultiLineString25D )
  {
    int nLines;
    wkbPtr >> nLines;
    for ( int i = 0; i < nLines; ++i )
    {
      wkbPtr.readHeader();
      wkbPtr = locateBetweenWkbString( wkbPtr, resultGeom, fromMeasure, toMeasure );
    }
  }
QgsFeatureList QgsSnapToGridAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
  QgsFeature f = feature;
  if ( f.hasGeometry() )
  {
    double intervalX = mIntervalX;
    if ( mDynamicIntervalX )
      intervalX = mIntervalXProperty.valueAsDouble( context.expressionContext(), intervalX );

    double intervalY = mIntervalY;
    if ( mDynamicIntervalY )
      intervalY = mIntervalYProperty.valueAsDouble( context.expressionContext(), intervalY );

    double intervalZ = mIntervalZ;
    if ( mDynamicIntervalZ )
      intervalZ = mIntervalZProperty.valueAsDouble( context.expressionContext(), intervalZ );

    double intervalM = mIntervalM;
    if ( mDynamicIntervalM )
      intervalM = mIntervalMProperty.valueAsDouble( context.expressionContext(), intervalM );

    QgsGeometry outputGeometry = f.geometry().snappedToGrid( intervalX, intervalY, intervalZ, intervalM );
    if ( outputGeometry.isNull() )
    {
      feedback->reportError( QObject::tr( "Error snapping geometry %1" ).arg( feature.id() ) );
    }
    f.setGeometry( outputGeometry );
  }
  return QgsFeatureList() << f;
}
Example #9
0
double QgsDistanceArea::measureLength( const QgsGeometry& geometry ) const
{
  if ( geometry.isNull() )
    return 0.0;

  const QgsAbstractGeometry* geomV2 = geometry.geometry();
  return measure( geomV2, Length );
}
Example #10
0
void QgsGeometryAnalyzer::addEventLayerFeature( QgsFeature &feature, const QgsGeometry &geom, const QgsGeometry &lineGeom, QgsVectorFileWriter *fileWriter, QgsFeatureList &memoryFeatures,
    int offsetField, double offsetScale, bool forceSingleType )
{
  if ( geom.isNull() )
  {
    return;
  }

  QList<QgsGeometry> geomList;
  if ( forceSingleType )
  {
    geomList = geom.asGeometryCollection();
  }
  else
  {
    geomList.push_back( geom );
  }

  QList<QgsGeometry>::iterator geomIt = geomList.begin();
  for ( ; geomIt != geomList.end(); ++geomIt )
  {
    //consider offset
    QgsGeometry newGeom = *geomIt;
    if ( offsetField >= 0 )
    {
      double offsetVal = feature.attribute( offsetField ).toDouble();
      offsetVal *= offsetScale;
      newGeom = createOffsetGeometry( *geomIt, lineGeom, offsetVal );
      if ( newGeom.isNull() )
      {
        continue;
      }
    }

    feature.setGeometry( newGeom );
    if ( fileWriter )
    {
      fileWriter->addFeature( feature );
    }
    else
    {
      memoryFeatures << feature;
    }
  }
}
Example #11
0
void QgsMapToolLabel::createRubberBands()
{
  delete mLabelRubberBand;
  delete mFeatureRubberBand;

  //label rubber band
  QgsRectangle rect = mCurrentLabel.pos.labelRect;
  mLabelRubberBand = new QgsRubberBand( mCanvas, QgsWkbTypes::LineGeometry );
  mLabelRubberBand->addPoint( QgsPointXY( rect.xMinimum(), rect.yMinimum() ) );
  mLabelRubberBand->addPoint( QgsPointXY( rect.xMinimum(), rect.yMaximum() ) );
  mLabelRubberBand->addPoint( QgsPointXY( rect.xMaximum(), rect.yMaximum() ) );
  mLabelRubberBand->addPoint( QgsPointXY( rect.xMaximum(), rect.yMinimum() ) );
  mLabelRubberBand->addPoint( QgsPointXY( rect.xMinimum(), rect.yMinimum() ) );
  mLabelRubberBand->setColor( QColor( 0, 255, 0, 65 ) );
  mLabelRubberBand->setWidth( 3 );
  mLabelRubberBand->show();

  //feature rubber band
  QgsVectorLayer *vlayer = mCurrentLabel.layer;
  if ( vlayer )
  {
    QgsFeature f;
    if ( currentFeature( f, true ) )
    {
      QgsGeometry geom = f.geometry();
      if ( !geom.isNull() )
      {
        QgsSettings settings;
        int r = settings.value( QStringLiteral( "qgis/digitizing/line_color_red" ), 255 ).toInt();
        int g = settings.value( QStringLiteral( "qgis/digitizing/line_color_green" ), 0 ).toInt();
        int b = settings.value( QStringLiteral( "qgis/digitizing/line_color_blue" ), 0 ).toInt();
        int a = settings.value( QStringLiteral( "qgis/digitizing/line_color_alpha" ), 200 ).toInt();
        mFeatureRubberBand = new QgsRubberBand( mCanvas, geom.type() );
        mFeatureRubberBand->setColor( QColor( r, g, b, a ) );
        mFeatureRubberBand->setToGeometry( geom, vlayer );
        mFeatureRubberBand->show();
      }
    }

    //fixpoint rubber band
    QgsPointXY fixPoint;
    if ( currentLabelRotationPoint( fixPoint, false, false ) )
    {
      if ( mCanvas )
      {
        const QgsMapSettings &s = mCanvas->mapSettings();
        fixPoint = s.mapToLayerCoordinates( vlayer, fixPoint );
      }

      QgsGeometry pointGeom = QgsGeometry::fromPointXY( fixPoint );
      mFixPointRubberBand = new QgsRubberBand( mCanvas, QgsWkbTypes::LineGeometry );
      mFixPointRubberBand->setColor( QColor( 0, 0, 255, 65 ) );
      mFixPointRubberBand->setToGeometry( pointGeom, vlayer );
      mFixPointRubberBand->show();
    }
  }
}
Example #12
0
double QgsDistanceArea::measurePerimeter( const QgsGeometry& geometry ) const
{
  if ( geometry.isNull() )
    return 0.0;

  const QgsAbstractGeometry* geomV2 = geometry.geometry();
  if ( !geomV2 || geomV2->dimension() < 2 )
  {
    return 0.0;
  }

  if ( !mEllipsoidalMode || mEllipsoid == GEO_NONE )
  {
    return geomV2->perimeter();
  }

  //create list with (single) surfaces
  QList< const QgsSurface* > surfaces;
  const QgsSurface* surf = dynamic_cast<const QgsSurface*>( geomV2 );
  if ( surf )
  {
    surfaces.append( surf );
  }
  const QgsMultiSurface* multiSurf = dynamic_cast<const QgsMultiSurface*>( geomV2 );
  if ( multiSurf )
  {
    surfaces.reserve(( surf ? 1 : 0 ) + multiSurf->numGeometries() );
    for ( int i = 0; i  < multiSurf->numGeometries(); ++i )
    {
      surfaces.append( static_cast<const QgsSurface*>( multiSurf->geometryN( i ) ) );
    }
  }

  double length = 0;
  QList<const QgsSurface*>::const_iterator surfaceIt = surfaces.constBegin();
  for ( ; surfaceIt != surfaces.constEnd(); ++surfaceIt )
  {
    if ( !*surfaceIt )
    {
      continue;
    }

    QgsPolygonV2* poly = ( *surfaceIt )->surfaceToPolygon();
    const QgsCurve* outerRing = poly->exteriorRing();
    if ( outerRing )
    {
      length += measure( outerRing );
    }
    int nInnerRings = poly->numInteriorRings();
    for ( int i = 0; i < nInnerRings; ++i )
    {
      length += measure( poly->interiorRing( i ) );
    }
    delete poly;
  }
  return length;
}
Example #13
0
void QgsMapToolCapture::validateGeometry()
{
  QgsSettings settings;
  if ( settings.value( QStringLiteral( "qgis/digitizing/validate_geometries" ), 1 ).toInt() == 0 )
    return;

  if ( mValidator )
  {
    mValidator->deleteLater();
    mValidator = nullptr;
  }

  mGeomErrors.clear();
  while ( !mGeomErrorMarkers.isEmpty() )
  {
    delete mGeomErrorMarkers.takeFirst();
  }

  QgsGeometry geom;

  switch ( mCaptureMode )
  {
    case CaptureNone:
    case CapturePoint:
      return;
    case CaptureLine:
      if ( size() < 2 )
        return;
      geom = QgsGeometry( mCaptureCurve.curveToLine() );
      break;
    case CapturePolygon:
      if ( size() < 3 )
        return;
      QgsLineString *exteriorRing = mCaptureCurve.curveToLine();
      exteriorRing->close();
      QgsPolygon *polygon = new QgsPolygon();
      polygon->setExteriorRing( exteriorRing );
      geom = QgsGeometry( polygon );
      break;
  }

  if ( geom.isNull() )
    return;

  QgsGeometry::ValidationMethod method = QgsGeometry::ValidatorQgisInternal;
  if ( settings.value( QStringLiteral( "qgis/digitizing/validate_geometries" ), 1 ).toInt() == 2 )
    method = QgsGeometry::ValidatorGeos;
  mValidator = new QgsGeometryValidator( geom, nullptr, method );
  connect( mValidator, &QgsGeometryValidator::errorFound, this, &QgsMapToolCapture::addError );
  mValidator->start();
  QgsDebugMsgLevel( QStringLiteral( "Validation started" ), 4 );
}
Example #14
0
bool QgsSpatialQuery::hasValidGeometry( QgsFeature &feature )
{
  if ( !feature.isValid() )
    return false;

  QgsGeometry geom = feature.geometry();

  if ( geom.isNull() || geom.isEmpty() )
    return false;

  return true;

} // bool QgsSpatialQuery::hasValidGeometry(QgsFeature &feature)
void QgsExpressionSelectionDialog::mButtonZoomToFeatures_clicked()
{
  if ( mExpressionBuilder->expressionText().isEmpty() || !mMapCanvas )
    return;

  QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );

  QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( mExpressionBuilder->expressionText() )
                              .setExpressionContext( context )
                              .setNoAttributes();

  QgsFeatureIterator features = mLayer->getFeatures( request );

  QgsRectangle bbox;
  bbox.setMinimal();
  QgsFeature feat;
  int featureCount = 0;
  while ( features.nextFeature( feat ) )
  {
    QgsGeometry geom = feat.geometry();
    if ( geom.isNull() || geom.constGet()->isEmpty() )
      continue;

    QgsRectangle r = mMapCanvas->mapSettings().layerExtentToOutputExtent( mLayer, geom.boundingBox() );
    bbox.combineExtentWith( r );
    featureCount++;
  }
  features.close();

  QgsSettings settings;
  int timeout = settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
  if ( featureCount > 0 )
  {
    mMapCanvas->zoomToFeatureExtent( bbox );
    if ( mMessageBar )
    {
      mMessageBar->pushMessage( QString(),
                                tr( "Zoomed to %n matching feature(s)", "number of matching features", featureCount ),
                                Qgis::Info,
                                timeout );
    }
  }
  else if ( mMessageBar )
  {
    mMessageBar->pushMessage( QString(),
                              tr( "No matching features found" ),
                              Qgis::Info,
                              timeout );
  }
  saveRecent();
}
Example #16
0
QgsGeometry QgsMapToolDeleteRing::ringUnderPoint( const QgsPoint &p, QgsFeatureId &fid, int &partNum, int &ringNum )
{
  //There is no clean way to find if we are inside the ring of a feature,
  //so we iterate over all the features visible in the canvas
  //If several rings are found at this position, the smallest one is chosen,
  //in order to be able to delete a ring inside another ring
  QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( toLayerCoordinates( vlayer, mCanvas->extent() ) ) );
  QgsFeature f;
  QgsGeometry g;
  QgsGeometry ringGeom;
  QgsMultiPolygon pol;
  QgsPolygon tempPol;
  QgsGeometry tempGeom;
  double area = std::numeric_limits<double>::max();
  while ( fit.nextFeature( f ) )
  {
    g = f.geometry();
    if ( g.isNull() )
      continue;
    if ( g.wkbType() == QgsWkbTypes::Polygon ||  g.wkbType()  == QgsWkbTypes::Polygon25D )
    {
      pol = QgsMultiPolygon() << g.asPolygon();
    }
    else
    {
      pol = g.asMultiPolygon();
    }

    for ( int i = 0; i < pol.size() ; ++i )
    {
      //for each part
      if ( pol[i].size() > 1 )
      {
        for ( int j = 1; j < pol[i].size(); ++j )
        {
          tempPol = QgsPolygon() << pol[i][j];
          tempGeom = QgsGeometry::fromPolygon( tempPol );
          if ( tempGeom.area() < area && tempGeom.contains( &p ) )
          {
            fid = f.id();
            partNum = i;
            ringNum = j;
            area = tempGeom.area();
            ringGeom = tempGeom;
          }
        }
      }
    }
  }
  return ringGeom;
}
Example #17
0
void QgsSelectByFormDialog::zoomToFeatures( const QString &filter )
{
  QgsFeatureIds ids;

  QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );

  QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( filter )
                              .setExpressionContext( context )
                              .setSubsetOfAttributes( QgsAttributeList() );

  QgsFeatureIterator features = mLayer->getFeatures( request );

  QgsRectangle bbox;
  bbox.setMinimal();
  QgsFeature feat;
  int featureCount = 0;
  while ( features.nextFeature( feat ) )
  {
    QgsGeometry geom = feat.geometry();
    if ( geom.isNull() || geom.geometry()->isEmpty() )
      continue;

    QgsRectangle r = mMapCanvas->mapSettings().layerExtentToOutputExtent( mLayer, geom.boundingBox() );
    bbox.combineExtentWith( r );
    featureCount++;
  }
  features.close();

  QgsSettings settings;
  int timeout = settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
  if ( featureCount > 0 )
  {
    mMapCanvas->zoomToFeatureExtent( bbox );
    if ( mMessageBar )
    {
      mMessageBar->pushMessage( QString(),
                                tr( "Zoomed to %n matching feature(s)", "number of matching features", featureCount ),
                                QgsMessageBar::INFO,
                                timeout );
    }
  }
  else if ( mMessageBar )
  {
    mMessageBar->pushMessage( QString(),
                              tr( "No matching features found" ),
                              QgsMessageBar::INFO,
                              timeout );
  }
}
Example #18
0
void QgsEllipseSymbolLayer::calculateOffsetAndRotation( QgsSymbolRenderContext &context,
    double scaledWidth,
    double scaledHeight,
    bool &hasDataDefinedRotation,
    QPointF &offset,
    double &angle ) const
{
  double offsetX = 0;
  double offsetY = 0;
  markerOffset( context, scaledWidth, scaledHeight, mSymbolWidthUnit, mSymbolHeightUnit, offsetX, offsetY, mSymbolWidthMapUnitScale, mSymbolHeightMapUnitScale );
  offset = QPointF( offsetX, offsetY );

//priority for rotation: 1. data defined symbol level, 2. symbol layer rotation (mAngle)
  bool ok = true;
  angle = mAngle + mLineAngle;
  bool usingDataDefinedRotation = false;
  if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyAngle ) )
  {
    context.setOriginalValueVariable( angle );
    angle = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyAngle, context.renderContext().expressionContext(), 0 ) + mLineAngle;
    usingDataDefinedRotation = ok;
  }

  hasDataDefinedRotation = context.renderHints() & QgsSymbol::DynamicRotation || usingDataDefinedRotation;
  if ( hasDataDefinedRotation )
  {
    // For non-point markers, "dataDefinedRotation" means following the
    // shape (shape-data defined). For them, "field-data defined" does
    // not work at all. TODO: if "field-data defined" ever gets implemented
    // we'll need a way to distinguish here between the two, possibly
    // using another flag in renderHints()
    const QgsFeature *f = context.feature();
    if ( f )
    {
      const QgsGeometry g = f->geometry();
      if ( !g.isNull() && g.type() == QgsWkbTypes::PointGeometry )
      {
        const QgsMapToPixel &m2p = context.renderContext().mapToPixel();
        angle += m2p.mapRotation();
      }
    }
  }

  if ( angle )
    offset = _rotatedOffset( offset, angle );
}
Example #19
0
ErrorList topolTest::checkValid( double tolerance, QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent )
{
  Q_UNUSED( tolerance );
  Q_UNUSED( layer1 );
  Q_UNUSED( layer2 );
  Q_UNUSED( isExtent );

  int i = 0;
  ErrorList errorList;
  QgsFeature f;

  QList<FeatureLayer>::Iterator it;

  for ( it = mFeatureList1.begin(); it != mFeatureList1.end(); ++it )
  {
    if ( !( ++i % 100 ) )
      emit progress( ++i );
    if ( testCanceled() )
      break;

    QgsGeometry g = it->feature.geometry();
    if ( g.isNull() )
    {
      QgsMessageLog::logMessage( tr( "Invalid geometry in validity test." ), tr( "Topology plugin" ) );
      continue;
    }

    GEOSGeometry *gGeos = g.exportToGeos();
    if ( !gGeos )
      continue;

    if ( !GEOSisValid_r( QgsGeometry::getGEOSHandler(), gGeos ) )
    {
      QgsRectangle r = g.boundingBox();
      QList<FeatureLayer> fls;
      fls << *it << *it;

      TopolErrorValid *err = new TopolErrorValid( r, g, fls );
      errorList << err;
    }
    GEOSGeom_destroy_r( QgsGeometry::getGEOSHandler(), gGeos );
  }

  return errorList;
}
Example #20
0
//! Makes sure that what came out from difference of two geometries is good to be used in the output
static bool sanitizeDifferenceResult( QgsGeometry &geom )
{
  if ( geom.isNull() )
  {
    // TODO: not sure if this ever happens - if it does, that means GEOS failed badly - would be good to have a test for such situation
    throw QgsProcessingException( QStringLiteral( "%1\n\n%2" ).arg( QObject::tr( "GEOS geoprocessing error: difference failed." ), geom.lastError() ) );
  }

  // if geomB covers the whole source geometry, we get an empty geometry collection
  if ( geom.isEmpty() )
    return false;

  // some data providers are picky about the geometries we pass to them: we can't add single-part geometries
  // when we promised multi-part geometries, so ensure we have the right type
  geom.convertToMultiType();

  return true;
}
Example #21
0
QgsGeometry QgsGeometryAnalyzer::dissolveFeature( const QgsFeature &f, const QgsGeometry &dissolveInto )
{
  if ( !f.hasGeometry() )
  {
    return dissolveInto;
  }

  QgsGeometry featureGeometry = f.geometry();

  if ( dissolveInto.isNull() )
  {
    return featureGeometry;
  }
  else
  {
    return dissolveInto.combine( featureGeometry );
  }
}
Example #22
0
bool TopolError::fixUnion( const FeatureLayer &fl1, const FeatureLayer &fl2 )
{
  bool ok;
  QgsFeature f1, f2;

  ok = fl1.layer->getFeatures( QgsFeatureRequest().setFilterFid( fl1.feature.id() ) ).nextFeature( f1 );
  ok = ok && fl2.layer->getFeatures( ( QgsFeatureRequest().setFilterFid( fl2.feature.id() ) ) ).nextFeature( f2 );

  if ( !ok )
    return false;

  QgsGeometry g = f1.geometry().combine( f2.geometry() );
  if ( g.isNull() )
    return false;

  if ( fl2.layer->deleteFeature( f2.id() ) )
    return fl1.layer->changeGeometry( f1.id(), g );

  return false;
}
Example #23
0
bool TopolError::fixMove( const FeatureLayer &fl1, const FeatureLayer &fl2 )
{
  bool ok;
  QgsFeature f1, f2;

  ok = fl1.layer->getFeatures( QgsFeatureRequest().setFilterFid( fl1.feature.id() ) ).nextFeature( f1 );
  ok = ok && fl2.layer->getFeatures( ( QgsFeatureRequest().setFilterFid( fl2.feature.id() ) ) ).nextFeature( f2 );

  if ( !ok )
    return false;


  // 0 means success
  QgsGeometry g = f1.geometry();
  QgsGeometry difference = g.makeDifference( f2.geometry() );
  if ( !difference.isNull() )
  {
    return fl1.layer->changeGeometry( f1.id(), difference );
  }

  return false;
}
Example #24
0
QgsVectorLayer::EditResult QgsVectorLayerEditUtils::deleteVertex( QgsFeatureId featureId, int vertex )
{
  if ( !mLayer->isSpatial() )
    return QgsVectorLayer::InvalidLayer;

  QgsFeature f;
  if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) || !f.hasGeometry() )
    return QgsVectorLayer::FetchFeatureFailed; // geometry not found

  QgsGeometry geometry = f.geometry();

  if ( !geometry.deleteVertex( vertex ) )
    return QgsVectorLayer::EditFailed;

  if ( geometry.constGet() && geometry.constGet()->nCoordinates() == 0 )
  {
    //last vertex deleted, set geometry to null
    geometry.set( nullptr );
  }

  mLayer->editBuffer()->changeGeometry( featureId, geometry );
  return !geometry.isNull() ? QgsVectorLayer::Success : QgsVectorLayer::EmptyGeometry;
}
//! Returns a simplified version the specified geometry (Removing duplicated points) when is applied the specified map2pixel context
QgsGeometry QgsMapToPixelSimplifier::simplify( const QgsGeometry& geometry ) const
{
  if ( geometry.isNull() )
  {
    return QgsGeometry();
  }
  if ( mSimplifyFlags == QgsMapToPixelSimplifier::NoFlags )
  {
    return geometry;
  }

  // Check whether the geometry can be simplified using the map2pixel context
  const QgsWkbTypes::Type singleType = QgsWkbTypes::singleType( geometry.wkbType() );
  const QgsWkbTypes::Type flatType = QgsWkbTypes::flatType( singleType );
  if ( flatType == QgsWkbTypes::Point )
  {
    return geometry;
  }

  const bool isaLinearRing = flatType == QgsWkbTypes::Polygon;
  const int numPoints = geometry.geometry()->nCoordinates();

  if ( numPoints <= ( isaLinearRing ? 6 : 3 ) )
  {
    // No simplify simple geometries
    return geometry;
  }

  const QgsRectangle envelope = geometry.boundingBox();
  if ( qMax( envelope.width(), envelope.height() ) / numPoints > mTolerance * 2.0 )
  {
    //points are in average too far apart to lead to any significant simplification
    return geometry;
  }

  return simplifyGeometry( mSimplifyFlags, mSimplifyAlgorithm, geometry.wkbType(), *geometry.geometry(), envelope, mTolerance, false );
}
Example #26
0
bool QgsGeometryAnalyzer::eventLayer( QgsVectorLayer *lineLayer, QgsVectorLayer *eventLayer, int lineField, int eventField, QgsFeatureIds &unlocatedFeatureIds, const QString &outputLayer,
                                      const QString &outputFormat, int locationField1, int locationField2, int offsetField, double offsetScale,
                                      bool forceSingleGeometry, QgsVectorDataProvider *memoryProvider, QProgressDialog *p )
{
  if ( !lineLayer || !eventLayer || !lineLayer->isValid() || !eventLayer->isValid() )
  {
    return false;
  }

  //create line field / id map for line layer
  QMultiHash< QString, QgsFeature > lineLayerIdMap; //1:n possible (e.g. several linear reference geometries for one feature in the event layer)
  QgsFeatureIterator fit = lineLayer->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() << lineField ) );
  QgsFeature fet;
  while ( fit.nextFeature( fet ) )
  {
    lineLayerIdMap.insert( fet.attribute( lineField ).toString(), fet );
  }

  //create output datasource or attributes in memory provider
  QgsVectorFileWriter *fileWriter = nullptr;
  QgsFeatureList memoryProviderFeatures;
  if ( !memoryProvider )
  {
    QgsWkbTypes::Type memoryProviderType = QgsWkbTypes::MultiLineString;
    if ( locationField2 == -1 )
    {
      memoryProviderType = forceSingleGeometry ? QgsWkbTypes::Point : QgsWkbTypes::MultiPoint;
    }
    else
    {
      memoryProviderType = forceSingleGeometry ? QgsWkbTypes::LineString : QgsWkbTypes::MultiLineString;
    }
    fileWriter = new QgsVectorFileWriter( outputLayer,
                                          eventLayer->dataProvider()->encoding(),
                                          eventLayer->fields(),
                                          memoryProviderType,
                                          lineLayer->crs(),
                                          outputFormat );
  }
  else
  {
    memoryProvider->addAttributes( eventLayer->fields().toList() );
  }

  //iterate over eventLayer and write new features to output file or layer
  fit = eventLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ) );
  QgsGeometry lrsGeom;
  double measure1, measure2 = 0.0;

  int nEventFeatures = eventLayer->featureCount();
  int featureCounter = 0;
  int nOutputFeatures = 0; //number of output features for the current event feature
  if ( p )
  {
    p->setWindowModality( Qt::WindowModal );
    p->setMinimum( 0 );
    p->setMaximum( nEventFeatures );
    p->show();
  }

  while ( fit.nextFeature( fet ) )
  {
    nOutputFeatures = 0;

    //update progress dialog
    if ( p )
    {
      if ( p->wasCanceled() )
      {
        break;
      }
      p->setValue( featureCounter );
      ++featureCounter;
    }

    measure1 = fet.attribute( locationField1 ).toDouble();
    if ( locationField2 != -1 )
    {
      measure2 = fet.attribute( locationField2 ).toDouble();
      if ( qgsDoubleNear( ( measure2 - measure1 ), 0.0 ) )
      {
        continue;
      }
    }

    QList<QgsFeature> featureIdList = lineLayerIdMap.values( fet.attribute( eventField ).toString() );
    QList<QgsFeature>::iterator featureIdIt = featureIdList.begin();
    for ( ; featureIdIt != featureIdList.end(); ++featureIdIt )
    {
      if ( locationField2 == -1 )
      {
        lrsGeom = locateAlongMeasure( measure1, featureIdIt->geometry() );
      }
      else
      {
        lrsGeom = locateBetweenMeasures( measure1, measure2, featureIdIt->geometry() );
      }

      if ( !lrsGeom.isNull() )
      {
        ++nOutputFeatures;
        addEventLayerFeature( fet, lrsGeom, featureIdIt->geometry(), fileWriter, memoryProviderFeatures, offsetField, offsetScale, forceSingleGeometry );
      }
    }
    if ( nOutputFeatures < 1 )
    {
      unlocatedFeatureIds.insert( fet.id() );
    }
  }

  if ( p )
  {
    p->setValue( nEventFeatures );
  }

  if ( memoryProvider )
  {
    memoryProvider->addFeatures( memoryProviderFeatures );
  }
  delete fileWriter;
  return true;
}
Example #27
0
bool QgsGeometryAnalyzer::buffer( QgsVectorLayer *layer, const QString &shapefileName, double bufferDistance,
                                  bool onlySelectedFeatures, bool dissolve, int bufferDistanceField, QProgressDialog *p )
{
  if ( !layer )
  {
    return false;
  }

  QgsVectorDataProvider *dp = layer->dataProvider();
  if ( !dp )
  {
    return false;
  }

  QgsWkbTypes::Type outputType = QgsWkbTypes::Polygon;
  if ( dissolve )
  {
    outputType = QgsWkbTypes::MultiPolygon;
  }
  QgsCoordinateReferenceSystem crs = layer->crs();

  QgsVectorFileWriter vWriter( shapefileName, dp->encoding(), layer->fields(), outputType, crs );
  QgsFeature currentFeature;
  QgsGeometry dissolveGeometry; //dissolve geometry (if dissolve enabled)

  //take only selection
  if ( onlySelectedFeatures )
  {
    //use QgsVectorLayer::featureAtId
    const QgsFeatureIds selection = layer->selectedFeatureIds();
    if ( p )
    {
      p->setMaximum( selection.size() );
    }

    int processedFeatures = 0;
    QgsFeatureIds::const_iterator it = selection.constBegin();
    for ( ; it != selection.constEnd(); ++it )
    {
      if ( p )
      {
        p->setValue( processedFeatures );
      }

      if ( p && p->wasCanceled() )
      {
        break;
      }
      if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( *it ) ).nextFeature( currentFeature ) )
      {
        continue;
      }
      bufferFeature( currentFeature, processedFeatures, &vWriter, dissolve, dissolveGeometry, bufferDistance, bufferDistanceField );
      ++processedFeatures;
    }

    if ( p )
    {
      p->setValue( selection.size() );
    }
  }
  //take all features
  else
  {
    QgsFeatureIterator fit = layer->getFeatures();

    int featureCount = layer->featureCount();
    if ( p )
    {
      p->setMaximum( featureCount );
    }
    int processedFeatures = 0;

    while ( fit.nextFeature( currentFeature ) )
    {
      if ( p )
      {
        p->setValue( processedFeatures );
      }
      if ( p && p->wasCanceled() )
      {
        break;
      }
      bufferFeature( currentFeature, processedFeatures, &vWriter, dissolve, dissolveGeometry, bufferDistance, bufferDistanceField );
      ++processedFeatures;
    }
    if ( p )
    {
      p->setValue( featureCount );
    }
  }

  if ( dissolve )
  {
    QgsFeature dissolveFeature;
    if ( dissolveGeometry.isNull() )
    {
      QgsDebugMsg( "no dissolved geometry - should not happen" );
      return false;
    }
    dissolveFeature.setGeometry( dissolveGeometry );
    vWriter.addFeature( dissolveFeature );
  }
  return true;
}
Example #28
0
bool QgsGeometryAnalyzer::convexHull( QgsVectorLayer *layer, const QString &shapefileName,
                                      bool onlySelectedFeatures, int uniqueIdField, QProgressDialog *p )
{
  if ( !layer )
  {
    return false;
  }
  QgsVectorDataProvider *dp = layer->dataProvider();
  if ( !dp )
  {
    return false;
  }
  bool useField = false;
  if ( uniqueIdField == -1 )
  {
    uniqueIdField = 0;
  }
  else
  {
    useField = true;
  }
  QgsFields fields;
  fields.append( QgsField( QStringLiteral( "UID" ), QVariant::String ) );
  fields.append( QgsField( QStringLiteral( "AREA" ), QVariant::Double ) );
  fields.append( QgsField( QStringLiteral( "PERIM" ), QVariant::Double ) );

  QgsWkbTypes::Type outputType = QgsWkbTypes::Polygon;
  QgsCoordinateReferenceSystem crs = layer->crs();

  QgsVectorFileWriter vWriter( shapefileName, dp->encoding(), fields, outputType, crs );
  QgsFeature currentFeature;
  QgsGeometry dissolveGeometry; //dissolve geometry
  QMultiMap<QString, QgsFeatureId> map;

  if ( onlySelectedFeatures )
  {
    //use QgsVectorLayer::featureAtId
    const QgsFeatureIds selection = layer->selectedFeatureIds();
    QgsFeatureIds::const_iterator it = selection.constBegin();
    for ( ; it != selection.constEnd(); ++it )
    {
#if 0
      if ( p )
      {
        p->setValue( processedFeatures );
      }
      if ( p && p->wasCanceled() )
      {
        // break; // it may be better to do something else here?
        return false;
      }
#endif
      if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( *it ) ).nextFeature( currentFeature ) )
      {
        continue;
      }
      map.insert( currentFeature.attribute( uniqueIdField ).toString(), currentFeature.id() );
    }
  }
  else
  {
    QgsFeatureIterator fit = layer->getFeatures();
    while ( fit.nextFeature( currentFeature ) )
    {
#if 0
      if ( p )
      {
        p->setValue( processedFeatures );
      }
      if ( p && p->wasCanceled() )
      {
        // break; // it may be better to do something else here?
        return false;
      }
#endif
      map.insert( currentFeature.attribute( uniqueIdField ).toString(), currentFeature.id() );
    }
  }

  QMultiMap<QString, QgsFeatureId>::const_iterator jt = map.constBegin();
  while ( jt != map.constEnd() )
  {
    QString currentKey = jt.key();
    int processedFeatures = 0;
    //take only selection
    if ( onlySelectedFeatures )
    {
      //use QgsVectorLayer::featureAtId
      const QgsFeatureIds selection = layer->selectedFeatureIds();
      if ( p )
      {
        p->setMaximum( selection.size() );
      }
      processedFeatures = 0;
      while ( jt != map.constEnd() && ( jt.key() == currentKey || !useField ) )
      {
        if ( p && p->wasCanceled() )
        {
          break;
        }
        if ( selection.contains( jt.value() ) )
        {
          if ( p )
          {
            p->setValue( processedFeatures );
          }
          if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( jt.value() ) ).nextFeature( currentFeature ) )
          {
            continue;
          }
          convexFeature( currentFeature, processedFeatures, dissolveGeometry );
          ++processedFeatures;
        }
        ++jt;
      }
      QList<double> values;
      if ( dissolveGeometry.isNull() )
      {
        QgsDebugMsg( "no dissolved geometry - should not happen" );
        return false;
      }
      dissolveGeometry = dissolveGeometry.convexHull();
      values = simpleMeasure( dissolveGeometry );
      QgsAttributes attributes( 3 );
      attributes[0] = QVariant( currentKey );
      attributes[1] = values.at( 0 );
      attributes[2] = values.at( 1 );
      QgsFeature dissolveFeature;
      dissolveFeature.setAttributes( attributes );
      dissolveFeature.setGeometry( dissolveGeometry );
      vWriter.addFeature( dissolveFeature );
    }
    //take all features
    else
    {
      int featureCount = layer->featureCount();
      if ( p )
      {
        p->setMaximum( featureCount );
      }
      processedFeatures = 0;
      while ( jt != map.constEnd() && ( jt.key() == currentKey || !useField ) )
      {
        if ( p )
        {
          p->setValue( processedFeatures );
        }

        if ( p && p->wasCanceled() )
        {
          break;
        }
        if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( jt.value() ) ).nextFeature( currentFeature ) )
        {
          continue;
        }
        convexFeature( currentFeature, processedFeatures, dissolveGeometry );
        ++processedFeatures;
        ++jt;
      }
      QList<double> values;
      if ( dissolveGeometry.isNull() )
      {
        QgsDebugMsg( "no dissolved geometry - should not happen" );
        return false;
      }
      dissolveGeometry = dissolveGeometry.convexHull();
      // values = simpleMeasure( tmpGeometry );
      values = simpleMeasure( dissolveGeometry );
      QgsAttributes attributes;
      attributes[0] = QVariant( currentKey );
      attributes[1] = QVariant( values[ 0 ] );
      attributes[2] = QVariant( values[ 1 ] );
      QgsFeature dissolveFeature;
      dissolveFeature.setAttributes( attributes );
      dissolveFeature.setGeometry( dissolveGeometry );
      vWriter.addFeature( dissolveFeature );
    }
  }
  return true;
}
Example #29
0
QgsGeometry QgsGeometryAnalyzer::createOffsetGeometry( const QgsGeometry &geom, const QgsGeometry &lineGeom, double offset )
{
  if ( !geom || lineGeom.isNull() )
  {
    return QgsGeometry();
  }

  QList<QgsGeometry> inputGeomList;

  if ( geom.isMultipart() )
  {
    inputGeomList = geom.asGeometryCollection();
  }
  else
  {
    inputGeomList.push_back( geom );
  }

  QList<GEOSGeometry *> outputGeomList;
  QList<QgsGeometry>::const_iterator inputGeomIt = inputGeomList.constBegin();
  GEOSContextHandle_t geosctxt = QgsGeometry::getGEOSHandler();
  for ( ; inputGeomIt != inputGeomList.constEnd(); ++inputGeomIt )
  {
    if ( geom.type() == QgsWkbTypes::LineGeometry )
    {
      GEOSGeometry *inputGeomItGeos = inputGeomIt->exportToGeos();
      GEOSGeometry *offsetGeom = GEOSOffsetCurve_r( geosctxt, inputGeomItGeos, -offset, 8 /*quadSegments*/, 0 /*joinStyle*/, 5.0 /*mitreLimit*/ );
      GEOSGeom_destroy_r( geosctxt, inputGeomItGeos );
      if ( !offsetGeom || !GEOSisValid_r( geosctxt, offsetGeom ) )
      {
        return QgsGeometry();
      }
      if ( !GEOSisValid_r( geosctxt, offsetGeom ) || GEOSGeomTypeId_r( geosctxt, offsetGeom ) != GEOS_LINESTRING || GEOSGeomGetNumPoints_r( geosctxt, offsetGeom ) < 1 )
      {
        GEOSGeom_destroy_r( geosctxt, offsetGeom );
        return QgsGeometry();
      }
      outputGeomList.push_back( offsetGeom );
    }
    else if ( geom.type() == QgsWkbTypes::PointGeometry )
    {
      QgsPointXY p = ( *inputGeomIt ).asPoint();
      p = createPointOffset( p.x(), p.y(), offset, lineGeom );
      GEOSCoordSequence *ptSeq = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
      GEOSCoordSeq_setX_r( geosctxt, ptSeq, 0, p.x() );
      GEOSCoordSeq_setY_r( geosctxt, ptSeq, 0, p.y() );
      GEOSGeometry *geosPt = GEOSGeom_createPoint_r( geosctxt, ptSeq );
      outputGeomList.push_back( geosPt );
    }
  }

  QgsGeometry outGeometry;
  if ( !geom.isMultipart() )
  {
    GEOSGeometry *outputGeom = outputGeomList.at( 0 );
    if ( outputGeom )
    {
      outGeometry.fromGeos( outputGeom );
    }
  }
  else
  {
    GEOSGeometry **geomArray = new GEOSGeometry*[outputGeomList.size()];
    for ( int i = 0; i < outputGeomList.size(); ++i )
    {
      geomArray[i] = outputGeomList.at( i );
    }
    GEOSGeometry *collection = nullptr;
    if ( geom.type() == QgsWkbTypes::PointGeometry )
    {
      collection = GEOSGeom_createCollection_r( geosctxt, GEOS_MULTIPOINT, geomArray, outputGeomList.size() );
    }
    else if ( geom.type() == QgsWkbTypes::LineGeometry )
    {
      collection = GEOSGeom_createCollection_r( geosctxt, GEOS_MULTILINESTRING, geomArray, outputGeomList.size() );
    }
    outGeometry.fromGeos( collection );
    delete[] geomArray;
  }
  return outGeometry;
}
void QgsMapToolRotateFeature::canvasReleaseEvent( QgsMapMouseEvent *e )
{
  if ( !mCanvas )
  {
    return;
  }

  QgsVectorLayer *vlayer = currentVectorLayer();
  if ( !vlayer )
  {
    deleteRotationWidget();
    deleteRubberband();
    notifyNotVectorLayer();
    return;
  }

  if ( e->button() == Qt::RightButton )
  {
    cancel();
    return;
  }

  // place anchor point on CTRL + click
  if ( e->modifiers() & Qt::ControlModifier )
  {
    if ( !mAnchorPoint )
    {
      return;
    }
    mAnchorPoint->setCenter( toMapCoordinates( e->pos() ) );
    mStartPointMapCoords = toMapCoordinates( e->pos() );
    mStPoint = e->pos();
    return;
  }

  deleteRotationWidget();

  // Initialize rotation if not yet active
  if ( !mRotationActive )
  {
    mRotation = 0;
    mRotationOffset = 0;

    deleteRubberband();

    mInitialPos = e->pos();

    if ( !vlayer->isEditable() )
    {
      notifyNotEditableLayer();
      return;
    }

    QgsPointXY layerCoords = toLayerCoordinates( vlayer, e->pos() );
    double searchRadius = QgsTolerance::vertexSearchRadius( mCanvas->currentLayer(), mCanvas->mapSettings() );
    QgsRectangle selectRect( layerCoords.x() - searchRadius, layerCoords.y() - searchRadius,
                             layerCoords.x() + searchRadius, layerCoords.y() + searchRadius );

    if ( vlayer->selectedFeatureCount() == 0 )
    {
      QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( selectRect ).setNoAttributes() );

      //find the closest feature
      QgsGeometry pointGeometry = QgsGeometry::fromPointXY( layerCoords );
      if ( pointGeometry.isNull() )
      {
        return;
      }

      double minDistance = std::numeric_limits<double>::max();

      QgsFeature cf;
      QgsFeature f;
      while ( fit.nextFeature( f ) )
      {
        if ( f.hasGeometry() )
        {
          double currentDistance = pointGeometry.distance( f.geometry() );
          if ( currentDistance < minDistance )
          {
            minDistance = currentDistance;
            cf = f;
          }
        }
      }

      if ( minDistance == std::numeric_limits<double>::max() )
      {
        emit messageEmitted( tr( "Could not find a nearby feature in the current layer." ) );
        return;
      }

      QgsRectangle bound = cf.geometry().boundingBox();
      mStartPointMapCoords = toMapCoordinates( vlayer, bound.center() );

      if ( !mAnchorPoint )
      {
        mAnchorPoint = qgis::make_unique<QgsVertexMarker>( mCanvas );
      }
      mAnchorPoint->setIconType( QgsVertexMarker::ICON_CROSS );
      mAnchorPoint->setCenter( mStartPointMapCoords );

      mStPoint = toCanvasCoordinates( mStartPointMapCoords );

      mRotatedFeatures.clear();
      mRotatedFeatures << cf.id(); //todo: take the closest feature, not the first one...

      mRubberBand = createRubberBand( vlayer->geometryType() );
      mRubberBand->setToGeometry( cf.geometry(), vlayer );
    }
    else
    {
      mRotatedFeatures = vlayer->selectedFeatureIds();

      mRubberBand = createRubberBand( vlayer->geometryType() );

      QgsFeature feat;
      QgsFeatureIterator it = vlayer->getSelectedFeatures();
      while ( it.nextFeature( feat ) )
      {
        mRubberBand->addGeometry( feat.geometry(), vlayer );
      }
    }

    mRubberBand->show();

    double XDistance = mInitialPos.x() - mAnchorPoint->x();
    double YDistance = mInitialPos.y() - mAnchorPoint->y();
    mRotationOffset = std::atan2( YDistance, XDistance ) * ( 180 / M_PI );

    createRotationWidget();
    if ( e->modifiers() & Qt::ShiftModifier )
    {
      if ( mRotationWidget )
      {
        mRotationWidget->setMagnet( 45 );
      }
    }

    mRotationActive = true;

    return;
  }

  applyRotation( mRotation );
}