Skip to content

Commit

Permalink
[feature][expressions] Add expressions for creating triangular/square…
Browse files Browse the repository at this point in the history
…/curved waves

along a geometry

The new expression functions are:
- triangular_wave: Constructs triangular waves along the boundary of a geometry.
- square_wave: Constructs square/rectangular waves along the boundary of a geometry
- wave: Constructs rounded (sine-like) waves along the boundary of a geometry
- triangular_wave_randomized: Constructs randomized triangular waves along the boundary of a geometry
- square_wave_randomized: Constructs randomized square/rectangular waves along the boundary of a geometry.
- wave_randomized: Constructs randomized curved (sine-like) waves along the boundary of a geometry
  • Loading branch information
nyalldawson committed Oct 22, 2021
1 parent 430c5b2 commit 9496367
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 0 deletions.
15 changes: 15 additions & 0 deletions resources/function_help/json/square_wave
@@ -0,0 +1,15 @@
{
"name": "square_wave",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Constructs square/rectangular waves along the boundary of a geometry.",
"arguments": [
{"arg":"geometry","description":"a geometry"},
{"arg":"wavelength","description":"wavelength of square waveform"},
{"arg":"amplitude","description":"amplitude of square waveform"},
{"arg":"strict","optional": true, "default": false, "description":"By default the wavelength argument is treated as a \"maximum wavelength\", where the actual wavelength will be dynamically adjusted so that an exact number of square waves are created along the boundaries of the geometry. If the strict argument is set to true then the wavelength will be used exactly and an incomplete pattern may be used for the final waveform."}
],
"examples": [
{ "expression":"square_wave(geom_from_wkt('LineString(0 0, 10 0)'), 3, 1)", "returns":"Square waves with wavelength 3 and amplitude 1 along the linestring"}
]
}
17 changes: 17 additions & 0 deletions resources/function_help/json/square_wave_randomized
@@ -0,0 +1,17 @@
{
"name": "square_wave_randomized",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Constructs randomized square/rectangular waves along the boundary of a geometry.",
"arguments": [
{"arg":"geometry","description":"a geometry"},
{"arg":"min_wavelength","description":"minimum wavelength of waves"},
{"arg":"max_wavelength","description":"maximum wavelength of waves"},
{"arg":"min_amplitude","description":"minimum amplitude of waves"},
{"arg":"max_amplitude","description":"maximum amplitude of waves"},
{"arg":"seed","optional": true, "default": "0", "description":"specifies a random seed for generating waves. If the seed is 0, then a completely random set of waves will be generated."}
],
"examples": [
{ "expression":"square_wave_randomized(geom_from_wkt('LineString(0 0, 10 0)'), 2, 3, 0.1, 0.2)", "returns":"Randomly sized square waves with wavelengths between 2 and 3 and amplitudes between 0.1 and 0.2 along the linestring"}
]
}
15 changes: 15 additions & 0 deletions resources/function_help/json/triangular_wave
@@ -0,0 +1,15 @@
{
"name": "triangular_wave",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Constructs triangular waves along the boundary of a geometry.",
"arguments": [
{"arg":"geometry","description":"a geometry"},
{"arg":"wavelength","description":"wavelength of triangular waveform"},
{"arg":"amplitude","description":"amplitude of triangular waveform"},
{"arg":"strict","optional": true, "default": false, "description":"By default the wavelength argument is treated as a \"maximum wavelength\", where the actual wavelength will be dynamically adjusted so that an exact number of triangular waves are created along the boundaries of the geometry. If the strict argument is set to true then the wavelength will be used exactly and an incomplete pattern may be used for the final waveform."}
],
"examples": [
{ "expression":"triangular_wave(geom_from_wkt('LineString(0 0, 10 0)'), 3, 1)", "returns":"Triangular waves with wavelength 3 and amplitude 1 along the linestring"}
]
}
17 changes: 17 additions & 0 deletions resources/function_help/json/triangular_wave_randomized
@@ -0,0 +1,17 @@
{
"name": "triangular_wave_randomized",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Constructs randomized triangular waves along the boundary of a geometry.",
"arguments": [
{"arg":"geometry","description":"a geometry"},
{"arg":"min_wavelength","description":"minimum wavelength of waves"},
{"arg":"max_wavelength","description":"maximum wavelength of waves"},
{"arg":"min_amplitude","description":"minimum amplitude of waves"},
{"arg":"max_amplitude","description":"maximum amplitude of waves"},
{"arg":"seed","optional": true, "default": "0", "description":"specifies a random seed for generating waves. If the seed is 0, then a completely random set of waves will be generated."}
],
"examples": [
{ "expression":"triangular_wave_randomized(geom_from_wkt('LineString(0 0, 10 0)'), 2, 3, 0.1, 0.2)", "returns":"Randomly sized triangular waves with wavelengths between 2 and 3 and amplitudes between 0.1 and 0.2 along the linestring"}
]
}
16 changes: 16 additions & 0 deletions resources/function_help/json/wave
@@ -0,0 +1,16 @@
{
"name": "wave",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Constructs rounded (sine-like) waves along the boundary of a geometry.",
"arguments": [
{"arg":"geometry","description":"a geometry"},
{"arg":"wavelength","description":"wavelength of sine-like waveform"},
{"arg":"amplitude","description":"amplitude of sine-like waveform"},
{"arg":"strict","optional": true, "default": false, "description":"By default the wavelength argument is treated as a \"maximum wavelength\", where the actual wavelength will be dynamically adjusted so that an exact number of waves are created along the boundaries of the geometry. If the strict argument is set to true then the wavelength will be used exactly and an incomplete pattern may be used for the final waveform."}
],
"examples": [
{ "expression":"wave(geom_from_wkt('LineString(0 0, 10 0)'), 3, 1)", "returns":"Sine-like waves with wavelength 3 and amplitude 1 along the linestring"}

]
}
17 changes: 17 additions & 0 deletions resources/function_help/json/wave_randomized
@@ -0,0 +1,17 @@
{
"name": "wave_randomized",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Constructs randomized curved (sine-like) waves along the boundary of a geometry.",
"arguments": [
{"arg":"geometry","description":"a geometry"},
{"arg":"min_wavelength","description":"minimum wavelength of waves"},
{"arg":"max_wavelength","description":"maximum wavelength of waves"},
{"arg":"min_amplitude","description":"minimum amplitude of waves"},
{"arg":"max_amplitude","description":"maximum amplitude of waves"},
{"arg":"seed","optional": true, "default": "0", "description":"specifies a random seed for generating waves. If the seed is 0, then a completely random set of waves will be generated."}
],
"examples": [
{ "expression":"wave_randomized(geom_from_wkt('LineString(0 0, 10 0)'), 2, 3, 0.1, 0.2)", "returns":"Randomly sized curved waves with wavelengths between 2 and 3 and amplitudes between 0.1 and 0.2 along the linestring"}
]
}
165 changes: 165 additions & 0 deletions src/core/expression/qgsexpressionfunction.cpp
Expand Up @@ -2834,6 +2834,123 @@ static QVariant fcnSmooth( const QVariantList &values, const QgsExpressionContex
return smoothed;
}

