Skip to content
Permalink
Browse files
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 2b6c3c1db2cc3c8214a6c019c2c044b8bae712f2
@@ -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,
@@ -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

@@ -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 \)/;

0 comments on commit 2b6c3c1

Please sign in to comment.