Skip to content

Commit f595d53

Browse files
committedNov 26, 2018
Negative indices count from back of linestring
1 parent 1e54799 commit f595d53

File tree

3 files changed

+434
-126
lines changed

3 files changed

+434
-126
lines changed
 

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

Lines changed: 151 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -81,188 +81,259 @@ Construct a linestring from a single 2d line segment.
8181
virtual bool equals( const QgsCurve &other ) const;
8282

8383

84+
8485
SIP_PYOBJECT pointN( int i ) const;
8586
%Docstring
86-
Returns the specified point from inside the line string.
87+
Returns the point at the specified index. An IndexError will be raised if no point with the specified index exists.
8788

88-
:param i: index of point, starting at 0 for the first point
89+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
90+
corresponds to the last point in the line.
8991
%End
9092
%MethodCode
91-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
93+
const int count = sipCpp->numPoints();
94+
if ( a0 < -count || a0 >= count )
9295
{
9396
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
9497
sipIsErr = 1;
9598
}
9699
else
97100
{
98-
std::unique_ptr< QgsPoint > p = qgis::make_unique< QgsPoint >( sipCpp->pointN( a0 ) );
101+
std::unique_ptr< QgsPoint > p;
102+
if ( a0 >= 0 )
103+
p = qgis::make_unique< QgsPoint >( sipCpp->pointN( a0 ) );
104+
else // negative index, count backwards from end
105+
p = qgis::make_unique< QgsPoint >( sipCpp->pointN( count + a0 ) );
99106
sipRes = sipConvertFromType( p.release(), sipType_QgsPoint, Py_None );
100107
}
101108
%End
102109

110+
103111
virtual double xAt( int index ) const;
104112

113+
%Docstring
114+
Returns the x-coordinate of the specified node in the line string.
115+
116+
An IndexError will be raised if no point with the specified index exists.
117+
118+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
119+
corresponds to the last point in the line.
120+
%End
105121
%MethodCode
106-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
122+
const int count = sipCpp->numPoints();
123+
if ( a0 < -count || a0 >= count )
107124
{
108125
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
109126
sipIsErr = 1;
110127
}
111128
else
112129
{
113-
return PyFloat_FromDouble( sipCpp->xAt( a0 ) );
130+
if ( a0 >= 0 )
131+
return PyFloat_FromDouble( sipCpp->xAt( a0 ) );
132+
else
133+
return PyFloat_FromDouble( sipCpp->xAt( count + a0 ) );
114134
}
115135
%End
116136

137+
117138
virtual double yAt( int index ) const;
118139

140+
%Docstring
141+
Returns the y-coordinate of the specified node in the line string.
142+
143+
An IndexError will be raised if no point with the specified index exists.
144+
145+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
146+
corresponds to the last point in the line.
147+
%End
119148
%MethodCode
120-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
149+
const int count = sipCpp->numPoints();
150+
if ( a0 < -count || a0 >= count )
121151
{
122152
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
123153
sipIsErr = 1;
124154
}
125155
else
126156
{
127-
return PyFloat_FromDouble( sipCpp->yAt( a0 ) );
157+
if ( a0 >= 0 )
158+
return PyFloat_FromDouble( sipCpp->yAt( a0 ) );
159+
else
160+
return PyFloat_FromDouble( sipCpp->yAt( count + a0 ) );
128161
}
129162
%End
130163

131164

132165

133166

134167

168+
135169
double zAt( int index ) const;
136170
%Docstring
137171
Returns the z-coordinate of the specified node in the line string.
138172

139-
:param index: index of node, where the first node in the line is 0
173+
An IndexError will be raised if no point with the specified index exists.
140174

141-
:return: z-coordinate of node, or ``nan`` if index is out of bounds or the line
142-
does not have a z dimension
175+
If the LineString does not have a z-dimension then ``nan`` will be returned.
143176

144-
.. seealso:: :py:func:`setZAt`
177+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
178+
corresponds to the last point in the line.
145179
%End
146180
%MethodCode
147-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
181+
const int count = sipCpp->numPoints();
182+
if ( a0 < -count || a0 >= count )
148183
{
149184
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
150185
sipIsErr = 1;
151186
}
152187
else
153188
{
154-
return PyFloat_FromDouble( sipCpp->zAt( a0 ) );
189+
if ( a0 >= 0 )
190+
return PyFloat_FromDouble( sipCpp->zAt( a0 ) );
191+
else
192+
return PyFloat_FromDouble( sipCpp->zAt( count + a0 ) );
155193
}
156194
%End
157195

196+
158197
double mAt( int index ) const;
159198
%Docstring
160-
Returns the m value of the specified node in the line string.
199+
Returns the m-coordinate of the specified node in the line string.
161200