static QVariant fcnTriangularWave( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );

if ( geom.isNull() )
return QVariant();

const double wavelength = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
const double amplitude = QgsExpressionUtils::getDoubleValue( values.at( 2 ), parent );
const bool strict = QgsExpressionUtils::getIntValue( values.at( 3 ), parent );

const QgsGeometry waved = geom.triangularWaves( wavelength, amplitude, strict );
if ( waved.isNull() )
return QVariant();

return waved;
}

static QVariant fcnTriangularWaveRandomized( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );

if ( geom.isNull() )
return QVariant();

const double minWavelength = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
const double maxWavelength = QgsExpressionUtils::getDoubleValue( values.at( 2 ), parent );
const double minAmplitude = QgsExpressionUtils::getDoubleValue( values.at( 3 ), parent );
const double maxAmplitude = QgsExpressionUtils::getDoubleValue( values.at( 4 ), parent );
const long long seed = QgsExpressionUtils::getIntValue( values.at( 5 ), parent );

const QgsGeometry waved = geom.triangularWavesRandomized( minWavelength, maxWavelength,
minAmplitude, maxAmplitude, seed );
if ( waved.isNull() )
return QVariant();

return waved;
}

static QVariant fcnSquareWave( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );

if ( geom.isNull() )
return QVariant();

const double wavelength = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
const double amplitude = QgsExpressionUtils::getDoubleValue( values.at( 2 ), parent );
const bool strict = QgsExpressionUtils::getIntValue( values.at( 3 ), parent );

const QgsGeometry waved = geom.squareWaves( wavelength, amplitude, strict );
if ( waved.isNull() )
return QVariant();

return waved;
}

static QVariant fcnSquareWaveRandomized( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );

if ( geom.isNull() )
return QVariant();

