Skip to content

Commit

Permalink
[convert to curve] initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
olivierdalang authored and nyalldawson committed Jun 18, 2021
1 parent 9553f06 commit c7e75a9
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 1 deletion.
139 changes: 138 additions & 1 deletion src/app/vertextool/qgsvertextool.cpp
Expand Up @@ -12,7 +12,7 @@
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsmessagelog.h"
#include "qgsvertextool.h"

#include "qgsadvanceddigitizingdockwidget.h"
Expand Down Expand Up @@ -1374,6 +1374,15 @@ void QgsVertexTool::keyPressEvent( QKeyEvent *e )
}
break;
}
case Qt::Key_C:
{
if ( mDraggingVertex || ( !mDraggingEdge && !mSelectedVertices.isEmpty() ) )
{
e->ignore(); // Override default shortcut management
toggleVertexCurve();
}
break;
}
case Qt::Key_R:
{
if ( e->modifiers() & Qt::ShiftModifier && !mDraggingVertex && !mDraggingEdge )
Expand Down Expand Up @@ -2499,6 +2508,7 @@ void QgsVertexTool::deleteVertex()
std::sort( vertexIds.begin(), vertexIds.end(), std::greater<int>() );
for ( int vertexId : vertexIds )
{
QgsMessageLog::logMessage("DELETE : fid:"+QString::number(fid)+" ; vertexId:"+QString::number(vertexId), "DEBUG");
if ( res != QgsVectorLayer::EmptyGeometry )
res = layer->deleteVertex( fid, vertexId );
if ( res != QgsVectorLayer::EmptyGeometry && res != QgsVectorLayer::Success )
Expand Down Expand Up @@ -2549,6 +2559,133 @@ void QgsVertexTool::deleteVertex()
mVertexEditor->updateEditor( mLockedFeature.get() );
}


void QgsVertexTool::toggleVertexCurve()
{
Vertex toConvert = Vertex(nullptr, -1, -1);
if ( mSelectedVertices.size() == 1 )
{
toConvert = mSelectedVertices.first();
}
else if( mDraggingVertexType == AddingVertex )
{
toConvert = *mDraggingVertex;
}
else
{
// We only support converting one vertex at a time
QgsMessageLog::logMessage("Need exactly 1 selected/editted vertex", "DEBUG");
return;
}


QgsVectorLayer *layer = toConvert.layer;
QgsFeatureId fId = toConvert.fid;
int vNr = toConvert.vertexId;
QgsVertexId vId;
QgsFeature feature = layer->getFeature( fId );
QgsGeometry geom = feature.geometry();
geom.vertexIdFromVertexNr( vNr, vId );

if ( ! QgsWkbTypes::isCurvedType( layer->wkbType() ) )
{
// The layer is not a curved type
QgsMessageLog::logMessage("Layer is not curved", "DEBUG");
return;
}

// We get the vertex ID and the point
// QgsPoint vPt = geom.constGet()->vertexAt(vId);

// QgsVertexId vIdPrev;
// QgsVertexId vIdNext;
// geom.constGet()->adjacentVertices(vId, vIdPrev, vIdNext);
// if( ! vIdPrev.isValid() || ! vIdNext.isValid() ){
// QgsMessageLog::logMessage("Can't work on first or last point", "DEBUG");
// return;
// }

QgsCompoundCurve *compoundCurve = dynamic_cast<QgsCompoundCurve *>( geom.get() );
if( ! compoundCurve ){
QgsMessageLog::logMessage("Only compound curves supported (for now)", "DEBUG");
return;
}

// const QgsCurve *c0 = compoundCurve->curveAt(0);
// QgsMessageLog::logMessage("Curve 0 : " + c0->asWkt(), "DEBUG");
// const QgsCurve *c1 = compoundCurve->curveAt(1);
// QgsMessageLog::logMessage("Curve 1 : " + c1->asWkt(), "DEBUG");



// QgsCircularString arc = new QgsCircularString(prevVid, vid, nextVid);

// QgsGeometry geomPartA = geom.clone();
// QgsGeometry geomPartB = geom.clone();

// geomPartA.splitFeature()

// QgsMessageLog::logMessage("Geom is of type : " + geom.constGet()->wktTypeStr(), "DEBUG");

// vid.part;
// vid.ring;
// vid.vertex;
// QgsCompoundCurve *compoundCurveCopy = compoundCurve->clone();

// QgsVectorLayerEditUtils
// bool success;
// QgsMessageLog::logMessage("CONVERT : fid:"+QString::number(fId)+" ; vertexId:"+QString::number(vNr), "DEBUG");

// QVector< QPair<int, QgsVertexId> > curveVertexId = compoundCurve->curveVertexId(vId);
// QgsMessageLog::logMessage("curveVertexId (sic!)", "DEBUG");
// for ( auto it = curveVertexId.constBegin(); it != curveVertexId.constEnd(); ++it )
// {
// QgsMessageLog::logMessage("Curve : "+QString::number(it->first)+" Point : "+QString::number(it->second.part)+"/"+QString::number(it->second.ring)+"/"+QString::number(it->second.vertex), "DEBUG");
// }


QgsAbstractGeometry *geomTmp = geom.constGet()->clone();
QgsCompoundCurve *compoundCurveCopy = compoundCurve->clone();



if ( vId.type == QgsVertexId::CurveVertex ) {
layer->beginEditCommand( tr( "Converting vertex to linear" ) );
// layer->deleteVertex( fId, vNr );
// layer->insertVertex( vPt, fId, vNr );
// vId.type = QgsVertexId::CurveVertex;
// feature.setGeometry( QgsGeometry(compoundCurveCopy ));
compoundCurveCopy->convertVertex( vId, QgsVertexId::SegmentVertex );

} else {
layer->beginEditCommand( tr( "Converting vertex to curve" ) );
// layer->deleteVertex( fId, vNr );
// layer->insertVertex( vPt, fId, vNr );
// vId.type = QgsVertexId::SegmentVertex;
// feature.setGeometry( QgsGeometry(compoundCurveCopy ));
compoundCurveCopy->convertVertex( vId, QgsVertexId::CurveVertex );
}

geom.set( compoundCurveCopy );
layer->changeGeometry( fId, geom );

// if ( success )
// {
QgsMessageLog::logMessage("Should be OK", "DEBUG");
layer->endEditCommand();
layer->triggerRepaint();
// }
// else
// {
// QgsMessageLog::logMessage("Has failed :-/", "DEBUG");
// layer->destroyEditCommand();
// }


if ( mVertexEditor && mLockedFeature )
mVertexEditor->updateEditor( mLockedFeature.get() );
}

void QgsVertexTool::setHighlightedVertices( const QList<Vertex> &listVertices, HighlightMode mode )
{
// we need to make a local copy of vertices - often this method gets called
Expand Down
2 changes: 2 additions & 0 deletions src/app/vertextool/qgsvertextool.h
Expand Up @@ -221,6 +221,8 @@ class APP_EXPORT QgsVertexTool : public QgsMapToolAdvancedDigitizing

void deleteVertex();

void toggleVertexCurve();

typedef QHash<QgsVectorLayer *, QHash<QgsFeatureId, QgsGeometry> > VertexEdits;

void addExtraVerticesToEdits( VertexEdits &edits, const QgsPointXY &mapPoint, QgsVectorLayer *dragLayer = nullptr, const QgsPoint &layerPoint = QgsPoint() );
Expand Down
75 changes: 75 additions & 0 deletions src/core/geometry/qgscompoundcurve.cpp
Expand Up @@ -15,6 +15,7 @@
* *
***************************************************************************/

#include "qgsmessagelog.h"
#include "qgscompoundcurve.h"
#include "qgsapplication.h"
#include "qgscircularstring.h"
Expand Down Expand Up @@ -914,6 +915,80 @@ QVector< QPair<int, QgsVertexId> > QgsCompoundCurve::curveVertexId( QgsVertexId
return curveIds;
}

bool QgsCompoundCurve::convertVertex( QgsVertexId position, QgsVertexId::VertexType type )
{

// First we find out the sub-curves that are contain that vertex.

// If there is more than one, it means the vertex was at the beginning or end
// of an arc, which we don't support. [TODO : could also happen if at the begging or end of
// two LineStrings, esp. after converting some other vertices... we may need to merge successive linestrings]

// If there is exactly one, we may either be on a LineString, or on a CircularString.

// If on CircularString, we need to check if the vertex is a CurveVertex. I not, the vertex
// was at the beginning or end, which we don't support. If yes, we must split the CircularString
// at vertex -1 and +1, , drop the middle part and insert a LineString instead with the same points.
// [TODO : as said above, probably worth merging successive linestrings into one once this is done]

// If on a LineString, we need to split the LineString at vertex -1 and +1, and insert a CircularString
// instead.

QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );


QgsMessageLog::logMessage("Starting convertVertex", "DEBUG");

for ( auto it = curveIds.constBegin(); it != curveIds.constEnd(); ++it )
{
QgsMessageLog::logMessage("Treating curve "+QString::number(it->first), "DEBUG");

const int curveId = it->first;
const QgsCurve *curve = curveAt(curveId);
const QgsVertexId subVertexId = it->second;

// If it's a circular string, we convert to LineString
const QgsCircularString *circularString = dynamic_cast<const QgsCircularString *>( curve );
if( circularString ){
QgsMessageLog::logMessage("Dealing with CircularString", "DEBUG");
// remove the existing CircularString

QgsMessageLog::logMessage("A. remove curve" + QString::number(curveId), "DEBUG");
// We remove the existing CircularString and create a LineString instead
removeCurve(curveId);
QVector<QgsPoint> points;
circularString->points(points);
QgsLineString *newLineString = new QgsLineString(points);
mCurves.insert(it->first, newLineString);

}

const QgsLineString *lineString = dynamic_cast<const QgsLineString *>( curve );
if( lineString ){
QgsMessageLog::logMessage("TODO : LineString not treated yet", "DEBUG");
// TODO
}

}

// We merge consecutive LineStrings
// TODO ? : move this to a new mergeConsecutiveLineStrings() method;
// const QVector< QgsCurve * > curves = mCurves;
// int i = 0;
// lastCurve *curve
// for ( QgsCurve *curve : curves )
// {

// }

clearCache();

bool success = true;
return success;

}


double QgsCompoundCurve::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
{
return QgsGeometryUtils::closestSegmentFromComponents( mCurves, QgsGeometryUtils::Vertex, pt, segmentPt, vertexAfter, leftOf, epsilon );
Expand Down
1 change: 1 addition & 0 deletions src/core/geometry/qgscompoundcurve.h
Expand Up @@ -124,6 +124,7 @@ class CORE_EXPORT QgsCompoundCurve: public QgsCurve
bool insertVertex( QgsVertexId position, const QgsPoint &vertex ) override;
bool moveVertex( QgsVertexId position, const QgsPoint &newPos ) override;
bool deleteVertex( QgsVertexId position ) override;
bool convertVertex( QgsVertexId position, QgsVertexId::VertexType type );
double closestSegment( const QgsPoint &pt, QgsPoint &segmentPt SIP_OUT, QgsVertexId &vertexAfter SIP_OUT, int *leftOf SIP_OUT = nullptr, double epsilon = 4 * std::numeric_limits<double>::epsilon() ) const override;
bool pointAt( int node, QgsPoint &point, QgsVertexId::VertexType &type ) const override;
void sumUpArea( double &sum SIP_OUT ) const override;
Expand Down
2 changes: 2 additions & 0 deletions src/gui/qgsmaptoolcapture.cpp
Expand Up @@ -345,6 +345,8 @@ void QgsMapToolCapture::resetRubberBand()
if ( !mRubberBand )
return;
QgsLineString *lineString = mCaptureCurve.curveToLine();
// maybe ?
// mRubberBand->reset( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry, mDigitizingType );
mRubberBand->reset( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry );
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() );
mRubberBand->addGeometry( QgsGeometry( lineString ), vlayer );
Expand Down
46 changes: 46 additions & 0 deletions test_digicurve.py
@@ -0,0 +1,46 @@
from itertools import count

l = QgsVectorLayer("CompoundCurve?crs=epsg:4326", "test layer", "memory")
QgsProject.instance().addMapLayer(l)


f1 = QgsFeature()
f1.setGeometry(QgsGeometry.fromWkt("LINESTRING(0 0, 5 5, 10 0, 15 5, 20 0)"))
f2 = QgsFeature()
f2.setGeometry(QgsGeometry.fromWkt("COMPOUNDCURVE((0 10, 5 15), CIRCULARSTRING(5 15, 10 10, 15 15), (15 15, 20 10))"))
f3 = QgsFeature()
f3.setGeometry(QgsGeometry.fromWkt("COMPOUNDCURVE(CIRCULARSTRING(0 20, 5 25, 10 20, 15 25, 20 20))"))
f4 = QgsFeature()
f4.setGeometry(QgsGeometry.fromWkt("COMPOUNDCURVE(CIRCULARSTRING(0 30, 5 35, 10 30), (10 30, 15 35, 20 30))"))
f5 = QgsFeature()
f5.setGeometry(QgsGeometry.fromWkt("COMPOUNDCURVE((0 50, 5 55), (5 55, 10 50, 15 55, 20 50))"))
f6 = QgsFeature()
f6.setGeometry(QgsGeometry.fromWkt("COMPOUNDCURVE(CIRCULARSTRING(0 60, 5 65, 10 60), (10 60, 15 65), CIRCULARSTRING(15 65, 20 60, 25 65))"))
l.dataProvider().addFeatures([f1, f2, f3, f4, f5, f6])


for f in l.getFeatures():
print(f"Feature {f.id()}")
print(" QgsCompoundCurve::nextVertex()")
print(f" {f.geometry().constGet().__class__.__name__} has {f.geometry().constGet().numPoints()} points")

id = QgsVertexId()
for i in count():
exists, point = f.geometry().constGet().nextVertex(id)
if not exists:
break
print(f" Point id: <{id.part}/{id.ring}/{id.vertex}> point: <{point.x()};{point.y()}>")

print(" QgsCompoundCurve::nCurves() then QgsCurve::nextVertex()")
if not hasattr(f.geometry().constGet(), 'nCurves'):
print(f" {f.geometry().constGet().__class__.__name__} has not nCurves")
else:
for n in range(f.geometry().constGet().nCurves()):
curve = f.geometry().constGet().curveAt(n)
print(f" {curve.__class__.__name__} has {curve.numPoints()} points")
id = QgsVertexId()
for i in count():
exists, point = curve.nextVertex(id)
if not exists:
break
print(f" Curve {n} / Point id: <{id.part}/{id.ring}/{id.vertex}> point: <{point.x()};{point.y()}>")
Binary file added test_digicurve.py.qgz
Binary file not shown.

0 comments on commit c7e75a9

Please sign in to comment.