QGIS/src/app/qgsmaptooloffsetcurve.cpp
Nyall Dawson aceddae65a QgsFeature cleanup, part 1
Deprecate some QgsFeature methods which take or return pointers,
update other classes as required
2016-07-31 21:23:07 +10:00

498 lines
14 KiB
C++

/***************************************************************************
qgsmaptooloffsetcurve.cpp
------------------------------------------------------------
begin : February 2012
copyright : (C) 2012 by Marco Hugentobler
email : marco dot hugentobler at sourcepole dot ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsdoublespinbox.h"
#include "qgsfeatureiterator.h"
#include "qgsmaptooloffsetcurve.h"
#include "qgsmapcanvas.h"
#include "qgsmaplayerregistry.h"
#include "qgsrubberband.h"
#include "qgssnappingutils.h"
#include "qgsvectorlayer.h"
#include "qgsvertexmarker.h"
#include <QGraphicsProxyWidget>
#include <QMouseEvent>
#include "qgisapp.h"
QgsMapToolOffsetCurve::QgsMapToolOffsetCurve( QgsMapCanvas* canvas )
: QgsMapToolEdit( canvas )
, mRubberBand( nullptr )
, mOriginalGeometry( nullptr )
, mModifiedFeature( -1 )
, mGeometryModified( false )
, mDistanceWidget( nullptr )
, mSnapVertexMarker( nullptr )
, mForceCopy( false )
, mMultiPartGeometry( false )
{
}
QgsMapToolOffsetCurve::~QgsMapToolOffsetCurve()
{
deleteRubberBandAndGeometry();
deleteDistanceWidget();
delete mSnapVertexMarker;
}
void QgsMapToolOffsetCurve::canvasReleaseEvent( QgsMapMouseEvent* e )
{
if ( !mCanvas )
{
return;
}
QgsVectorLayer* layer = currentVectorLayer();
if ( !layer )
{
deleteRubberBandAndGeometry();
notifyNotVectorLayer();
return;
}
if ( e->button() == Qt::RightButton )
{
deleteRubberBandAndGeometry();
deleteDistanceWidget();
return;
}
if ( mOriginalGeometry.isEmpty() )
{
deleteRubberBandAndGeometry();
mGeometryModified = false;
mForceCopy = false;
if ( e->button() == Qt::RightButton )
{
return;
}
QgsSnappingUtils* snapping = mCanvas->snappingUtils();
// store previous settings
int oldType;
double oldSearchRadius;
QgsTolerance::UnitType oldSearchRadiusUnit;
QgsSnappingUtils::SnapToMapMode oldMode = snapping->snapToMapMode();
snapping->defaultSettings( oldType, oldSearchRadius, oldSearchRadiusUnit );
// setup new settings (temporary)
QSettings settings;
snapping->setSnapToMapMode( QgsSnappingUtils::SnapAllLayers );
snapping->setDefaultSettings( QgsPointLocator::Edge,
settings.value( "/qgis/digitizing/search_radius_vertex_edit", 10 ).toDouble(),
( QgsTolerance::UnitType ) settings.value( "/qgis/digitizing/search_radius_vertex_edit_unit", QgsTolerance::Pixels ).toInt() );
QgsPointLocator::Match match = snapping->snapToMap( e->pos() );
// restore old settings
snapping->setSnapToMapMode( oldMode );
snapping->setDefaultSettings( oldType, oldSearchRadius, oldSearchRadiusUnit );
if ( match.hasEdge() && match.layer() )
{
mSourceLayerId = match.layer()->id();
QgsFeature fet;
if ( match.layer()->getFeatures( QgsFeatureRequest( match.featureId() ) ).nextFeature( fet ) )
{
mForceCopy = ( e->modifiers() & Qt::ControlModifier ); //no geometry modification if ctrl is pressed
mOriginalGeometry = createOriginGeometry( match.layer(), match, fet );
mRubberBand = createRubberBand();
if ( mRubberBand )
{
mRubberBand->setToGeometry( mOriginalGeometry, layer );
}
mModifiedFeature = fet.id();
createDistanceWidget();
}
}
if ( mOriginalGeometry.isEmpty() )
{
emit messageEmitted( tr( "Could not find a nearby feature in any vector layer." ) );
}
return;
}
applyOffset();
}
void QgsMapToolOffsetCurve::applyOffset()
{
QgsVectorLayer* layer = currentVectorLayer();
if ( !layer )
{
deleteRubberBandAndGeometry();
notifyNotVectorLayer();
return;
}
// no modification
if ( !mGeometryModified )
{
deleteRubberBandAndGeometry();
layer->destroyEditCommand();
deleteDistanceWidget();
return;
}
if ( mMultiPartGeometry )
{
mModifiedGeometry.convertToMultiType();
}
layer->beginEditCommand( tr( "Offset curve" ) );
bool editOk;
if ( mSourceLayerId == layer->id() && !mForceCopy )
{
editOk = layer->changeGeometry( mModifiedFeature, mModifiedGeometry );
}
else
{
QgsFeature f;
f.setGeometry( mModifiedGeometry );
//add empty values for all fields (allows inserting attribute values via the feature form in the same session)
QgsAttributes attrs( layer->fields().count() );
const QgsFields& fields = layer->fields();
for ( int idx = 0; idx < fields.count(); ++idx )
{
attrs[idx] = QVariant();
}
f.setAttributes( attrs );
editOk = layer->addFeature( f );
}
if ( editOk )
{
layer->endEditCommand();
}
else
{
layer->destroyEditCommand();
}
deleteRubberBandAndGeometry();
deleteDistanceWidget();
delete mSnapVertexMarker;
mSnapVertexMarker = nullptr;
mForceCopy = false;
layer->triggerRepaint();
}
void QgsMapToolOffsetCurve::placeOffsetCurveToValue()
{
setOffsetForRubberBand( mDistanceWidget->value() );
}
void QgsMapToolOffsetCurve::canvasMoveEvent( QgsMapMouseEvent* e )
{
delete mSnapVertexMarker;
mSnapVertexMarker = nullptr;
if ( mOriginalGeometry.isEmpty() || !mRubberBand )
{
return;
}
QgsVectorLayer* layer = currentVectorLayer();
if ( !layer )
{
return;
}
mGeometryModified = true;
//get offset from current position rectangular to feature
QgsPoint layerCoords = toLayerCoordinates( layer, e->pos() );
//snap cursor to background layers
QgsPointLocator::Match m = mCanvas->snappingUtils()->snapToMap( e->pos() );
if ( m.isValid() )
{
if (( m.layer() && m.layer()->id() != mSourceLayerId ) || m.featureId() != mModifiedFeature )
{
layerCoords = toLayerCoordinates( layer, m.point() );
mSnapVertexMarker = new QgsVertexMarker( mCanvas );
mSnapVertexMarker->setIconType( QgsVertexMarker::ICON_CROSS );
mSnapVertexMarker->setColor( Qt::green );
mSnapVertexMarker->setPenWidth( 1 );
mSnapVertexMarker->setCenter( m.point() );
}
}
QgsPoint minDistPoint;
int beforeVertex;
double leftOf;
double offset = sqrt( mOriginalGeometry.closestSegmentWithContext( layerCoords, minDistPoint, beforeVertex, &leftOf ) );
if ( offset == 0.0 )
{
return;
}
if ( mDistanceWidget )
{
// this will also set the rubber band
mDistanceWidget->setValue( leftOf < 0 ? offset : -offset );
mDistanceWidget->setFocus( Qt::TabFocusReason );
}
else
{
//create offset geometry using geos
setOffsetForRubberBand( leftOf < 0 ? offset : -offset );
}
}
QgsGeometry QgsMapToolOffsetCurve::createOriginGeometry( QgsVectorLayer* vl, const QgsPointLocator::Match& match, QgsFeature& snappedFeature )
{
if ( !vl )
{
return QgsGeometry();
}
mMultiPartGeometry = false;
//assign feature part by vertex number (snap to vertex) or by before vertex number (snap to segment)
int partVertexNr = match.vertexIndex();
if ( vl == currentVectorLayer() && !mForceCopy )
{
//don't consider selected geometries, only the snap result
return convertToSingleLine( snappedFeature.constGeometry() ? *snappedFeature.constGeometry() : QgsGeometry(), partVertexNr, mMultiPartGeometry );
}
else //snapped to a background layer
{
//if source layer is polygon / multipolygon, create a linestring from the snapped ring
if ( vl->geometryType() == Qgis::Polygon )
{
//make linestring from polygon ring and return this geometry
return linestringFromPolygon( snappedFeature.constGeometry(), partVertexNr );
}
//for background layers, try to merge selected entries together if snapped feature is contained in selection
const QgsFeatureIds& selection = vl->selectedFeaturesIds();
if ( selection.size() < 1 || !selection.contains( match.featureId() ) )
{
return convertToSingleLine( snappedFeature.constGeometry() ? *snappedFeature.constGeometry() : QgsGeometry(), partVertexNr, mMultiPartGeometry );
}
else
{
//merge together if several features
QgsFeatureList selectedFeatures = vl->selectedFeatures();
QgsFeatureList::iterator selIt = selectedFeatures.begin();
QgsGeometry geom = selIt->constGeometry() ? *selIt->constGeometry() : QgsGeometry();
++selIt;
for ( ; selIt != selectedFeatures.end(); ++selIt )
{
QgsGeometry* combined = geom.combine( selIt->constGeometry() );
geom = *combined;
delete combined;
}
//if multitype, return only the snapped to geometry
if ( geom.isMultipart() )
{
return convertToSingleLine( snappedFeature.constGeometry() ? *snappedFeature.constGeometry() : QgsGeometry(),
match.vertexIndex(), mMultiPartGeometry );
}
return geom;
}
}
}
void QgsMapToolOffsetCurve::createDistanceWidget()
{
if ( !mCanvas )
{
return;
}
deleteDistanceWidget();
mDistanceWidget = new QgsDoubleSpinBox();
mDistanceWidget->setMinimum( -99999999 );
mDistanceWidget->setMaximum( 99999999 );
mDistanceWidget->setDecimals( 6 );
mDistanceWidget->setPrefix( tr( "Offset: " ) );
mDistanceWidget->setClearValue( 0.0 );
QgisApp::instance()->addUserInputWidget( mDistanceWidget );
mDistanceWidget->setFocus( Qt::TabFocusReason );
QObject::connect( mDistanceWidget, SIGNAL( valueChanged( double ) ), this, SLOT( placeOffsetCurveToValue() ) );
QObject::connect( mDistanceWidget, SIGNAL( editingFinished() ), this, SLOT( applyOffset() ) );
}
void QgsMapToolOffsetCurve::deleteDistanceWidget()
{
if ( mDistanceWidget )
{
QObject::disconnect( mDistanceWidget, SIGNAL( valueChanged( double ) ), this, SLOT( placeOffsetCurveToValue() ) );
QObject::disconnect( mDistanceWidget, SIGNAL( editingFinished() ), this, SLOT( applyOffset() ) );
mDistanceWidget->releaseKeyboard();
mDistanceWidget->deleteLater();
}
mDistanceWidget = nullptr;
}
void QgsMapToolOffsetCurve::deleteRubberBandAndGeometry()
{
delete mRubberBand;
mRubberBand = nullptr;
}
void QgsMapToolOffsetCurve::setOffsetForRubberBand( double offset )
{
// need at least geos 3.3 for OffsetCurve tool
#if defined(GEOS_VERSION_MAJOR) && defined(GEOS_VERSION_MINOR) && \
((GEOS_VERSION_MAJOR>3) || ((GEOS_VERSION_MAJOR==3) && (GEOS_VERSION_MINOR>=3)))
if ( !mRubberBand || mOriginalGeometry.isEmpty() )
{
return;
}
QgsVectorLayer* sourceLayer = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( mSourceLayerId ) );
if ( !sourceLayer )
{
return;
}
QgsGeometry geomCopy( mOriginalGeometry );
const GEOSGeometry* geosGeom = geomCopy.asGeos();
if ( geosGeom )
{
QSettings s;
int joinStyle = s.value( "/qgis/digitizing/offset_join_style", 0 ).toInt();
int quadSegments = s.value( "/qgis/digitizing/offset_quad_seg", 8 ).toInt();
double mitreLimit = s.value( "/qgis/digitizing/offset_miter_limit", 5.0 ).toDouble();
GEOSGeometry* offsetGeom = GEOSOffsetCurve_r( QgsGeometry::getGEOSHandler(), geosGeom, offset, quadSegments, joinStyle, mitreLimit );
if ( !offsetGeom )
{
deleteRubberBandAndGeometry();
deleteDistanceWidget();
delete mSnapVertexMarker;
mSnapVertexMarker = nullptr;
mForceCopy = false;
mGeometryModified = false;
deleteDistanceWidget();
emit messageEmitted( tr( "Creating offset geometry failed" ), QgsMessageBar::CRITICAL );
return;
}
if ( offsetGeom )
{
mModifiedGeometry.fromGeos( offsetGeom );
mRubberBand->setToGeometry( mModifiedGeometry, sourceLayer );
}
}
#else //GEOS_VERSION>=3.3
Q_UNUSED( offset );
#endif //GEOS_VERSION>=3.3
}
QgsGeometry QgsMapToolOffsetCurve::linestringFromPolygon( const QgsGeometry* featureGeom, int vertex )
{
if ( !featureGeom )
{
return QgsGeometry();
}
Qgis::WkbType geomType = featureGeom->wkbType();
int currentVertex = 0;
QgsMultiPolygon multiPoly;
if ( geomType == Qgis::WKBPolygon || geomType == Qgis::WKBPolygon25D )
{
QgsPolygon polygon = featureGeom->asPolygon();
multiPoly.append( polygon );
}
else if ( geomType == Qgis::WKBMultiPolygon || geomType == Qgis::WKBMultiPolygon25D )
{
//iterate all polygons / rings
QgsMultiPolygon multiPoly = featureGeom->asMultiPolygon();
}
else
{
return QgsGeometry();
}
QgsMultiPolygon::const_iterator multiPolyIt = multiPoly.constBegin();
for ( ; multiPolyIt != multiPoly.constEnd(); ++multiPolyIt )
{
QgsPolygon::const_iterator polyIt = multiPolyIt->constBegin();
for ( ; polyIt != multiPolyIt->constEnd(); ++polyIt )
{
currentVertex += polyIt->size();
if ( vertex < currentVertex )
{
//found, return ring
QgsGeometry* g = QgsGeometry::fromPolyline( *polyIt );
QgsGeometry result = *g;
delete g;
return result;
}
}
}
return QgsGeometry();
}
QgsGeometry QgsMapToolOffsetCurve::convertToSingleLine( const QgsGeometry& geom, int vertex, bool& isMulti )
{
if ( geom.isEmpty() )
{
return QgsGeometry();
}
isMulti = false;
Qgis::WkbType geomType = geom.wkbType();
if ( geomType == Qgis::WKBLineString || geomType == Qgis::WKBLineString25D )
{
return geom;
}
else if ( geomType == Qgis::WKBMultiLineString || geomType == Qgis::WKBMultiLineString25D )
{
//search vertex
isMulti = true;
int currentVertex = 0;
QgsMultiPolyline multiLine = geom.asMultiPolyline();
QgsMultiPolyline::const_iterator it = multiLine.constBegin();
for ( ; it != multiLine.constEnd(); ++it )
{
currentVertex += it->size();
if ( vertex < currentVertex )
{
QgsGeometry* g = QgsGeometry::fromPolyline( *it );
return *g;
}
}
}
return QgsGeometry();
}
QgsGeometry* QgsMapToolOffsetCurve::convertToMultiLine( QgsGeometry* geom )
{
Q_UNUSED( geom );
return nullptr;
}