const double minWavelength = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
const double maxWavelength = QgsExpressionUtils::getDoubleValue( values.at( 2 ), parent );
const double minAmplitude = QgsExpressionUtils::getDoubleValue( values.at( 3 ), parent );
const double maxAmplitude = QgsExpressionUtils::getDoubleValue( values.at( 4 ), parent );
const long long seed = QgsExpressionUtils::getIntValue( values.at( 5 ), parent );

const QgsGeometry waved = geom.squareWavesRandomized( minWavelength, maxWavelength,
minAmplitude, maxAmplitude, seed );
if ( waved.isNull() )
return QVariant();

return waved;
}

static QVariant fcnRoundWave( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );

if ( geom.isNull() )
return QVariant();

const double wavelength = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
const double amplitude = QgsExpressionUtils::getDoubleValue( values.at( 2 ), parent );
const bool strict = QgsExpressionUtils::getIntValue( values.at( 3 ), parent );

const QgsGeometry waved = geom.roundWaves( wavelength, amplitude, strict );
if ( waved.isNull() )
return QVariant();

return waved;
}

static QVariant fcnRoundWaveRandomized( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );

if ( geom.isNull() )
return QVariant();

const double minWavelength = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
const double maxWavelength = QgsExpressionUtils::getDoubleValue( values.at( 2 ), parent );
const double minAmplitude = QgsExpressionUtils::getDoubleValue( values.at( 3 ), parent );
const double maxAmplitude = QgsExpressionUtils::getDoubleValue( values.at( 4 ), parent );
const long long seed = QgsExpressionUtils::getIntValue( values.at( 5 ), parent );

const QgsGeometry waved = geom.roundWavesRandomized( minWavelength, maxWavelength,
minAmplitude, maxAmplitude, seed );
if ( waved.isNull() )
return QVariant();

return waved;
}