162-
:param index: index of node, where the first node in the line is 0
201+
An IndexError will be raised if no point with the specified index exists.
163202

164-
:return: m value of node, or ``nan`` if index is out of bounds or the line
165-
does not have m values
203+
If the LineString does not have a m-dimension then ``nan`` will be returned.
166204

167-
.. seealso:: :py:func:`setMAt`
205+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
206+
corresponds to the last point in the line.
168207
%End
169208
%MethodCode
170-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
209+
const int count = sipCpp->numPoints();
210+
if ( a0 < -count || a0 >= count )
171211
{
172212
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
173213
sipIsErr = 1;
174214
}
175215
else
176216
{
177-
return PyFloat_FromDouble( sipCpp->mAt( a0 ) );
217+
if ( a0 >= 0 )
218+
return PyFloat_FromDouble( sipCpp->mAt( a0 ) );
219+
else
220+
return PyFloat_FromDouble( sipCpp->mAt( count + a0 ) );
178221
}
179222
%End
180223

224+
181225
void setXAt( int index, double x );
182226
%Docstring
183227
Sets the x-coordinate of the specified node in the line string.
228+
The corresponding node must already exist in line string.
229+
230+
An IndexError will be raised if no point with the specified index exists.
184231

185-
:param index: index of node, where the first node in the line is 0. Corresponding
186-
node must already exist in line string.
187-
:param x: x-coordinate of node
232+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
233+
corresponds to the last point in the line.
188234

189235
.. seealso:: :py:func:`xAt`
190236
%End
191237
%MethodCode
192-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
238+
const int count = sipCpp->numPoints();
239+
if ( a0 < -count || a0 >= count )
193240
{
194241
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
195242
sipIsErr = 1;
196243
}
197244
else
198245
{
199-
sipCpp->setXAt( a0, a1 );
246+
if ( a0 >= 0 )
247+
sipCpp->setXAt( a0, a1 );
248+
else
249+
sipCpp->setXAt( count + a0, a1 );
200250
}
201251
%End
202252

253+
203254
void setYAt( int index, double y );
204255
%Docstring
205256
Sets the y-coordinate of the specified node in the line string.
257+
The corresponding node must already exist in line string.
206258

207-
:param index: index of node, where the first node in the line is 0. Corresponding
208-
node must already exist in line string.
209-
:param y: y-coordinate of node
259+
An IndexError will be raised if no point with the specified index exists.
260+
261+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
262+
corresponds to the last point in the line.
210263

211264
.. seealso:: :py:func:`yAt`
212265
%End
213266
%MethodCode
214-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
267+
const int count = sipCpp->numPoints();
268+
if ( a0 < -count || a0 >= count )
215269
{
216270
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
217271
sipIsErr = 1;
218272
}
219273
else
220274
{
221-
sipCpp->setYAt( a0, a1 );
275+
if ( a0 >= 0 )
276+
sipCpp->setYAt( a0, a1 );
277+
else
278+
sipCpp->setYAt( count + a0, a1 );
222279
}
223280
%End
224281

282+
225283
void setZAt( int index, double z );
226284
%Docstring
227285
Sets the z-coordinate of the specified node in the line string.
286+
The corresponding node must already exist in line string and the line string must have z-dimension.
228287

229-
:param index: index of node, where the first node in the line is 0. Corresponding
230-
node must already exist in line string, and the line string must have z-dimension.
231-
:param z: z-coordinate of node
288+
An IndexError will be raised if no point with the specified index exists.
289+
290+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
291+
corresponds to the last point in the line.
232292

233293
.. seealso:: :py:func:`zAt`
234294
%End
235295
%MethodCode
236-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
296+
const int count = sipCpp->numPoints();
297+
if ( a0 < -count || a0 >= count )
237298
{
238299
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
239300
sipIsErr = 1;
240301
}
241302
else
242303
{
243-
sipCpp->setZAt( a0, a1 );
304+
if ( a0 >= 0 )
305+
sipCpp->setZAt( a0, a1 );
306+
else
307+
sipCpp->setZAt( count + a0, a1 );
244308
}
245309
%End
246310

311+
247312
void setMAt( int index, double m );
248313
%Docstring
249-
Sets the m value of the specified node in the line string.
314+
Sets the m-coordinate of the specified node in the line string.
315+
The corresponding node must already exist in line string and the line string must have m-dimension.
316+
317+
An IndexError will be raised if no point with the specified index exists.
250318

251-
:param index: index of node, where the first node in the line is 0. Corresponding
252-
node must already exist in line string, and the line string must have m values.
253-
:param m: m value of node
319+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
320+
corresponds to the last point in the line.
254321

