Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Fix PyQGIS QgsLineString constructor only accepts lists of QgsPoint,
not QgsPointXY as indicated by the documentation

Also add support for constructing QgsLineString using arrays of
arrays of floats, given that we're having to hand-roll sip conversion
code anyway!

Now the following is supported:

  line = QgsLineString([[1,2], [3,4], [5,6]])

which is much nicer and more "pythonic" then the explicit
QgsPoint/QgsPointXY sequences!

Fixes #43200
  • Loading branch information
nyalldawson committed May 25, 2021
1 parent 2a06fcc commit 2b6c3c1
Show file tree
Hide file tree
Showing 4 changed files with 479 additions and 36 deletions.
206 changes: 185 additions & 21 deletions python/core/auto_generated/geometry/qgslinestring.sip.in
Expand Up @@ -31,13 +31,193 @@ Line string geometry type, with support for z-dimension and m-values.
Constructor for an empty linestring geometry.
%End

QgsLineString( const QVector<QgsPoint> &points ) /HoldGIL/;
QgsLineString( SIP_PYOBJECT points /TypeHint="Sequence[Union[QgsPoint, QgsPointXY, Sequence[float]]]"/ ) [( const QVector<double> &x, const QVector<double> &y, const QVector<double> &z = QVector<double>(), const QVector<double> &m = QVector<double>(), bool is25DType = false )];
%Docstring
Construct a linestring from a vector of points.
Z and M type will be set based on the type of the first point
in the vector.
Construct a linestring from a sequence of points (:py:class:`QgsPoint` objects, :py:class:`QgsPointXY` objects, or sequences of float values).

.. versionadded:: 3.0
The linestring Z and M type will be set based on the type of the first point in the sequence.

.. versionadded:: 3.20
%End
%MethodCode
if ( !PySequence_Check( a0 ) )
{
PyErr_SetString( PyExc_TypeError, QStringLiteral( "A sequence of QgsPoint, QgsPointXY or array of floats is expected" ).toUtf8().constData() );
sipIsErr = 1;
}
else
{
int state;
const int size = PySequence_Size( a0 );
QVector< double > xl;
QVector< double > yl;
bool hasZ = false;
QVector< double > zl;
bool hasM = false;
QVector< double > ml;
xl.reserve( size );
yl.reserve( size );

bool is25D = false;

sipIsErr = 0;
for ( int i = 0; i < size; ++i )
{
PyObject *value = PySequence_GetItem( a0, i );
if ( !value )
{
PyErr_SetString( PyExc_TypeError, QStringLiteral( "Invalid type at index %1." ).arg( i ) .toUtf8().constData() );
sipIsErr = 1;
break;
}

if ( PySequence_Check( value ) )
{
const int elementSize = PySequence_Size( value );
if ( elementSize < 2 || elementSize > 4 )
{
sipIsErr = 1;
PyErr_SetString( PyExc_TypeError, QStringLiteral( "Invalid sequence size at index %1. Expected an array of 2-4 float values, got %2." ).arg( i ).arg( elementSize ).toUtf8().constData() );
Py_DECREF( value );
break;
}
else
{
sipIsErr = 0;
for ( int j = 0; j < elementSize; ++j )
{
PyObject *element = PySequence_GetItem( value, j );
if ( !element )
{
PyErr_SetString( PyExc_TypeError, QStringLiteral( "Invalid type at index %1." ).arg( i ) .toUtf8().constData() );
sipIsErr = 1;
break;
}

PyErr_Clear();
double d = PyFloat_AsDouble( element );
if ( PyErr_Occurred() )
{
Py_DECREF( value );
sipIsErr = 1;
break;
}
if ( j == 0 )
xl.append( d );
else if ( j == 1 )
yl.append( d );

if ( i == 0 && j == 2 )
{
hasZ = true;
zl.reserve( size );
zl.append( d );
}
else if ( i > 0 && j == 2 && hasZ )
{
zl.append( d );
}

if ( i == 0 && j == 3 )
{
hasM = true;
ml.reserve( size );
ml.append( d );
}
else if ( i > 0 && j == 3 && hasM )
{
ml.append( d );
}

Py_DECREF( element );
}

if ( hasZ && elementSize < 3 )
zl.append( std::numeric_limits< double >::quiet_NaN() );
if ( hasM && elementSize < 4 )
ml.append( std::numeric_limits< double >::quiet_NaN() );

Py_DECREF( value );
if ( sipIsErr )
{
break;
}
}
}
else
{
if ( sipCanConvertToType( value, sipType_QgsPointXY, SIP_NOT_NONE ) )
{
sipIsErr = 0;
QgsPointXY *p = reinterpret_cast<QgsPointXY *>( sipConvertToType( value, sipType_QgsPointXY, 0, SIP_NOT_NONE, &state, &sipIsErr ) );
if ( !sipIsErr )
{
xl.append( p->x() );
yl.append( p->y() );
}
sipReleaseType( p, sipType_QgsPointXY, state );
}
else if ( sipCanConvertToType( value, sipType_QgsPoint, SIP_NOT_NONE ) )
{
sipIsErr = 0;
QgsPoint *p = reinterpret_cast<QgsPoint *>( sipConvertToType( value, sipType_QgsPoint, 0, SIP_NOT_NONE, &state, &sipIsErr ) );
if ( !sipIsErr )
{
xl.append( p->x() );
yl.append( p->y() );

if ( i == 0 && p->is3D() )
{
hasZ = true;
zl.reserve( size );
zl.append( p->z() );
}
else if ( i > 0 && hasZ )
{
zl.append( p->z() );
}

if ( i == 0 && p->isMeasure() )
{
hasM = true;
ml.reserve( size );
ml.append( p->m() );
}
else if ( i > 0 && hasM )
{
ml.append( p->m() );
}

if ( i == 0 && p->wkbType() == QgsWkbTypes::Point25D )
is25D = true;
}
sipReleaseType( p, sipType_QgsPoint, state );
}
else
{
sipIsErr = 1;
}

Py_DECREF( value );

if ( sipIsErr )
{
// couldn't convert the sequence value to a QgsPoint or QgsPointXY
PyErr_SetString( PyExc_TypeError, QStringLiteral( "Invalid type at index %1. Expected QgsPoint, QgsPointXY or array of floats." ).arg( i ) .toUtf8().constData() );
break;
}
}
}
if ( sipIsErr == 0 )
sipCpp = new sipQgsLineString( QgsLineString( xl, yl, zl, ml, is25D ) );
}
%End