static QVariant fcnCollectGeometries( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QVariantList list;
Expand Down Expand Up @@ -7093,6 +7210,54 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< QgsExpressionFunction::Parameter( QStringLiteral( "offset" ), true, 0.25 )
<< QgsExpressionFunction::Parameter( QStringLiteral( "min_length" ), true, -1 )
<< QgsExpressionFunction::Parameter( QStringLiteral( "max_angle" ), true, 180 ), fcnSmooth, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "triangular_wave" ),
{
QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "wavelength" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "amplitude" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "strict" ), true, false )
}, fcnTriangularWave, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "triangular_wave_randomized" ),
{
QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "min_wavelength" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "max_wavelength" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "min_amplitude" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "max_amplitude" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "seed" ), true, 0 )
}, fcnTriangularWaveRandomized, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "square_wave" ),
{
QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "wavelength" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "amplitude" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "strict" ), true, false )
}, fcnSquareWave, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "square_wave_randomized" ),
{
QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "min_wavelength" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "max_wavelength" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "min_amplitude" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "max_amplitude" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "seed" ), true, 0 )
}, fcnSquareWaveRandomized, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "wave" ),
{
QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "wavelength" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "amplitude" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "strict" ), true, false )
}, fcnRoundWave, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "wave_randomized" ),
{
QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "min_wavelength" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "max_wavelength" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "min_amplitude" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "max_amplitude" ) ),
QgsExpressionFunction::Parameter( QStringLiteral( "seed" ), true, 0 )
}, fcnRoundWaveRandomized, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "num_points" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ), fcnGeomNumPoints, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "num_interior_rings" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ), fcnGeomNumInteriorRings, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "num_rings" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ), fcnGeomNumRings, QStringLiteral( "GeometryGroup" ) )
Expand Down
18 changes: 18 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -1329,6 +1329,24 @@ class TestQgsExpression: public QObject
QTest::newRow( "affine_transform line XY" ) << "geom_to_wkt(affine_transform(geom_from_wkt('LINESTRING(1 0, 2 0)'), 0, 0, 90, 2, 1))" << false << QVariant( "LineString (0 2, 0 4)" );
QTest::newRow( "affine_transform polygon XYZ" ) << "geom_to_wkt(affine_transform(geom_from_wkt('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))'), 0, 0, -90, 0.5, 0.5))" << false << QVariant( "Polygon ((0 0, 0.5 0, 0.5 -0.5, 0 -0.5, 0 0))" );
QTest::newRow( "affine_transform point XY with translation on ZM" ) << "geom_to_wkt(affine_transform(geom_from_wkt('POINT(1 1)'), 0, 0, 0, 1, 1, 3, 4))" << false << QVariant( "PointZM (1 1 3 4)" );
QTest::newRow( "triangular_wave not geom" ) << "triangular_wave('g', 1, 2)" << true << QVariant();
QTest::newRow( "triangular_wave null" ) << "triangular_wave(NULL, 1, 2)" << false << QVariant();
QTest::newRow( "triangular_wave geometry" ) << "geom_to_wkt(triangular_wave(geom_from_wkt('LINESTRING(1 1, 10 1)'), 5, 1))" << false << QVariant( "LineString (1 1, 2.125 2, 4.375 0, 6.625 2, 8.875 0, 10 1)" );
QTest::newRow( "triangular_wave_randomized not geom" ) << "triangular_wave_randomized('g', 1, 2, 3, 4)" << true << QVariant();
QTest::newRow( "triangular_wave_randomized null" ) << "triangular_wave_randomized(NULL, 1, 2, 3, 4)" << false << QVariant();
QTest::newRow( "triangular_wave_randomized geometry" ) << "geom_to_wkt(triangular_wave_randomized(geom_from_wkt('LINESTRING(1 1, 10 1)'), 2, 3, 0.5, 1, 1), 1)" << false << QVariant( "LineString (1 1, 1.7 2, 2.8 0, 3.9 1.7, 5.1 0.2, 6.6 1.9, 7.7 0.2, 9 1.6, 10 1)" );
QTest::newRow( "square_wave not geom" ) << "square_wave('g', 1, 2)" << true << QVariant();
QTest::newRow( "square_wave null" ) << "square_wave(NULL, 1, 2)" << false << QVariant();
QTest::newRow( "square_wave geometry" ) << "geom_to_wkt(square_wave(geom_from_wkt('LINESTRING(1 1, 10 1)'), 5, 1))" << false << QVariant( "LineString (1 1, 1 2, 3.25 2, 3.25 0, 5.5 0, 5.5 2, 7.75 2, 7.75 0, 10 0, 10 1)" );
QTest::newRow( "square_wave_randomized not geom" ) << "square_wave_randomized('g', 1, 2, 3, 4)" << true << QVariant();
QTest::newRow( "square_wave_randomized null" ) << "square_wave_randomized(NULL, 1, 2, 3, 4)" << false << QVariant();
QTest::newRow( "square_wave_randomized geometry" ) << "geom_to_wkt(square_wave_randomized(geom_from_wkt('LINESTRING(1 1, 10 1)'), 2, 3, 0.5, 1, 1), 1)" << false << QVariant( "LineString (1 1, 1 2, 2.5 2, 2.5 0.4, 4 0.4, 4 1.6, 5.2 1.6, 5.2 0.3, 6.5 0.3, 6.5 2, 7.9 2, 7.9 0.3, 9.2 0.3, 9.2 1.7, 10 1.7, 10 1)" );
QTest::newRow( "wave not geom" ) << "wave('g', 1, 2)" << true << QVariant();
QTest::newRow( "wave null" ) << "wave(NULL, 1, 2)" << false << QVariant();
QTest::newRow( "wave geometry" ) << "left(geom_to_wkt(wave(geom_from_wkt('LINESTRING(1 1, 10 1)'), 5, 1), 1), 100)" << false << QVariant( "LineString (1 1, 1.1 0.9, 1.2 0.7, 1.3 0.6, 1.4 0.4, 1.6 0.3, 1.7 0.2, 1.8 0.1, 1.9 0.1, 2 0, 2.1 0," );
QTest::newRow( "wave_randomized not geom" ) << "wave_randomized('g', 1, 2, 3, 4)" << true << QVariant();
QTest::newRow( "wave_randomized null" ) << "wave_randomized(NULL, 1, 2, 3, 4)" << false << QVariant();
QTest::newRow( "wave_randomized geometry" ) << "left(geom_to_wkt(wave_randomized(geom_from_wkt('LINESTRING(1 1, 10 1)'), 2, 3, 0.5, 1, 1), 1), 100)" << false << QVariant( "LineString (1 1, 1.1 0.9, 1.1 0.7, 1.2 0.6, 1.3 0.4, 1.4 0.3, 1.4 0.2, 1.5 0.1, 1.6 0.1, 1.7 0, 1.7 " );
QTest::newRow( "is_multipart true" ) << "is_multipart(geom_from_wkt('MULTIPOINT ((0 0),(1 1),(2 2))'))" << false << QVariant( true );
QTest::newRow( "is_multipart false" ) << "is_multipart(geom_from_wkt('POINT (0 0)'))" << false << QVariant( false );
QTest::newRow( "is_multipart false empty geometry" ) << "is_multipart(geom_from_wkt('POINT EMPTY'))" << false << QVariant( false );
Expand Down

0 comments on commit 9496367

Please sign in to comment.