255322
.. seealso:: :py:func:`mAt`
256323
%End
257324
%MethodCode
258-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
325+
const int count = sipCpp->numPoints();
326+
if ( a0 < -count || a0 >= count )
259327
{
260328
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
261329
sipIsErr = 1;
262330
}
263331
else
264332
{
265-
sipCpp->setMAt( a0, a1 );
333+
if ( a0 >= 0 )
334+
sipCpp->setMAt( a0, a1 );
335+
else
336+
sipCpp->setMAt( count + a0, a1 );
266337
}
267338
%End
268339

@@ -436,37 +507,51 @@ of the curve.
436507

437508
SIP_PYOBJECT __getitem__( int index );
438509
%Docstring
439-
Returns the point at the specified ``index``. An IndexError will be raised if no point with the specified ``index`` exists.
510+
Returns the point at the specified ``index``. An IndexError will be raised if no point with the specified ``index`` exists.
440511

441-
.. versionadded:: 3.6
512+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
513+
corresponds to the last point in the line.
514+
515+
.. versionadded:: 3.6
442516
%End
443517
%MethodCode
444-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
445-
{
446-
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
447-
sipIsErr = 1;
448-
}
518+
const int count = sipCpp->numPoints();
519+
if ( a0 < -count || a0 >= count )
520+
{
521+
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
522+
sipIsErr = 1;
523+
}
524+
else
525+
{
526+
std::unique_ptr< QgsPoint > p;
527+
if ( a0 >= 0 )
528+
p = qgis::make_unique< QgsPoint >( sipCpp->pointN( a0 ) );
449529
else
450-
{
451-
std::unique_ptr< QgsPoint > p = qgis::make_unique< QgsPoint >( sipCpp->pointN( a0 ) );
452-
sipRes = sipConvertFromType( p.release(), sipType_QgsPoint, Py_None );
453-
}
530+
p = qgis::make_unique< QgsPoint >( sipCpp->pointN( count + a0 ) );
531+
sipRes = sipConvertFromType( p.release(), sipType_QgsPoint, Py_None );
532+
}
454533
%End
455534

456535
void __setitem__( int index, const QgsPoint &point );
457536
%Docstring
458-
Sets the point at the specified ``index``. A point at the ``index`` must already exist or an IndexError will be raised.
537+
Sets the point at the specified ``index``. A point at the ``index`` must already exist or an IndexError will be raised.
538+
539+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
540+
corresponds to the last point in the line.
459541

460-
.. versionadded:: 3.6
542+
.. versionadded:: 3.6
461543
%End
462544
%MethodCode
463-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
545+
const int count = sipCpp->numPoints();
546+
if ( a0 < -count || a0 >= count )
464547
{
465548
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
466549
sipIsErr = 1;
467550
}
468551
else
469552
{
553+
if ( a0 < 0 )
554+
a0 = count + a0;
470555
sipCpp->setXAt( a0, a1->x() );
471556
sipCpp->setYAt( a0, a1->y() );
472557
if ( sipCpp->isMeasure() )
@@ -476,15 +561,22 @@ of the curve.
476561
}
477562
%End
478563

564+
479565
void __delitem__( int index );
480566
%Docstring
481-
Deletes the vertex at the specified ``index``. A point at the ``index`` must already exist or an IndexError will be raised.
567+
Deletes the vertex at the specified ``index``. A point at the ``index`` must already exist or an IndexError will be raised.
568+
569+
Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
570+
corresponds to the last point in the line.
482571

483-
.. versionadded:: 3.6
572+
.. versionadded:: 3.6
484573
%End
485574
%MethodCode
486-
if ( a0 >= 0 && a0 < sipCpp->numPoints() )
575+
const int count = sipCpp->numPoints();
576+
if ( a0 >= 0 && a0 < count )
487577
sipCpp->deleteVertex( QgsVertexId( -1, -1, a0 ) );
578+
else if ( a0 < 0 && a0 >= -count )
579+
sipCpp->deleteVertex( QgsVertexId( -1, -1, count + a0 ) );
488580
else
489581
{
490582
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );

‎src/core/geometry/qgslinestring.h

Lines changed: 229 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -94,54 +94,97 @@ class CORE_EXPORT QgsLineString: public QgsCurve
9494

9595
bool equals( const QgsCurve &other ) const override;
9696

