Skip to content

Commit

Permalink
[FEATURE][expression] Add a to_decimal() function to convert degree/m…
Browse files Browse the repository at this point in the history
…inute/second strings
  • Loading branch information
nirvn committed Jul 31, 2020
1 parent f7ca8b6 commit 07ab4b3
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 0 deletions.
12 changes: 12 additions & 0 deletions resources/function_help/json/to_decimal
@@ -0,0 +1,12 @@
{
"name": "to_decimal",
"type": "function",
"groups": ["Conversions"],
"description": "Converts a degree, minute, second coordinate to its decimal equivalent.",
"arguments": [
{"arg":"value","description":"A degree, minute, second string."}
],
"examples": [
{ "expression":"to_decimal('6°21\\'16.445')", "returns":"6.3545680555"}
]
}
11 changes: 11 additions & 0 deletions src/core/expression/qgsexpressionfunction.cpp
Expand Up @@ -17,6 +17,7 @@
#include <random>

#include "qgscoordinateformatter.h"
#include "qgscoordinateutils.h"
#include "qgsexpressionfunction.h"
#include "qgsexpressionutils.h"
#include "qgsexpressionnodeimpl.h"
Expand Down Expand Up @@ -2100,6 +2101,15 @@ static QVariant fcnToDegreeMinute( const QVariantList &values, const QgsExpressi
return floatToDegreeFormat( format, values, context, parent, node );
}

static QVariant fcnToDecimal( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
double value = 0.0;
bool ok = false;
value = QgsCoordinateUtils::dmsToDecimal( QgsExpressionUtils::getStringValue( values.at( 0 ), parent ), &ok );

return ok ? QVariant( value ) : QVariant();
}

static QVariant fcnToDegreeMinuteSecond( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node )
{
QgsCoordinateFormatter::Format format = QgsCoordinateFormatter::FormatDegreesMinutesSeconds;
Expand Down Expand Up @@ -5737,6 +5747,7 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< new QgsStaticExpressionFunction( QStringLiteral( "to_interval" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnToInterval, QStringList() << QStringLiteral( "Conversions" ) << QStringLiteral( "Date and Time" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "tointerval" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "to_dm" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "axis" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "precision" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "formatting" ), true ), fcnToDegreeMinute, QStringLiteral( "Conversions" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "todm" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "to_dms" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "axis" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "precision" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "formatting" ), true ), fcnToDegreeMinuteSecond, QStringLiteral( "Conversions" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "todms" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "to_decimal" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnToDecimal, QStringLiteral( "Conversions" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "todecimal" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "coalesce" ), -1, fcnCoalesce, QStringLiteral( "Conditionals" ), QString(), false, QSet<QString>(), false, QStringList(), true )
<< new QgsStaticExpressionFunction( QStringLiteral( "nullif" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value1" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "value2" ) ), fcnNullIf, QStringLiteral( "Conditionals" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "if" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "condition" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "result_when_true" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "result_when_false" ) ), fcnIf, QStringLiteral( "Conditionals" ), QString(), false, QSet<QString>(), true )
Expand Down
56 changes: 56 additions & 0 deletions src/core/qgscoordinateutils.cpp
Expand Up @@ -106,4 +106,60 @@ QString QgsCoordinateUtils::formatCoordinateForProject( QgsProject *project, con
}
}

double QgsCoordinateUtils::dmsToDecimal( const QString &string, bool *ok )
{
const QString negative( QStringLiteral( "swSW-" ) );
double value = 0.0;
bool okValue = false;

if ( ok )
{
*ok = false;
}
else
{
ok = &okValue;
}

QRegularExpression dms( "^\\s*(?:([-+nsew])\\s*)?(\\d{1,3})(?:[^0-9.]+([0-5]?\\d))?[^0-9.]+([0-5]?\\d(?:\\.\\d+)?)[^0-9.]*?([-+nsew])?\\s*$", QRegularExpression::CaseInsensitiveOption );
QRegularExpressionMatch match = dms.match( string.trimmed() );
if ( match.hasMatch() )
{
QString dms1 = match.captured( 2 );
QString dms2 = match.captured( 3 );
QString dms3 = match.captured( 4 );

double v = dms3.toDouble( ok );
if ( *ok == false )
return value;
// Allow for Degrees/minutes format as well as DMS
if ( !dms2.isEmpty() )
{
v = dms2.toInt( ok ) + v / 60.0;
if ( *ok == false )
return value;
}
v = dms1.toInt( ok ) + v / 60.0;
if ( *ok == false )
return value;

QString sign1 = match.captured( 1 );
QString sign2 = match.captured( 5 );

if ( sign1.isEmpty() )
{
value = !sign2.isEmpty() && negative.contains( sign2 ) ? -v : v;
}
else if ( sign2.isEmpty() )
{
value = !sign1.isEmpty() && negative.contains( sign1 ) ? -v : v;
}
else
{
*ok = false;
}
}
return value;
}

///@endcond
8 changes: 8 additions & 0 deletions src/core/qgscoordinateutils.h
Expand Up @@ -67,6 +67,14 @@ class CORE_EXPORT QgsCoordinateUtils
*/
Q_INVOKABLE static QString formatCoordinateForProject( QgsProject *project, const QgsPointXY &point, const QgsCoordinateReferenceSystem &destCrs, int precision );

/**
* Converts a degree minute second coordinate string to its decimal equivalent.
* \param string degree minute second string to convert
* \returns Double decimal value
* \since QGIS 3.16
*/
Q_INVOKABLE static double dmsToDecimal( const QString &string, bool *ok );

};


Expand Down
2 changes: 2 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -869,6 +869,8 @@ class TestQgsExpression: public QObject
QTest::newRow( "Y coordinate to degree minute second with suffix" ) << "to_dms(6.3545681,'y',2,'suffix')" << false << QVariant( "6°21′16.45″N" );
QTest::newRow( "Y coordinate to degree minute second without formatting" ) << "to_dms(6.3545681,'y',2,'')" << false << QVariant( "6°21′16.45″" );
QTest::newRow( "Y coordinate to degree minute second" ) << "to_dms(6.3545681,'y',2)" << false << QVariant( "6°21′16.45″" );
QTest::newRow( "degree minute second string to decimal" ) << "to_decimal('6°21′16.45″N')" << false << QVariant( 6.35456944444 );
QTest::newRow( "wrong degree minute second string to decimal" ) << "to_decimal('qgis')" << false << QVariant();

// geometry functions
QTest::newRow( "geom_to_wkb" ) << "geom_to_wkt(geom_from_wkb(geom_to_wkb(make_point(4,5))))" << false << QVariant( "Point (4 5)" );
Expand Down

0 comments on commit 07ab4b3

Please sign in to comment.