Skip to content

Commit 2b6c3c1

Browse files
committedMay 25, 2021
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
1 parent 2a06fcc commit 2b6c3c1

File tree

4 files changed

+479
-36
lines changed

4 files changed

+479
-36
lines changed
 

‎python/core/auto_generated/geometry/qgslinestring.sip.in

Lines changed: 185 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,193 @@ Line string geometry type, with support for z-dimension and m-values.
3131
Constructor for an empty linestring geometry.
3232
%End
3333

34-
QgsLineString( const QVector<QgsPoint> &points ) /HoldGIL/;
34+
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 )];
3535
%Docstring
36-
Construct a linestring from a vector of points.
37-
Z and M type will be set based on the type of the first point
38-
in the vector.
36+
Construct a linestring from a sequence of points (:py:class:`QgsPoint` objects, :py:class:`QgsPointXY` objects, or sequences of float values).
3937

40-
.. versionadded:: 3.0
38+
The linestring Z and M type will be set based on the type of the first point in the sequence.
39+
40+
.. versionadded:: 3.20
41+
%End
42+
%MethodCode
43+
if ( !PySequence_Check( a0 ) )
44+
{
45+
PyErr_SetString( PyExc_TypeError, QStringLiteral( "A sequence of QgsPoint, QgsPointXY or array of floats is expected" ).toUtf8().constData() );
46+
sipIsErr = 1;
47+
}
48+
else
49+
{
50+
int state;
51+
const int size = PySequence_Size( a0 );
52+
QVector< double > xl;
53+
QVector< double > yl;
54+
bool hasZ = false;
55+
QVector< double > zl;
56+
bool hasM = false;
57+
QVector< double > ml;
58+
xl.reserve( size );
59+
yl.reserve( size );
60+
61+
bool is25D = false;
62+
63+
sipIsErr = 0;
64+
for ( int i = 0; i < size; ++i )
65+
{
66+
PyObject *value = PySequence_GetItem( a0, i );
67+
if ( !value )
68+
{
69+
PyErr_SetString( PyExc_TypeError, QStringLiteral( "Invalid type at index %1." ).arg( i ) .toUtf8().constData() );
70+
sipIsErr = 1;
71+
break;
72+
}
73+
74+
if ( PySequence_Check( value ) )
75+
{
76+
const int elementSize = PySequence_Size( value );
77+
if ( elementSize < 2 || elementSize > 4 )
78+
{
79+
sipIsErr = 1;
80+
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() );
81+
Py_DECREF( value );
82+
break;
83+
}
84+
else
85+
{
86+
sipIsErr = 0;
87+
for ( int j = 0; j < elementSize; ++j )
88+
{
89+
PyObject *element = PySequence_GetItem( value, j );
90+
if ( !element )
91+
{
92+
PyErr_SetString( PyExc_TypeError, QStringLiteral( "Invalid type at index %1." ).arg( i ) .toUtf8().constData() );
93+
sipIsErr = 1;
94+
break;
95+
}
96+
97+
PyErr_Clear();
98+
double d = PyFloat_AsDouble( element );
99+
if ( PyErr_Occurred() )
100+
{
101+
Py_DECREF( value );
102+
sipIsErr = 1;
103+
break;
104+
}
105+
if ( j == 0 )
106+
xl.append( d );
107+
else if ( j == 1 )
108+
yl.append( d );
109+
110+
if ( i == 0 && j == 2 )
111+
{
112+
hasZ = true;
113+
zl.reserve( size );
114+
zl.append( d );
115+
}
116+
else if ( i > 0 && j == 2 && hasZ )
117+
{
118+
zl.append( d );
119+
}
120+
121+
if ( i == 0 && j == 3 )
122+
{
123+
hasM = true;
124+
ml.reserve( size );
125+
ml.append( d );
126+
}
127+
else if ( i > 0 && j == 3 && hasM )
128+
{
129+
ml.append( d );
130+
}
131+
132+
Py_DECREF( element );
133+
}
134+
135+
if ( hasZ && elementSize < 3 )
136+
zl.append( std::numeric_limits< double >::quiet_NaN() );
137+
if ( hasM && elementSize < 4 )
138+
ml.append( std::numeric_limits< double >::quiet_NaN() );
139+
140+
Py_DECREF( value );
141+
if ( sipIsErr )
142+
{
143+
break;
144+
}
145+
}
146+
}
147+
else
148+
{
149+
if ( sipCanConvertToType( value, sipType_QgsPointXY, SIP_NOT_NONE ) )
150+
{
151+
sipIsErr = 0;
152+
QgsPointXY *p = reinterpret_cast<QgsPointXY *>( sipConvertToType( value, sipType_QgsPointXY, 0, SIP_NOT_NONE, &state, &sipIsErr ) );
153+
if ( !sipIsErr )
154+
{
155+
xl.append( p->x() );
156+
yl.append( p->y() );
157+
}
158+
sipReleaseType( p, sipType_QgsPointXY, state );
159+
}
160+
else if ( sipCanConvertToType( value, sipType_QgsPoint, SIP_NOT_NONE ) )
161+
{
162+
sipIsErr = 0;
163+
QgsPoint *p = reinterpret_cast<QgsPoint *>( sipConvertToType( value, sipType_QgsPoint, 0, SIP_NOT_NONE, &state, &sipIsErr ) );
164+
if ( !sipIsErr )
165+
{
166+
xl.append( p->x() );
167+
yl.append( p->y() );
168+
169+
if ( i == 0 && p->is3D() )
170+
{
171+
hasZ = true;
172+
zl.reserve( size );
173+
zl.append( p->z() );
174+
}
175+
else if ( i > 0 && hasZ )
176+
{
177+
zl.append( p->z() );
178+
}
179+
180+
if ( i == 0 && p->isMeasure() )
181+
{
182+
hasM = true;
183+
ml.reserve( size );
184+
ml.append( p->m() );
185+
}
186+
else if ( i > 0 && hasM )
187+
{
188+
ml.append( p->m() );
189+
}
190+
191+
if ( i == 0 && p->wkbType() == QgsWkbTypes::Point25D )
192+
is25D = true;
193+
}
194+
sipReleaseType( p, sipType_QgsPoint, state );
195+
}
196+
else
197+
{
198+
sipIsErr = 1;
199+
}
200+
201+
Py_DECREF( value );
202+
203+
if ( sipIsErr )
204+
{
205+
// couldn't convert the sequence value to a QgsPoint or QgsPointXY
206+
PyErr_SetString( PyExc_TypeError, QStringLiteral( "Invalid type at index %1. Expected QgsPoint, QgsPointXY or array of floats." ).arg( i ) .toUtf8().constData() );
207+
break;
208+
}
209+
}
210+
}
211+
if ( sipIsErr == 0 )
212+
sipCpp = new sipQgsLineString( QgsLineString( xl, yl, zl, ml, is25D ) );
213+
}
214+
%End
215+
216+
explicit QgsLineString( const QgsLineSegment2D &segment ) /HoldGIL/;
217+
%Docstring
218+
Construct a linestring from a single 2d line segment.
219+
220+
.. versionadded:: 3.2
41221
%End
42222

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

69-
.. versionadded:: 3.2
70-
%End
71-
72-
QgsLineString( const QVector<QgsPointXY> &points ) /HoldGIL/;
73-
%Docstring
74-
Construct a linestring from list of points.
75-
This constructor is more efficient then calling :py:func:`~QgsLineString.setPoints`
76-
or repeatedly calling :py:func:`~QgsLineString.addVertex`
77-
78-
.. versionadded:: 3.0
79-
%End
80-
81-
explicit QgsLineString( const QgsLineSegment2D &segment ) /HoldGIL/;
82-
%Docstring
83-
Construct a linestring from a single 2d line segment.
84-
85249
.. versionadded:: 3.2
86250
%End
87251

‎scripts/sipify.pl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ sub fix_annotations {
481481
$line =~ s/\bSIP_GETWRAPPER\b/\/GetWrapper\//;
482482

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

0 commit comments

Comments
 (0)
Please sign in to comment.