97+
#ifndef SIP_RUN
98+
9799
/**
98100
* Returns the specified point from inside the line string.
99101
* \param i index of point, starting at 0 for the first point
100102
*/
101-
#ifndef SIP_RUN
102103
QgsPoint pointN( int i ) const;
103104
#else
105+
106+
/**
107+
* Returns the point at the specified index. An IndexError will be raised if no point with the specified index exists.
108+
*
109+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
110+
* corresponds to the last point in the line.
111+
*/
104112
SIP_PYOBJECT pointN( int i ) const;
105113
% MethodCode
106-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
114+
const int count = sipCpp->numPoints();
115+
if ( a0 < -count || a0 >= count )
107116
{
108117
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
109118
sipIsErr = 1;
110119
}
111120
else
112121
{
113-
std::unique_ptr< QgsPoint > p = qgis::make_unique< QgsPoint >( sipCpp->pointN( a0 ) );
122+
std::unique_ptr< QgsPoint > p;
123+
if ( a0 >= 0 )
124+
p = qgis::make_unique< QgsPoint >( sipCpp->pointN( a0 ) );
125+
else // negative index, count backwards from end
126+
p = qgis::make_unique< QgsPoint >( sipCpp->pointN( count + a0 ) );
114127
sipRes = sipConvertFromType( p.release(), sipType_QgsPoint, Py_None );
115128
}
116129
% End
117130
#endif
118131

132+
#ifndef SIP_RUN
133+
double xAt( int index ) const override;
134+
#else
135+
136+
/**
137+
* Returns the x-coordinate of the specified node in the line string.
138+
*
139+
* An IndexError will be raised if no point with the specified index exists.
140+
*
141+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
142+
* corresponds to the last point in the line.
143+
*/
119144
double xAt( int index ) const override;
120-
#ifdef SIP_RUN
121145
% MethodCode
122-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
146+
const int count = sipCpp->numPoints();
147+
if ( a0 < -count || a0 >= count )
123148
{
124149
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
125150
sipIsErr = 1;
126151
}
127152
else
128153
{
129-
return PyFloat_FromDouble( sipCpp->xAt( a0 ) );
154+
if ( a0 >= 0 )
155+
return PyFloat_FromDouble( sipCpp->xAt( a0 ) );
156+
else
157+
return PyFloat_FromDouble( sipCpp->xAt( count + a0 ) );
130158
}
131159
% End
132160
#endif
133161

162+
#ifndef SIP_RUN
163+
double yAt( int index ) const override;
164+
#else
165+
166+
/**
167+
* Returns the y-coordinate of the specified node in the line string.
168+
*
169+
* An IndexError will be raised if no point with the specified index exists.
170+
*
171+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
172+
* corresponds to the last point in the line.
173+
*/
134174
double yAt( int index ) const override;
135-
#ifdef SIP_RUN
136175
% MethodCode
137-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
176+
const int count = sipCpp->numPoints();
177+
if ( a0 < -count || a0 >= count )
138178
{
139179
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
140180
sipIsErr = 1;
141181
}
142182
else
143183
{
144-
return PyFloat_FromDouble( sipCpp->yAt( a0 ) );
184+
if ( a0 >= 0 )
185+
return PyFloat_FromDouble( sipCpp->yAt( a0 ) );
186+
else
187+
return PyFloat_FromDouble( sipCpp->yAt( count + a0 ) );
145188
}
146189
% End
147190
#endif
@@ -200,6 +243,8 @@ class CORE_EXPORT QgsLineString: public QgsCurve
200243
return mM.constData();
201244
}
202245

246+
#ifndef SIP_RUN
247+
203248
/**
204249
* Returns the z-coordinate of the specified node in the line string.
205250
* \param index index of node, where the first node in the line is 0
@@ -214,20 +259,38 @@ class CORE_EXPORT QgsLineString: public QgsCurve
214259
else
215260
return std::numeric_limits<double>::quiet_NaN();
216261
}
217-
#ifdef SIP_RUN
262+
#else
263+
264+
/**
265+
* Returns the z-coordinate of the specified node in the line string.
266+
*
267+
* An IndexError will be raised if no point with the specified index exists.
268+
*
269+
* If the LineString does not have a z-dimension then ``nan`` will be returned.
270+
*
271+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
272+
* corresponds to the last point in the line.
273+
*/
274+
double zAt( int index ) const;
218275
% MethodCode
219-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
276+
const int count = sipCpp->numPoints();
277+
if ( a0 < -count || a0 >= count )
220278
{
221279
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
222280
sipIsErr = 1;
223281
}
224282
else
225283
{
226-
return PyFloat_FromDouble( sipCpp->zAt( a0 ) );
284+
if ( a0 >= 0 )
285+
return PyFloat_FromDouble( sipCpp->zAt( a0 ) );
286+
else
287+
return PyFloat_FromDouble( sipCpp->zAt( count + a0 ) );
227288
}
228289
% End
229290
#endif
230291