explicit QgsLineString( const QgsLineSegment2D &segment ) /HoldGIL/;
%Docstring
Construct a linestring from a single 2d line segment.

.. versionadded:: 3.2
%End

QgsLineString( const QVector<double> &x, const QVector<double> &y,
Expand Down Expand Up @@ -66,22 +246,6 @@ will be created using the minimum size of these arrays.
%Docstring
Constructs a linestring with a single segment from ``p1`` to ``p2``.

.. versionadded:: 3.2
%End

QgsLineString( const QVector<QgsPointXY> &points ) /HoldGIL/;
%Docstring
Construct a linestring from list of points.
This constructor is more efficient then calling :py:func:`~QgsLineString.setPoints`
or repeatedly calling :py:func:`~QgsLineString.addVertex`

.. versionadded:: 3.0
%End

explicit QgsLineString( const QgsLineSegment2D &segment ) /HoldGIL/;
%Docstring
Construct a linestring from a single 2d line segment.

.. versionadded:: 3.2
%End

Expand Down
2 changes: 1 addition & 1 deletion scripts/sipify.pl
Expand Up @@ -481,7 +481,7 @@ sub fix_annotations {
$line =~ s/\bSIP_GETWRAPPER\b/\/GetWrapper\//;

$line =~ s/SIP_PYNAME\(\s*(\w+)\s*\)/\/PyName=$1\//;
$line =~ s/SIP_TYPEHINT\(\s*(\w+)\s*\)/\/TypeHint="$1"\//;
$line =~ s/SIP_TYPEHINT\(\s*([\w\s,\[\]]+?)\s*\)/\/TypeHint="$1"\//;
$line =~ s/SIP_VIRTUALERRORHANDLER\(\s*(\w+)\s*\)/\/VirtualErrorHandler=$1\//;
$line =~ s/SIP_THROW\(\s*(\w+)\s*\)/throw\( $1 \)/;

Expand Down

0 comments on commit 2b6c3c1

Please sign in to comment.