292+
#ifndef SIP_RUN
293+
231294
/**
232295
* Returns the m value of the specified node in the line string.
233296
* \param index index of node, where the first node in the line is 0
@@ -242,20 +305,38 @@ class CORE_EXPORT QgsLineString: public QgsCurve
242305
else
243306
return std::numeric_limits<double>::quiet_NaN();
244307
}
245-
#ifdef SIP_RUN
308+
#else
309+
310+
/**
311+
* Returns the m-coordinate of the specified node in the line string.
312+
*
313+
* An IndexError will be raised if no point with the specified index exists.
314+
*
315+
* If the LineString does not have a m-dimension then ``nan`` will be returned.
316+
*
317+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
318+
* corresponds to the last point in the line.
319+
*/
320+
double mAt( int index ) const;
246321
% MethodCode
247-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
322+
const int count = sipCpp->numPoints();
323+
if ( a0 < -count || a0 >= count )
248324
{
249325
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
250326
sipIsErr = 1;
251327
}
252328
else
253329
{
254-
return PyFloat_FromDouble( sipCpp->mAt( a0 ) );
330+
if ( a0 >= 0 )
331+
return PyFloat_FromDouble( sipCpp->mAt( a0 ) );
332+
else
333+
return PyFloat_FromDouble( sipCpp->mAt( count + a0 ) );
255334
}
256335
% End
257336
#endif
258337

338+
#ifndef SIP_RUN
339+
259340
/**
260341
* Sets the x-coordinate of the specified node in the line string.
261342
* \param index index of node, where the first node in the line is 0. Corresponding
@@ -264,20 +345,39 @@ class CORE_EXPORT QgsLineString: public QgsCurve
264345
* \see xAt()
265346
*/
266347
void setXAt( int index, double x );
267-
#ifdef SIP_RUN
348+
#else
349+
350+
/**
351+
* Sets the x-coordinate of the specified node in the line string.
352+
* The corresponding node must already exist in line string.
353+
*
354+
* An IndexError will be raised if no point with the specified index exists.
355+
*
356+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
357+
* corresponds to the last point in the line.
358+
*
359+
* \see xAt()
360+
*/
361+
void setXAt( int index, double x );
268362
% MethodCode
269-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
363+
const int count = sipCpp->numPoints();
364+
if ( a0 < -count || a0 >= count )
270365
{
271366
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
272367
sipIsErr = 1;
273368
}
274369
else
275370
{
276-
sipCpp->setXAt( a0, a1 );
371+
if ( a0 >= 0 )
372+
sipCpp->setXAt( a0, a1 );
373+
else
374+
sipCpp->setXAt( count + a0, a1 );
277375
}
278376
% End
279377
#endif
280378

379+
#ifndef SIP_RUN
380+
281381
/**
282382
* Sets the y-coordinate of the specified node in the line string.
283383
* \param index index of node, where the first node in the line is 0. Corresponding
@@ -286,20 +386,39 @@ class CORE_EXPORT QgsLineString: public QgsCurve
286386
* \see yAt()
287387
*/
288388
void setYAt( int index, double y );
289-
#ifdef SIP_RUN
389+
#else
390+
391+
/**
392+
* Sets the y-coordinate of the specified node in the line string.
393+
* The corresponding node must already exist in line string.
394+
*
395+
* An IndexError will be raised if no point with the specified index exists.
396+
*
397+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
398+
* corresponds to the last point in the line.
399+
*
400+
* \see yAt()
401+
*/
402+
void setYAt( int index, double y );
290403
% MethodCode
291-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
404+
const int count = sipCpp->numPoints();
405+
if ( a0 < -count || a0 >= count )
292406
{
293407
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
294408
sipIsErr = 1;
295409
}
296410
else
297411
{
298-
sipCpp->setYAt( a0, a1 );
412+
if ( a0 >= 0 )
413+
sipCpp->setYAt( a0, a1 );
414+
else
415+
sipCpp->setYAt( count + a0, a1 );
299416
}
300417
% End
301418
#endif
302419

420+
#ifndef SIP_RUN
421+
303422
/**
304423
* Sets the z-coordinate of the specified node in the line string.
305424
* \param index index of node, where the first node in the line is 0. Corresponding
@@ -312,20 +431,39 @@ class CORE_EXPORT QgsLineString: public QgsCurve
312431
if ( index >= 0 && index < mZ.size() )
313432
mZ[ index ] = z;
314433
}
315-
#ifdef SIP_RUN
434+
#else
435+
436+
/**
437+
* Sets the z-coordinate of the specified node in the line string.
438+
* The corresponding node must already exist in line string and the line string must have z-dimension.
439+
*
440+
* An IndexError will be raised if no point with the specified index exists.
441+
*
442+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
443+
* corresponds to the last point in the line.
444+
*
445+
* \see zAt()
446+
*/
447+
void setZAt( int index, double z );
316448
% MethodCode
317-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
449+
const int count = sipCpp->numPoints();
450+
if ( a0 < -count || a0 >= count )
318451
{
319452
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
320453
sipIsErr = 1;
321454
}
322455
else
323456
{
324-
sipCpp->setZAt( a0, a1 );
457+
if ( a0 >= 0 )
458+
sipCpp->setZAt( a0, a1 );
459+
else
460+
sipCpp->setZAt( count + a0, a1 );
325461
}
326462
% End
327463
#endif
328464

465+
#ifndef SIP_RUN
466+
329467
/**
330468
* Sets the m value of the specified node in the line string.
331469
* \param index index of node, where the first node in the line is 0. Corresponding
@@ -338,16 +476,33 @@ class CORE_EXPORT QgsLineString: public QgsCurve
338476
if ( index >= 0 && index < mM.size() )
339477
mM[ index ] = m;
340478
}
341-
#ifdef SIP_RUN
479+
#else
480+
481+
/**
482+
* Sets the m-coordinate of the specified node in the line string.
483+
* The corresponding node must already exist in line string and the line string must have m-dimension.
484+
*
485+
* An IndexError will be raised if no point with the specified index exists.
486+
*
487+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
488+
* corresponds to the last point in the line.
489+
*
490+
* \see mAt()
491+
*/
492+
void setMAt( int index, double m );
342493
% MethodCode
343-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
494+
const int count = sipCpp->numPoints();
495+
if ( a0 < -count || a0 >= count )
344496
{
345497
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
346498
sipIsErr = 1;
347499
}
348500
else
349501
{
350-
sipCpp->setMAt( a0, a1 );
502+
if ( a0 >= 0 )
503+
sipCpp->setMAt( a0, a1 );
504+
else
505+
sipCpp->setMAt( count + a0, a1 );
351506
}
352507
% End
353508
#endif
@@ -484,39 +639,53 @@ class CORE_EXPORT QgsLineString: public QgsCurve
484639
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
485640
% End
486641

642+
/**
643+
* Returns the point at the specified ``index``. An IndexError will be raised if no point with the specified ``index`` exists.
644+
*
645+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
646+
* corresponds to the last point in the line.
647+
*
648+
* \since QGIS 3.6
649+
*/
487650
SIP_PYOBJECT __getitem__( int index );
488-
% Docstring
489-
Returns the point at the specified ``index``. An IndexError will be raised if no point with the specified ``index`` exists.
490-
491-
.. versionadded:: 3.6
492-
% End
493651
% MethodCode
494-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
495-
{
496-
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
497-
sipIsErr = 1;
498-
}
652+
const int count = sipCpp->numPoints();
653+
if ( a0 < -count || a0 >= count )
654+
{
655+
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
656+
sipIsErr = 1;
657+
}
658+
else
659+
{
660+
std::unique_ptr< QgsPoint > p;
661+
if ( a0 >= 0 )
662+
p = qgis::make_unique< QgsPoint >( sipCpp->pointN( a0 ) );
499663
else
500-
{
501-
std::unique_ptr< QgsPoint > p = qgis::make_unique< QgsPoint >( sipCpp->pointN( a0 ) );
502-
sipRes = sipConvertFromType( p.release(), sipType_QgsPoint, Py_None );
503-
}
664+
p = qgis::make_unique< QgsPoint >( sipCpp->pointN( count + a0 ) );
665+
sipRes = sipConvertFromType( p.release(), sipType_QgsPoint, Py_None );
666+
}
504667
% End
505668

669+
/**
670+
* Sets the point at the specified ``index``. A point at the ``index`` must already exist or an IndexError will be raised.
671+
*
672+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
673+
* corresponds to the last point in the line.
674+
*
675+
* \since QGIS 3.6
676+
*/
506677
void __setitem__( int index, const QgsPoint &point );
507-
% Docstring
508-
Sets the point at the specified ``index``. A point at the ``index`` must already exist or an IndexError will be raised.
509-
510-
.. versionadded:: 3.6
511-
% End
512678
% MethodCode
513-
if ( a0 < 0 || a0 >= sipCpp->numPoints() )
679+
const int count = sipCpp->numPoints();
680+
if ( a0 < -count || a0 >= count )
514681
{
515682
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
516683
sipIsErr = 1;
517684
}
518685
else
519686
{
687+
if ( a0 < 0 )
688+
a0 = count + a0;
520689
sipCpp->setXAt( a0, a1->x() );
521690
sipCpp->setYAt( a0, a1->y() );
522691
if ( sipCpp->isMeasure() )
@@ -526,15 +695,22 @@ class CORE_EXPORT QgsLineString: public QgsCurve
526695
}
527696
% End
528697

529-
void __delitem__( int index );
530-
% Docstring
531-
Deletes the vertex at the specified ``index``. A point at the ``index`` must already exist or an IndexError will be raised.
532698

533-
.. versionadded:: 3.6
534-
% End
699+
/**
700+
* Deletes the vertex at the specified ``index``. A point at the ``index`` must already exist or an IndexError will be raised.
701+
*
702+
* Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1
703+
* corresponds to the last point in the line.
704+
*
705+
* \since QGIS 3.6
706+
*/
707+
void __delitem__( int index );
535708
% MethodCode
536-
if ( a0 >= 0 && a0 < sipCpp->numPoints() )
709+
const int count = sipCpp->numPoints();
710+
if ( a0 >= 0 && a0 < count )
537711
sipCpp->deleteVertex( QgsVertexId( -1, -1, a0 ) );
712+
else if ( a0 < 0 && a0 >= -count )
713+
sipCpp->deleteVertex( QgsVertexId( -1, -1, count + a0 ) );
538714
else
539715
{
540716
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );

‎tests/src/python/test_qgsgeometry.py

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -268,103 +268,135 @@ def testLineStringPythonAdditions(self):
268268

269269
# pointN
270270
with self.assertRaises(IndexError):
271-
ls.pointN(-1)
271+
ls.pointN(-3)
272272
with self.assertRaises(IndexError):
273273
ls.pointN(2)
274274
self.assertEqual(ls.pointN(0), QgsPoint(1, 2))
275275
self.assertEqual(ls.pointN(1), QgsPoint(11, 12))
276+
self.assertEqual(ls.pointN(-2), QgsPoint(1, 2))
277+
self.assertEqual(ls.pointN(-1), QgsPoint(11, 12))
276278

277279
# xAt
278280
with self.assertRaises(IndexError):
279-
ls.xAt(-1)
281+
ls.xAt(-3)
280282
with self.assertRaises(IndexError):
281283
ls.xAt(2)
282284
self.assertEqual(ls.xAt(0), 1)
283285
self.assertEqual(ls.xAt(1), 11)
286+
self.assertEqual(ls.xAt(-2), 1)
287+
self.assertEqual(ls.xAt(-1), 11)
284288

285289
# yAt
286290
with self.assertRaises(IndexError):
287-
ls.yAt(-1)
291+
ls.yAt(-3)
288292
with self.assertRaises(IndexError):
289293
ls.yAt(2)
290294
self.assertEqual(ls.yAt(0), 2)
291295
self.assertEqual(ls.yAt(1), 12)
296+
self.assertEqual(ls.yAt(-2), 2)
297+
self.assertEqual(ls.yAt(-1), 12)
292298

293299
# zAt
294300
with self.assertRaises(IndexError):
295-
ls.zAt(-1)
301+
ls.zAt(-3)
296302
with self.assertRaises(IndexError):
297303
ls.zAt(2)
298304

299305
# mAt
300306
with self.assertRaises(IndexError):
301-
ls.mAt(-1)
307+
ls.mAt(-3)
302308
with self.assertRaises(IndexError):
303309
ls.mAt(2)
304310

305311
ls = QgsLineString([QgsPoint(1, 2, 3, 4), QgsPoint(11, 12, 13, 14)])
306312
self.assertEqual(ls.zAt(0), 3)
307313
self.assertEqual(ls.zAt(1), 13)
314+
self.assertEqual(ls.zAt(-2), 3)
315+
self.assertEqual(ls.zAt(-1), 13)
308316
self.assertEqual(ls.mAt(0), 4)
309317
self.assertEqual(ls.mAt(1), 14)
318+
self.assertEqual(ls.mAt(-2), 4)
319+
self.assertEqual(ls.mAt(-1), 14)
310320

311321
# setXAt
312322
with self.assertRaises(IndexError):
313-
ls.setXAt(-1, 55)
323+
ls.setXAt(-3, 55)
314324
with self.assertRaises(IndexError):
315325
ls.setXAt(2, 55)
316326
ls.setXAt(0, 5)
317327
ls.setXAt(1, 15)
318328
self.assertEqual(ls.xAt(0), 5)
319329
self.assertEqual(ls.xAt(1), 15)
330+
ls.setXAt(-2, 25)
331+
ls.setXAt(-1, 26)
332+
self.assertEqual(ls.xAt(0), 25)
333+
self.assertEqual(ls.xAt(1), 26)
320334

321335
# setYAt
322336
with self.assertRaises(IndexError):
323-
ls.setYAt(-1, 66)
337+
ls.setYAt(-3, 66)
324338
with self.assertRaises(IndexError):
325339
ls.setYAt(2, 66)
326340
ls.setYAt(0, 6)
327341
ls.setYAt(1, 16)
328342
self.assertEqual(ls.yAt(0), 6)
329343
self.assertEqual(ls.yAt(1), 16)
344+
ls.setYAt(-2, 16)
345+
ls.setYAt(-1, 22)
346+
self.assertEqual(ls.yAt(0), 16)
347+
self.assertEqual(ls.yAt(1), 22)
330348

331349
# setZAt
332350
with self.assertRaises(IndexError):
333-
ls.setZAt(-1, 77)
351+
ls.setZAt(-3, 77)
334352
with self.assertRaises(IndexError):
335353
ls.setZAt(2, 77)
336354
ls.setZAt(0, 7)
337355
ls.setZAt(1, 17)
338356
self.assertEqual(ls.zAt(0), 7)
339357
self.assertEqual(ls.zAt(1), 17)
358+
ls.setZAt(-2, 37)
359+
ls.setZAt(-1, 47)
360+
self.assertEqual(ls.zAt(0), 37)
361+
self.assertEqual(ls.zAt(1), 47)
340362

341363
# setMAt
342364
with self.assertRaises(IndexError):
343-
ls.setMAt(-1, 88)
365+
ls.setMAt(-3, 88)
344366
with self.assertRaises(IndexError):
345367
ls.setMAt(2, 88)
346368
ls.setMAt(0, 8)
347369
ls.setMAt(1, 18)
348370
self.assertEqual(ls.mAt(0), 8)
349371
self.assertEqual(ls.mAt(1), 18)
372+
ls.setMAt(-2, 58)
373+
ls.setMAt(-1, 68)
374+
self.assertEqual(ls.mAt(0), 58)
375+
self.assertEqual(ls.mAt(1), 68)
350376

351377
# get item
352378
with self.assertRaises(IndexError):
353-
ls[-1]
379+
ls[-3]
354380
with self.assertRaises(IndexError):
355381
ls[2]
356-
self.assertEqual(ls[0], QgsPoint(5, 6, 7, 8))
357-
self.assertEqual(ls[1], QgsPoint(15, 16, 17, 18))
382+
self.assertEqual(ls[0], QgsPoint(25, 16, 37, 58))
383+
self.assertEqual(ls[1], QgsPoint(26, 22, 47, 68))
384+
self.assertEqual(ls[-2], QgsPoint(25, 16, 37, 58))
385+
self.assertEqual(ls[-1], QgsPoint(26, 22, 47, 68))
358386

359387
# set item
360388
with self.assertRaises(IndexError):
361-
ls[-1] = QgsPoint(33, 34)
389+
ls[-3] = QgsPoint(33, 34)
362390
with self.assertRaises(IndexError):
363391
ls[2] = QgsPoint(33, 34)
364392
ls[0] = QgsPoint(33, 34, 35, 36)
365393
ls[1] = QgsPoint(43, 44, 45, 46)
366394
self.assertEqual(ls[0], QgsPoint(33, 34, 35, 36))
367395
self.assertEqual(ls[1], QgsPoint(43, 44, 45, 46))
396+
ls[-2] = QgsPoint(133, 134, 135, 136)
397+
ls[-1] = QgsPoint(143, 144, 145, 146)
398+
self.assertEqual(ls[0], QgsPoint(133, 134, 135, 136))
399+
self.assertEqual(ls[1], QgsPoint(143, 144, 145, 146))
368400

369401
# set item, z/m handling
370402
ls[0] = QgsPoint(66, 67)
@@ -383,7 +415,7 @@ def testLineStringPythonAdditions(self):
383415
# del item
384416
ls = QgsLineString([QgsPoint(1, 2), QgsPoint(11, 12), QgsPoint(33, 34)])
385417
with self.assertRaises(IndexError):
386-
del ls[-1]
418+
del ls[-4]
387419
with self.assertRaises(IndexError):
388420
del ls[3]
389421
del ls[1]
@@ -393,6 +425,14 @@ def testLineStringPythonAdditions(self):
393425
with self.assertRaises(IndexError):
394426
del ls[2]
395427

428+
ls = QgsLineString([QgsPoint(1, 2), QgsPoint(11, 12), QgsPoint(33, 34)])
429+
del ls[-3]
430+
self.assertEqual(len(ls), 2)
431+
self.assertEqual(ls[0], QgsPoint(11, 12))
432+
self.assertEqual(ls[1], QgsPoint(33, 34))
433+
with self.assertRaises(IndexError):
434+
del ls[-3]
435+
396436
def testReferenceGeometry(self):
397437
""" Test parsing a whole range of valid reference wkt formats and variants, and checking
398438
expected values such as length, area, centroids, bounding boxes, etc of the resultant geometry.

0 commit comments

Comments
 (0)
Please sign in to comment.