Add a numeric format for geographic coordinates

This commit is contained in:
Nyall Dawson 2022-04-21 10:47:16 +10:00
parent 799679cfe4
commit ee5cbe93a9
17 changed files with 2039 additions and 0 deletions

View File

@ -0,0 +1,8 @@
# The following has been generated automatically from src/core/numericformats/qgscoordinatenumericformat.h
# monkey patching scoped based enum
QgsGeographicCoordinateNumericFormat.AngleFormat.DegreesMinutesSeconds.__doc__ = "Degrees, minutes and seconds, eg 30 degrees 45'30"
QgsGeographicCoordinateNumericFormat.AngleFormat.DegreesMinutes.__doc__ = "Degrees and decimal minutes, eg 30 degrees 45.55'"
QgsGeographicCoordinateNumericFormat.AngleFormat.DecimalDegrees.__doc__ = "Decimal degrees, eg 30.7555 degrees"
QgsGeographicCoordinateNumericFormat.AngleFormat.__doc__ = 'Angle format options.\n\n' + '* ``DegreesMinutesSeconds``: ' + QgsGeographicCoordinateNumericFormat.AngleFormat.DegreesMinutesSeconds.__doc__ + '\n' + '* ``DegreesMinutes``: ' + QgsGeographicCoordinateNumericFormat.AngleFormat.DegreesMinutes.__doc__ + '\n' + '* ``DecimalDegrees``: ' + QgsGeographicCoordinateNumericFormat.AngleFormat.DecimalDegrees.__doc__
# --
QgsGeographicCoordinateNumericFormat.AngleFormat.baseClass = QgsGeographicCoordinateNumericFormat

View File

@ -0,0 +1,8 @@
# The following has been generated automatically from src/core/numericformats/qgsnumericformat.h
# monkey patching scoped based enum
QgsNumericFormatContext.Interpretation.Generic.__doc__ = "Generic"
QgsNumericFormatContext.Interpretation.Latitude.__doc__ = "Latitude values"
QgsNumericFormatContext.Interpretation.Longitude.__doc__ = "Longitude values"
QgsNumericFormatContext.Interpretation.__doc__ = 'Interpretation of numeric values.\n\n.. versionadded:: 3.26\n\n' + '* ``Generic``: ' + QgsNumericFormatContext.Interpretation.Generic.__doc__ + '\n' + '* ``Latitude``: ' + QgsNumericFormatContext.Interpretation.Latitude.__doc__ + '\n' + '* ``Longitude``: ' + QgsNumericFormatContext.Interpretation.Longitude.__doc__
# --
QgsNumericFormatContext.Interpretation.baseClass = QgsNumericFormatContext

View File

@ -0,0 +1,124 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/numericformats/qgscoordinatenumericformat.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsGeographicCoordinateNumericFormat : QgsBasicNumericFormat
{
%Docstring(signature="appended")
A numeric formatter which returns a text representation of a geographic coordinate (latitude or longitude).
.. versionadded:: 3.26
%End
%TypeHeaderCode
#include "qgscoordinatenumericformat.h"
%End
public:
static const QMetaObject staticMetaObject;
public:
enum class AngleFormat
{
DegreesMinutesSeconds,
DegreesMinutes,
DecimalDegrees,
};
QgsGeographicCoordinateNumericFormat();
%Docstring
Default constructor
%End
virtual QString id() const;
virtual QString visibleName() const;
virtual int sortKey();
virtual double suggestSampleValue() const;
virtual QString formatDouble( double value, const QgsNumericFormatContext &context ) const;
virtual QgsGeographicCoordinateNumericFormat *clone() const /Factory/;
virtual QgsNumericFormat *create( const QVariantMap &configuration, const QgsReadWriteContext &context ) const /Factory/;
virtual QVariantMap configuration( const QgsReadWriteContext &context ) const;
AngleFormat angleFormat() const;
%Docstring
Returns the angle format, which controls how bearing the angles are formatted
described in the returned strings.
.. seealso:: :py:func:`setAngleFormat`
%End
void setAngleFormat( AngleFormat format );
%Docstring
Sets the directional formatting option, which controls how bearing the angles are formatted
described in the returned strings.
.. seealso:: :py:func:`angleFormat`
%End
bool showLeadingZeros() const;
%Docstring
Returns ``True`` if leading zeros in the minutes or seconds values should be shown.
.. seealso:: :py:func:`setShowLeadingZeros`
%End
void setShowLeadingZeros( bool show );
%Docstring
Sets whether leading zeros in the minutes or seconds values should be shown.
.. seealso:: :py:func:`showLeadingZeros`
%End
bool showDegreeLeadingZeros() const;
%Docstring
Returns ``True`` if leading zeros for the degree values should be shown.
.. seealso:: :py:func:`setShowDegreeLeadingZeros`
%End
void setShowDegreeLeadingZeros( bool show );
%Docstring
Sets whether leading zeros for the degree values should be shown.
.. seealso:: :py:func:`showDegreeLeadingZeros`
%End
bool showDirectionalSuffix() const;
%Docstring
Returns ``True`` if directional suffixes (e.g. "N") should be included.
.. seealso:: :py:func:`setShowDirectionalSuffix`
%End
void setShowDirectionalSuffix( bool show );
%Docstring
Sets whether directional suffixes (e.g. "N") should be included.
.. seealso:: :py:func:`showDirectionalSuffix`
%End
virtual void setConfiguration( const QVariantMap &configuration, const QgsReadWriteContext &context );
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/numericformats/qgscoordinatenumericformat.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -21,6 +21,9 @@ A context for numeric formats
%TypeHeaderCode
#include "qgsnumericformat.h"
%End
public:
static const QMetaObject staticMetaObject;
public:
QgsNumericFormatContext();
@ -129,6 +132,32 @@ Sets the exponential ``character``.
.. seealso:: :py:func:`exponential`
%End
enum class Interpretation
{
Generic,
Latitude,
Longitude,
};
Interpretation interpretation() const;
%Docstring
Returns the interpretation of the numbers being converted.
.. seealso:: :py:func:`setInterpretation`
.. versionadded:: 3.26
%End
void setInterpretation( Interpretation interpretation );
%Docstring
Sets the ``interpretation`` of the numbers being converted.
.. seealso:: :py:func:`interpretation`
.. versionadded:: 3.26
%End
};
%ModuleHeaderCode
@ -138,6 +167,7 @@ Sets the exponential ``character``.
#include <qgsfallbacknumericformat.h>
#include <qgspercentagenumericformat.h>
#include <qgsscientificnumericformat.h>
#include <qgscoordinatenumericformat.h>
%End
class QgsNumericFormat
@ -158,6 +188,8 @@ This is an abstract base class and will always need to be subclassed.
%ConvertToSubClassCode
if ( dynamic_cast< QgsBearingNumericFormat * >( sipCpp ) )
sipType = sipType_QgsBearingNumericFormat;
else if ( dynamic_cast< QgsGeographicCoordinateNumericFormat * >( sipCpp ) )
sipType = sipType_QgsGeographicCoordinateNumericFormat;
else if ( dynamic_cast< QgsFallbackNumericFormat * >( sipCpp ) )
sipType = sipType_QgsFallbackNumericFormat;
else if ( dynamic_cast< QgsPercentageNumericFormat * >( sipCpp ) )

View File

@ -499,6 +499,7 @@
%Include auto_generated/network/qgshttpheaders.sip
%Include auto_generated/numericformats/qgsbasicnumericformat.sip
%Include auto_generated/numericformats/qgsbearingnumericformat.sip
%Include auto_generated/numericformats/qgscoordinatenumericformat.sip
%Include auto_generated/numericformats/qgscurrencynumericformat.sip
%Include auto_generated/numericformats/qgsfallbacknumericformat.sip
%Include auto_generated/numericformats/qgsfractionnumericformat.sip

View File

@ -141,6 +141,63 @@ Ownership of the returned object is transferred to the caller
class QgsGeographicCoordinateNumericFormatWidget : QgsNumericFormatWidget
{
%Docstring(signature="appended")
A widget which allow control over the properties of a :py:class:`QgsGeographicCoordinateNumericFormat`.
.. versionadded:: 3.26
%End
%TypeHeaderCode
#include "qgsnumericformatwidget.h"
%End
public:
QgsGeographicCoordinateNumericFormatWidget( const QgsNumericFormat *format, QWidget *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsGeographicCoordinateNumericFormatWidget, initially showing the specified ``format``.
%End
~QgsGeographicCoordinateNumericFormatWidget();
virtual void setFormat( QgsNumericFormat *format );
virtual QgsNumericFormat *format() /Factory/;
};
class QgsGeographicCoordinateNumericFormatDialog : QDialog
{
%Docstring(signature="appended")
A dialog which allow control over the properties of a :py:class:`QgsGeographicCoordinateNumericFormat`.
.. versionadded:: 3.26
%End
%TypeHeaderCode
#include "qgsnumericformatwidget.h"
%End
public:
QgsGeographicCoordinateNumericFormatDialog( const QgsNumericFormat *format, QWidget *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsGeographicCoordinateNumericFormatDialog, initially showing the specified ``format``.
%End
QgsGeographicCoordinateNumericFormat *format() /Factory/;
%Docstring
Returns the format defined by the current settings in the dialog.
Ownership of the returned object is transferred to the caller
%End
};
class QgsCurrencyNumericFormatWidget : QgsNumericFormatWidget

View File

@ -166,6 +166,7 @@ set(QGIS_CORE_SRCS
numericformats/qgsbasicnumericformat.cpp
numericformats/qgsbearingnumericformat.cpp
numericformats/qgscoordinatenumericformat.cpp
numericformats/qgscurrencynumericformat.cpp
numericformats/qgsfallbacknumericformat.cpp
numericformats/qgsfractionnumericformat.cpp
@ -1576,6 +1577,7 @@ set(QGIS_CORE_HDRS
numericformats/qgsbasicnumericformat.h
numericformats/qgsbearingnumericformat.h
numericformats/qgscoordinatenumericformat.h
numericformats/qgscurrencynumericformat.h
numericformats/qgsfallbacknumericformat.h
numericformats/qgsfractionnumericformat.h

View File

@ -0,0 +1,653 @@
/***************************************************************************
qgscoordinatenumericformat.cpp
--------------------------
begin : April 2022
copyright : (C) 2022 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgscoordinatenumericformat.h"
#include "qgis.h"
#include "qgscoordinateformatter.h"
#include <memory>
#include <iostream>
#include <locale>
#include <iomanip>
struct formatter : std::numpunct<wchar_t>
{
formatter( QChar thousands, bool showThousands, QChar decimal )
: mThousands( thousands.unicode() )
, mDecimal( decimal.unicode() )
, mShowThousands( showThousands )
{}
wchar_t do_decimal_point() const override { return mDecimal; }
wchar_t do_thousands_sep() const override { return mThousands; }
std::string do_grouping() const override { return mShowThousands ? "\3" : "\0"; }
wchar_t mThousands;
wchar_t mDecimal;
bool mShowThousands = true;
};
QgsGeographicCoordinateNumericFormat::QgsGeographicCoordinateNumericFormat()
{
}
QString QgsGeographicCoordinateNumericFormat::id() const
{
return QStringLiteral( "geographiccoordinate" );
}
QString QgsGeographicCoordinateNumericFormat::visibleName() const
{
return QObject::tr( "Geographic Coordinate" );
}
int QgsGeographicCoordinateNumericFormat::sortKey()
{
return QgsNumericFormat::sortKey();
}
double QgsGeographicCoordinateNumericFormat::suggestSampleValue() const
{
return 3.7555;
}
QString QgsGeographicCoordinateNumericFormat::formatDouble( double value, const QgsNumericFormatContext &context ) const
{
const QChar decimal = decimalSeparator().isNull() ? context.decimalSeparator() : decimalSeparator();
std::basic_stringstream<wchar_t> os;
os.imbue( std::locale( os.getloc(), new formatter( thousandsSeparator().isNull() ? context.thousandsSeparator() : thousandsSeparator(),
false,
decimal ) ) );
switch ( context.interpretation() )
{
case QgsNumericFormatContext::Interpretation::Latitude:
return formatLatitude( value, os, context );
case QgsNumericFormatContext::Interpretation::Generic:
case QgsNumericFormatContext::Interpretation::Longitude:
return formatLongitude( value, os, context );
}
BUILTIN_UNREACHABLE
}
QgsGeographicCoordinateNumericFormat *QgsGeographicCoordinateNumericFormat::clone() const
{
return new QgsGeographicCoordinateNumericFormat( *this );
}
QgsNumericFormat *QgsGeographicCoordinateNumericFormat::create( const QVariantMap &configuration, const QgsReadWriteContext &context ) const
{
std::unique_ptr< QgsGeographicCoordinateNumericFormat > res = std::make_unique< QgsGeographicCoordinateNumericFormat >();
res->setConfiguration( configuration, context );
res->mAngleFormat = qgsEnumKeyToValue( configuration.value( QStringLiteral( "angle_format" ) ).toString(), AngleFormat::DecimalDegrees );
res->mShowLeadingZeros = configuration.value( QStringLiteral( "show_leading_zeros" ), false ).toBool();
res->mShowLeadingDegreeZeros = configuration.value( QStringLiteral( "show_leading_degree_zeros" ), false ).toBool();
res->mUseSuffix = configuration.value( QStringLiteral( "show_suffix" ), false ).toBool();
return res.release();
}
QVariantMap QgsGeographicCoordinateNumericFormat::configuration( const QgsReadWriteContext &context ) const
{
QVariantMap res = QgsBasicNumericFormat::configuration( context );
res.insert( QStringLiteral( "angle_format" ), qgsEnumValueToKey( mAngleFormat ) );
res.insert( QStringLiteral( "show_leading_zeros" ), mShowLeadingZeros );
res.insert( QStringLiteral( "show_leading_degree_zeros" ), mShowLeadingDegreeZeros );
res.insert( QStringLiteral( "show_suffix" ), mUseSuffix );
return res;
}
QgsGeographicCoordinateNumericFormat::AngleFormat QgsGeographicCoordinateNumericFormat::angleFormat() const
{
return mAngleFormat;
}
void QgsGeographicCoordinateNumericFormat::setAngleFormat( QgsGeographicCoordinateNumericFormat::AngleFormat format )
{
mAngleFormat = format;
}
void QgsGeographicCoordinateNumericFormat::setConfiguration( const QVariantMap &configuration, const QgsReadWriteContext &context )
{
QgsBasicNumericFormat::setConfiguration( configuration, context );
mAngleFormat = qgsEnumKeyToValue( configuration.value( QStringLiteral( "angle_format" ) ).toString(), AngleFormat::DecimalDegrees );
mShowLeadingZeros = configuration.value( QStringLiteral( "show_leading_zeros" ), false ).toBool();
mShowLeadingDegreeZeros = configuration.value( QStringLiteral( "show_leading_degree_zeros" ), false ).toBool();
mUseSuffix = configuration.value( QStringLiteral( "show_suffix" ), false ).toBool();
}
bool QgsGeographicCoordinateNumericFormat::showLeadingZeros() const
{
return mShowLeadingZeros;
}
void QgsGeographicCoordinateNumericFormat::setShowLeadingZeros( bool newShowLeadingZeros )
{
mShowLeadingZeros = newShowLeadingZeros;
}
bool QgsGeographicCoordinateNumericFormat::showDegreeLeadingZeros() const
{
return mShowLeadingDegreeZeros;
}
void QgsGeographicCoordinateNumericFormat::setShowDegreeLeadingZeros( bool show )
{
mShowLeadingDegreeZeros = show;
}
bool QgsGeographicCoordinateNumericFormat::showDirectionalSuffix() const
{
return mUseSuffix;
}
void QgsGeographicCoordinateNumericFormat::setShowDirectionalSuffix( bool show )
{
mUseSuffix = show;
}
QString QgsGeographicCoordinateNumericFormat::formatLongitude( double value, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const
{
switch ( mAngleFormat )
{
case QgsGeographicCoordinateNumericFormat::AngleFormat::DegreesMinutesSeconds:
return formatLongitudeAsDegreesMinutesSeconds( value, ss, context );
case QgsGeographicCoordinateNumericFormat::AngleFormat::DegreesMinutes:
return formatLongitudeAsDegreesMinutes( value, ss, context );
case QgsGeographicCoordinateNumericFormat::AngleFormat::DecimalDegrees:
return formatLongitudeAsDegrees( value, ss, context );
}
BUILTIN_UNREACHABLE
}
QString QgsGeographicCoordinateNumericFormat::formatLatitude( double value, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const
{
switch ( mAngleFormat )
{
case QgsGeographicCoordinateNumericFormat::AngleFormat::DegreesMinutesSeconds:
return formatLatitudeAsDegreesMinutesSeconds( value, ss, context );
case QgsGeographicCoordinateNumericFormat::AngleFormat::DegreesMinutes:
return formatLatitudeAsDegreesMinutes( value, ss, context );
case QgsGeographicCoordinateNumericFormat::AngleFormat::DecimalDegrees:
return formatLatitudeAsDegrees( value, ss, context );
}
BUILTIN_UNREACHABLE
}
QString QgsGeographicCoordinateNumericFormat::formatLatitudeAsDegreesMinutesSeconds( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const
{
//first, limit latitude to -180 to 180 degree range
double wrappedY = std::fmod( val, 180.0 );
//next, wrap around latitudes > 90 or < -90 degrees, so that eg "110S" -> "70N"
if ( wrappedY > 90.0 )
{
wrappedY = wrappedY - 180.0;
}
else if ( wrappedY < -90.0 )
{
wrappedY = wrappedY + 180.0;
}
const int precisionMultiplier = std::pow( 10.0, numberDecimalPlaces() );
int degreesY = int( std::fabs( wrappedY ) );
const double floatMinutesY = ( std::fabs( wrappedY ) - degreesY ) * 60.0;
int intMinutesY = int( floatMinutesY );
double secondsY = ( floatMinutesY - intMinutesY ) * 60.0;
//make sure rounding to specified precision doesn't create seconds >= 60
if ( std::round( secondsY * precisionMultiplier ) >= 60 * precisionMultiplier )
{
secondsY = std::max( secondsY - 60, 0.0 );
intMinutesY++;
if ( intMinutesY >= 60 )
{
intMinutesY -= 60;
degreesY++;
}
}
QString hemisphere;
QString sign;
if ( mUseSuffix )
{
hemisphere = wrappedY < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
}
else
{
if ( wrappedY < 0 )
{
sign = context.negativeSign();
}
}
//check if coordinate is all zeros for the specified precision, and if so,
//remove the sign and hemisphere strings
if ( degreesY == 0 && intMinutesY == 0 && std::round( secondsY * precisionMultiplier ) == 0 )
{
sign = QString();
hemisphere.clear();
}
QString strMinutesY;
QString strSecondsY;
ss << std::fixed << std::setprecision( 0 );
ss << intMinutesY;
strMinutesY = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
ss << std::fixed << std::setprecision( numberDecimalPlaces() );
ss << secondsY;
strSecondsY = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
trimTrailingZeros( strSecondsY, context );
//pad with leading digits if required
if ( mShowLeadingZeros && intMinutesY < 10 )
strMinutesY = '0' + strMinutesY;
if ( mShowLeadingZeros && secondsY < 10 )
strSecondsY = '0' + strSecondsY;
ss << std::fixed << std::setprecision( 0 );
ss << degreesY;
QString degreesYStr = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
if ( mShowLeadingDegreeZeros )
degreesYStr = QString( QStringLiteral( "00" ) + degreesYStr ).right( 2 );
return sign + degreesYStr + QChar( 176 ) +
strMinutesY + QChar( 0x2032 ) +
strSecondsY + QChar( 0x2033 ) +
hemisphere;
}
QString QgsGeographicCoordinateNumericFormat::formatLongitudeAsDegreesMinutesSeconds( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const
{
//first, limit longitude to -360 to 360 degree range
double wrappedX = std::fmod( val, 360.0 );
//next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
if ( wrappedX > 180.0 )
{
wrappedX = wrappedX - 360.0;
}
else if ( wrappedX < -180.0 )
{
wrappedX = wrappedX + 360.0;
}
const int precisionMultiplier = std::pow( 10.0, numberDecimalPlaces() );
int degreesX = int( std::fabs( wrappedX ) );
const double floatMinutesX = ( std::fabs( wrappedX ) - degreesX ) * 60.0;
int intMinutesX = int( floatMinutesX );
double secondsX = ( floatMinutesX - intMinutesX ) * 60.0;
//make sure rounding to specified precision doesn't create seconds >= 60
if ( std::round( secondsX * precisionMultiplier ) >= 60 * precisionMultiplier )
{
secondsX = std::max( secondsX - 60, 0.0 );
intMinutesX++;
if ( intMinutesX >= 60 )
{
intMinutesX -= 60;
degreesX++;
}
}
QString hemisphere;
QString sign;
if ( mUseSuffix )
{
hemisphere = wrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
}
else
{
if ( wrappedX < 0 )
{
sign = context.negativeSign();
}
}
//check if coordinate is all zeros for the specified precision, and if so,
//remove the sign and hemisphere strings
if ( degreesX == 0 && intMinutesX == 0 && std::round( secondsX * precisionMultiplier ) == 0 )
{
sign.clear();
hemisphere.clear();
}
//also remove directional prefix from 180 degree longitudes
if ( degreesX == 180 && intMinutesX == 0 && std::round( secondsX * precisionMultiplier ) == 0 )
{
hemisphere.clear();
}
QString minutesX;
QString strSecondsX;
ss << std::fixed << std::setprecision( 0 );
ss << intMinutesX;
minutesX = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
ss << std::fixed << std::setprecision( numberDecimalPlaces() );
ss << secondsX;
strSecondsX = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
trimTrailingZeros( strSecondsX, context );
//pad with leading digits if required
if ( mShowLeadingZeros && intMinutesX < 10 )
minutesX = '0' + minutesX;
if ( mShowLeadingZeros && secondsX < 10 )
strSecondsX = '0' + strSecondsX;
ss << std::fixed << std::setprecision( 0 );
ss << degreesX;
QString degreesXStr = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
if ( mShowLeadingDegreeZeros )
degreesXStr = QString( QStringLiteral( "000" ) + degreesXStr ).right( 3 );
return sign + degreesXStr + QChar( 176 ) +
minutesX + QChar( 0x2032 ) +
strSecondsX + QChar( 0x2033 ) +
hemisphere;
}
QString QgsGeographicCoordinateNumericFormat::formatLatitudeAsDegreesMinutes( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const
{
//first, limit latitude to -180 to 180 degree range
double wrappedY = std::fmod( val, 180.0 );
//next, wrap around latitudes > 90 or < -90 degrees, so that eg "110S" -> "70N"
if ( wrappedY > 90.0 )
{
wrappedY = wrappedY - 180.0;
}
else if ( wrappedY < -90.0 )
{
wrappedY = wrappedY + 180.0;
}
int degreesY = int( std::fabs( wrappedY ) );
double floatMinutesY = ( std::fabs( wrappedY ) - degreesY ) * 60.0;
const int precisionMultiplier = std::pow( 10.0, numberDecimalPlaces() );
//make sure rounding to specified precision doesn't create minutes >= 60
if ( std::round( floatMinutesY * precisionMultiplier ) >= 60 * precisionMultiplier )
{
floatMinutesY = std::max( floatMinutesY - 60, 0.0 );
degreesY++;
}
QString hemisphere;
QString sign;
if ( mUseSuffix )
{
hemisphere = wrappedY < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
}
else
{
if ( wrappedY < 0 )
{
sign = context.negativeSign();
}
}
//check if coordinate is all zeros for the specified precision, and if so,
//remove the sign and hemisphere strings
if ( degreesY == 0 && std::round( floatMinutesY * precisionMultiplier ) == 0 )
{
sign.clear();
hemisphere.clear();
}
ss << std::fixed << std::setprecision( numberDecimalPlaces() );
ss << floatMinutesY;
QString strMinutesY = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
trimTrailingZeros( strMinutesY, context );
//pad with leading digits if required
if ( mShowLeadingZeros && floatMinutesY < 10 )
strMinutesY = '0' + strMinutesY;
ss << std::fixed << std::setprecision( 0 );
ss << degreesY;
QString degreesYStr = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
if ( mShowLeadingDegreeZeros )
degreesYStr = QString( QStringLiteral( "00" ) + degreesYStr ).right( 2 );
return sign + degreesYStr + QChar( 176 ) +
strMinutesY + QChar( 0x2032 ) +
hemisphere;
}
QString QgsGeographicCoordinateNumericFormat::formatLongitudeAsDegreesMinutes( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const
{
//first, limit longitude to -360 to 360 degree range
double wrappedX = std::fmod( val, 360.0 );
//next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
if ( wrappedX > 180.0 )
{
wrappedX = wrappedX - 360.0;
}
else if ( wrappedX < -180.0 )
{
wrappedX = wrappedX + 360.0;
}
int degreesX = int( std::fabs( wrappedX ) );
double floatMinutesX = ( std::fabs( wrappedX ) - degreesX ) * 60.0;
const int precisionMultiplier = std::pow( 10.0, numberDecimalPlaces() );
//make sure rounding to specified precision doesn't create minutes >= 60
if ( std::round( floatMinutesX * precisionMultiplier ) >= 60 * precisionMultiplier )
{
floatMinutesX = std::max( floatMinutesX - 60, 0.0 );
degreesX++;
}
QString hemisphere;
QString sign;
if ( mUseSuffix )
{
hemisphere = wrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
}
else
{
if ( wrappedX < 0 )
{
sign = context.negativeSign();
}
}
//check if coordinate is all zeros for the specified precision, and if so,
//remove the sign and hemisphere strings
if ( degreesX == 0 && std::round( floatMinutesX * precisionMultiplier ) == 0 )
{
sign.clear();
hemisphere.clear();
}
//also remove directional prefix from 180 degree longitudes
if ( degreesX == 180 && std::round( floatMinutesX * precisionMultiplier ) == 0 )
{
hemisphere.clear();
}
ss << std::fixed << std::setprecision( numberDecimalPlaces() );
ss << floatMinutesX;
QString strMinutesX = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
trimTrailingZeros( strMinutesX, context );
//pad with leading digits if required
if ( mShowLeadingZeros && floatMinutesX < 10 )
strMinutesX = '0' + strMinutesX;
ss << std::fixed << std::setprecision( 0 );
ss << degreesX;
QString degreesXStr = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
if ( mShowLeadingDegreeZeros )
degreesXStr = QString( QStringLiteral( "000" ) + degreesXStr ).right( 3 );
return sign + degreesXStr + QChar( 176 ) +
strMinutesX + QChar( 0x2032 ) +
hemisphere;
}
QString QgsGeographicCoordinateNumericFormat::formatLatitudeAsDegrees( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const
{
//first, limit latitude to -180 to 180 degree range
double wrappedY = std::fmod( val, 180.0 );
//next, wrap around latitudes > 90 or < -90 degrees, so that eg "110S" -> "70N"
if ( wrappedY > 90.0 )
{
wrappedY = wrappedY - 180.0;
}
else if ( wrappedY < -90.0 )
{
wrappedY = wrappedY + 180.0;
}
const double absY = std::fabs( wrappedY );
const int precisionMultiplier = std::pow( 10.0, numberDecimalPlaces() );
QString hemisphere;
QString sign;
if ( mUseSuffix )
{
hemisphere = wrappedY < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
}
else
{
if ( wrappedY < 0 )
{
sign = context.negativeSign();
}
}
//check if coordinate is all zeros for the specified precision, and if so,
//remove the sign and hemisphere strings
if ( std::round( absY * precisionMultiplier ) == 0 )
{
sign.clear();
hemisphere.clear();
}
ss << std::fixed << std::setprecision( numberDecimalPlaces() );
ss << absY;
QString strDegreesY = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
trimTrailingZeros( strDegreesY, context );
if ( mShowLeadingDegreeZeros && absY < 10 )
strDegreesY = '0' + strDegreesY;
return sign + strDegreesY + QChar( 176 ) + hemisphere;
}
QString QgsGeographicCoordinateNumericFormat::formatLongitudeAsDegrees( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const
{
//first, limit longitude to -360 to 360 degree range
double wrappedX = std::fmod( val, 360.0 );
//next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
if ( wrappedX > 180.0 )
{
wrappedX = wrappedX - 360.0;
}
else if ( wrappedX < -180.0 )
{
wrappedX = wrappedX + 360.0;
}
const double absX = std::fabs( wrappedX );
const int precisionMultiplier = std::pow( 10.0, numberDecimalPlaces() );
QString hemisphere;
QString sign;
if ( mUseSuffix )
{
hemisphere = wrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
}
else
{
if ( wrappedX < 0 )
{
sign = context.negativeSign();
}
}
//check if coordinate is all zeros for the specified precision, and if so,
//remove the sign and hemisphere strings
if ( std::round( absX * precisionMultiplier ) == 0 )
{
sign.clear();
hemisphere.clear();
}
//also remove directional prefix from 180 degree longitudes
if ( std::round( absX * precisionMultiplier ) == 180 * precisionMultiplier )
{
sign.clear();
hemisphere.clear();
}
ss << std::fixed << std::setprecision( numberDecimalPlaces() );
ss << absX;
QString strDegreesX = QString::fromStdWString( ss.str() );
ss.str( std::wstring() );
trimTrailingZeros( strDegreesX, context );
if ( mShowLeadingDegreeZeros && absX < 100 )
strDegreesX = '0' + strDegreesX;
if ( mShowLeadingDegreeZeros && absX < 10 )
strDegreesX = '0' + strDegreesX;
return sign + strDegreesX + QChar( 176 ) + hemisphere;
}
void QgsGeographicCoordinateNumericFormat::trimTrailingZeros( QString &input, const QgsNumericFormatContext &context ) const
{
const QChar decimal = decimalSeparator().isNull() ? context.decimalSeparator() : decimalSeparator();
if ( !showTrailingZeros() && input.contains( decimal ) )
{
int trimPoint = input.length() - 1;
while ( input.at( trimPoint ) == context.zeroDigit() )
trimPoint--;
if ( input.at( trimPoint ) == decimal )
trimPoint--;
input.truncate( trimPoint + 1 );
}
}

View File

@ -0,0 +1,142 @@
/***************************************************************************
qgscoordinatenumericformat.h
--------------------------
begin : April 2022
copyright : (C) 2022 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSCOORDINATENUMERICFORMAT_H
#define QGSCOORDINATENUMERICFORMAT_H
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgsbasicnumericformat.h"
/**
* \ingroup core
* \brief A numeric formatter which returns a text representation of a geographic coordinate (latitude or longitude).
*
* \since QGIS 3.26
*/
class CORE_EXPORT QgsGeographicCoordinateNumericFormat : public QgsBasicNumericFormat
{
Q_GADGET
public:
/**
* Angle format options.
*/
enum class AngleFormat
{
DegreesMinutesSeconds, //!< Degrees, minutes and seconds, eg 30 degrees 45'30
DegreesMinutes, //!< Degrees and decimal minutes, eg 30 degrees 45.55'
DecimalDegrees, //!< Decimal degrees, eg 30.7555 degrees
};
Q_ENUM( AngleFormat )
/**
* Default constructor
*/
QgsGeographicCoordinateNumericFormat();
QString id() const override;
QString visibleName() const override;
int sortKey() override;
double suggestSampleValue() const override;
QString formatDouble( double value, const QgsNumericFormatContext &context ) const override;
QgsGeographicCoordinateNumericFormat *clone() const override SIP_FACTORY;
QgsNumericFormat *create( const QVariantMap &configuration, const QgsReadWriteContext &context ) const override SIP_FACTORY;
QVariantMap configuration( const QgsReadWriteContext &context ) const override;
/**
* Returns the angle format, which controls how bearing the angles are formatted
* described in the returned strings.
*
* \see setAngleFormat()
*/
AngleFormat angleFormat() const;
/**
* Sets the directional formatting option, which controls how bearing the angles are formatted
* described in the returned strings.
*
* \see angleFormat()
*/
void setAngleFormat( AngleFormat format );
/**
* Returns TRUE if leading zeros in the minutes or seconds values should be shown.
*
* \see setShowLeadingZeros()
*/
bool showLeadingZeros() const;
/**
* Sets whether leading zeros in the minutes or seconds values should be shown.
*
* \see showLeadingZeros()
*/
void setShowLeadingZeros( bool show );
/**
* Returns TRUE if leading zeros for the degree values should be shown.
*
* \see setShowDegreeLeadingZeros()
*/
bool showDegreeLeadingZeros() const;
/**
* Sets whether leading zeros for the degree values should be shown.
*
* \see showDegreeLeadingZeros()
*/
void setShowDegreeLeadingZeros( bool show );
/**
* Returns TRUE if directional suffixes (e.g. "N") should be included.
*
* \see setShowDirectionalSuffix()
*/
bool showDirectionalSuffix() const;
/**
* Sets whether directional suffixes (e.g. "N") should be included.
*
* \see showDirectionalSuffix()
*/
void setShowDirectionalSuffix( bool show );
void setConfiguration( const QVariantMap &configuration, const QgsReadWriteContext &context ) override;
private:
AngleFormat mAngleFormat = AngleFormat::DecimalDegrees;
bool mShowLeadingZeros = false;
bool mShowLeadingDegreeZeros = false;
bool mUseSuffix = true;
QString formatLongitude( double value, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const;
QString formatLatitude( double value, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const;
QString formatLatitudeAsDegreesMinutesSeconds( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const;
QString formatLongitudeAsDegreesMinutesSeconds( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const;
QString formatLatitudeAsDegreesMinutes( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const;
QString formatLongitudeAsDegreesMinutes( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const;
QString formatLatitudeAsDegrees( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const;
QString formatLongitudeAsDegrees( double val, std::basic_stringstream<wchar_t> &ss, const QgsNumericFormatContext &context ) const;
void trimTrailingZeros( QString &input, const QgsNumericFormatContext &context ) const;
};
#endif // QGSCOORDINATENUMERICFORMAT_H

View File

@ -33,6 +33,8 @@ class QgsReadWriteContext;
*/
class CORE_EXPORT QgsNumericFormatContext
{
Q_GADGET
public:
/**
@ -183,6 +185,44 @@ class CORE_EXPORT QgsNumericFormatContext
mExponential = character;
}
/**
* Interpretation of numeric values.
*
* \since QGIS 3.26
*/
enum class Interpretation
{
Generic, //!< Generic
Latitude, //!< Latitude values
Longitude, //!< Longitude values
};
Q_ENUM( Interpretation )
/**
* Returns the interpretation of the numbers being converted.
*
* \see setInterpretation()
*
* \since QGIS 3.26
*/
Interpretation interpretation() const
{
return mInterpretation;
}
/**
* Sets the \a interpretation of the numbers being converted.
*
* \see interpretation()
*
* \since QGIS 3.26
*/
void setInterpretation( Interpretation interpretation )
{
mInterpretation = interpretation;
}
private:
QChar mThousandsSep;
QChar mDecimalSep;
@ -191,6 +231,8 @@ class CORE_EXPORT QgsNumericFormatContext
QChar mNegativeSign;
QChar mPositiveSign;
QChar mExponential;
Interpretation mInterpretation = Interpretation::Generic;
};
#ifdef SIP_RUN
@ -201,6 +243,7 @@ class CORE_EXPORT QgsNumericFormatContext
#include <qgsfallbacknumericformat.h>
#include <qgspercentagenumericformat.h>
#include <qgsscientificnumericformat.h>
#include <qgscoordinatenumericformat.h>
% End
#endif
@ -221,6 +264,8 @@ class CORE_EXPORT QgsNumericFormat
SIP_CONVERT_TO_SUBCLASS_CODE
if ( dynamic_cast< QgsBearingNumericFormat * >( sipCpp ) )
sipType = sipType_QgsBearingNumericFormat;
else if ( dynamic_cast< QgsGeographicCoordinateNumericFormat * >( sipCpp ) )
sipType = sipType_QgsGeographicCoordinateNumericFormat;
else if ( dynamic_cast< QgsFallbackNumericFormat * >( sipCpp ) )
sipType = sipType_QgsFallbackNumericFormat;
else if ( dynamic_cast< QgsPercentageNumericFormat * >( sipCpp ) )

View File

@ -22,6 +22,7 @@
#include "qgspercentagenumericformat.h"
#include "qgsscientificnumericformat.h"
#include "qgsfractionnumericformat.h"
#include "qgscoordinatenumericformat.h"
#include "qgsxmlutils.h"
QgsNumericFormatRegistry::QgsNumericFormatRegistry()
@ -29,6 +30,7 @@ QgsNumericFormatRegistry::QgsNumericFormatRegistry()
addFormat( new QgsFallbackNumericFormat() );
addFormat( new QgsBasicNumericFormat() );
addFormat( new QgsBearingNumericFormat() );
addFormat( new QgsGeographicCoordinateNumericFormat() );
addFormat( new QgsCurrencyNumericFormat() );
addFormat( new QgsPercentageNumericFormat() );
addFormat( new QgsScientificNumericFormat() );

View File

@ -40,6 +40,16 @@ class QgsBearingNumericFormatConfigurationWidgetFactory : public QgsNumericForma
}
};
class QgsGeographicCoordinateNumericFormatConfigurationWidgetFactory : public QgsNumericFormatConfigurationWidgetFactory
{
public:
QgsNumericFormatWidget *create( const QgsNumericFormat *format ) const
{
return new QgsGeographicCoordinateNumericFormatWidget( format );
}
};
class QgsCurrencyNumericFormatConfigurationWidgetFactory : public QgsNumericFormatConfigurationWidgetFactory
{
public:
@ -89,6 +99,7 @@ QgsNumericFormatGuiRegistry::QgsNumericFormatGuiRegistry()
addFormatConfigurationWidgetFactory( QStringLiteral( "percentage" ), new QgsPercentageNumericFormatConfigurationWidgetFactory() );
addFormatConfigurationWidgetFactory( QStringLiteral( "scientific" ), new QgsScientificNumericFormatConfigurationWidgetFactory() );
addFormatConfigurationWidgetFactory( QStringLiteral( "fraction" ), new QgsFractionNumericFormatConfigurationWidgetFactory() );
addFormatConfigurationWidgetFactory( QStringLiteral( "geographiccoordinate" ), new QgsGeographicCoordinateNumericFormatConfigurationWidgetFactory() );
}
QgsNumericFormatGuiRegistry::~QgsNumericFormatGuiRegistry()

View File

@ -20,6 +20,7 @@
#include "qgsbearingnumericformat.h"
#include "qgsscientificnumericformat.h"
#include "qgsfractionnumericformat.h"
#include "qgscoordinatenumericformat.h"
#include "qgsgui.h"
#include "qgis.h"
#include <QDialogButtonBox>
@ -218,6 +219,119 @@ QgsBearingNumericFormat *QgsBearingNumericFormatDialog::format()
//
// QgsGeographicCoordinateNumericFormatWidget
//
QgsGeographicCoordinateNumericFormatWidget::QgsGeographicCoordinateNumericFormatWidget( const QgsNumericFormat *format, QWidget *parent )
: QgsNumericFormatWidget( parent )
{
setupUi( this );
mDecimalsSpinBox->setClearValue( 6 );
mFormatComboBox->addItem( QObject::tr( "Decimal Degrees" ), static_cast< int >( QgsGeographicCoordinateNumericFormat::AngleFormat::DecimalDegrees ) );
mFormatComboBox->addItem( QObject::tr( "Degrees, Minutes" ), static_cast< int >( QgsGeographicCoordinateNumericFormat::AngleFormat::DegreesMinutes ) );
mFormatComboBox->addItem( QObject::tr( "Degrees, Minutes, Seconds" ), static_cast< int >( QgsGeographicCoordinateNumericFormat::AngleFormat::DegreesMinutesSeconds ) );
setFormat( format->clone() );
connect( mShowTrailingZerosCheckBox, &QCheckBox::toggled, this, [ = ]( bool checked )
{
mFormat->setShowTrailingZeros( checked );
if ( !mBlockSignals )
emit changed();
} );
connect( mShowDirectionalSuffixCheckBox, &QCheckBox::toggled, this, [ = ]( bool checked )
{
mFormat->setShowDirectionalSuffix( checked );
if ( !mBlockSignals )
emit changed();
} );
connect( mShowLeadingZerosCheckBox, &QCheckBox::toggled, this, [ = ]( bool checked )
{
mFormat->setShowLeadingZeros( checked );
if ( !mBlockSignals )
emit changed();
} );
connect( mShowLeadingZerosForDegreesCheckBox, &QCheckBox::toggled, this, [ = ]( bool checked )
{
mFormat->setShowDegreeLeadingZeros( checked );
if ( !mBlockSignals )
emit changed();
} );
connect( mDecimalsSpinBox, qOverload<int>( &QSpinBox::valueChanged ), this, [ = ]( int value )
{
mFormat->setNumberDecimalPlaces( value );
if ( !mBlockSignals )
emit changed();
} );
connect( mFormatComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, [ = ]( int )
{
mFormat->setAngleFormat( static_cast < QgsGeographicCoordinateNumericFormat::AngleFormat >( mFormatComboBox->currentData().toInt() ) );
if ( !mBlockSignals )
emit changed();
} );
}
QgsGeographicCoordinateNumericFormatWidget::~QgsGeographicCoordinateNumericFormatWidget() = default;
void QgsGeographicCoordinateNumericFormatWidget::setFormat( QgsNumericFormat *format )
{
mFormat.reset( static_cast< QgsGeographicCoordinateNumericFormat * >( format ) );
mBlockSignals = true;
mDecimalsSpinBox->setValue( mFormat->numberDecimalPlaces() );
mShowTrailingZerosCheckBox->setChecked( mFormat->showTrailingZeros() );
mShowDirectionalSuffixCheckBox->setChecked( mFormat->showDirectionalSuffix() );
mShowLeadingZerosCheckBox->setChecked( mFormat->showLeadingZeros() );
mShowLeadingZerosForDegreesCheckBox->setChecked( mFormat->showDegreeLeadingZeros() );
mFormatComboBox->setCurrentIndex( mFormatComboBox->findData( static_cast< int >( mFormat->angleFormat() ) ) );
mBlockSignals = false;
}
QgsNumericFormat *QgsGeographicCoordinateNumericFormatWidget::format()
{
return mFormat->clone();
}
//
// QgsGeographicCoordinateNumericFormatDialog
//
QgsGeographicCoordinateNumericFormatDialog::QgsGeographicCoordinateNumericFormatDialog( const QgsNumericFormat *format, QWidget *parent )
: QDialog( parent )
{
setLayout( new QVBoxLayout() );
mWidget = new QgsGeographicCoordinateNumericFormatWidget( format );
QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Ok );
connect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
layout()->addWidget( mWidget );
layout()->addWidget( buttonBox );
connect( mWidget, &QgsPanelWidget::panelAccepted, this, &QDialog::reject );
setObjectName( QStringLiteral( "QgsGeographicCoordinateNumericFormatDialog" ) );
QgsGui::enableAutoGeometryRestore( this );
}
QgsGeographicCoordinateNumericFormat *QgsGeographicCoordinateNumericFormatDialog::format()
{
return static_cast< QgsGeographicCoordinateNumericFormat * >( mWidget->format() );
}
//
// QgsCurrencyNumericFormatWidget
//

View File

@ -162,6 +162,68 @@ class GUI_EXPORT QgsBearingNumericFormatDialog : public QDialog
};
#include "ui_qgsgeographiccoordinatenumericformatwidgetbase.h"
class QgsGeographicCoordinateNumericFormat;
/**
* \ingroup gui
* \class QgsGeographicCoordinateNumericFormatWidget
* \brief A widget which allow control over the properties of a QgsGeographicCoordinateNumericFormat.
* \since QGIS 3.26
*/
class GUI_EXPORT QgsGeographicCoordinateNumericFormatWidget : public QgsNumericFormatWidget, private Ui::QgsGeographicCoordinateNumericFormatWidgetBase
{
Q_OBJECT
public:
/**
* Constructor for QgsGeographicCoordinateNumericFormatWidget, initially showing the specified \a format.
*/
QgsGeographicCoordinateNumericFormatWidget( const QgsNumericFormat *format, QWidget *parent SIP_TRANSFERTHIS = nullptr );
~QgsGeographicCoordinateNumericFormatWidget() override;
void setFormat( QgsNumericFormat *format ) override;
QgsNumericFormat *format() override SIP_FACTORY;
private:
std::unique_ptr< QgsGeographicCoordinateNumericFormat > mFormat;
bool mBlockSignals = false;
};
/**
* \ingroup gui
* \class QgsGeographicCoordinateNumericFormatDialog
* \brief A dialog which allow control over the properties of a QgsGeographicCoordinateNumericFormat.
* \since QGIS 3.26
*/
class GUI_EXPORT QgsGeographicCoordinateNumericFormatDialog : public QDialog
{
Q_OBJECT
public:
/**
* Constructor for QgsGeographicCoordinateNumericFormatDialog, initially showing the specified \a format.
*/
QgsGeographicCoordinateNumericFormatDialog( const QgsNumericFormat *format, QWidget *parent SIP_TRANSFERTHIS = nullptr );
/**
* Returns the format defined by the current settings in the dialog.
*
* Ownership of the returned object is transferred to the caller
*/
QgsGeographicCoordinateNumericFormat *format() SIP_FACTORY;
private:
QgsGeographicCoordinateNumericFormatWidget *mWidget = nullptr;
};
#include "ui_qgscurrencynumericformatwidgetbase.h"

View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsGeographicCoordinateNumericFormatWidgetBase</class>
<widget class="QgsPanelWidget" name="QgsGeographicCoordinateNumericFormatWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>420</width>
<height>370</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="10" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Format</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="mShowDirectionalSuffixCheckBox">
<property name="text">
<string>Show directional suffix</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="mShowLeadingZerosForDegreesCheckBox">
<property name="text">
<string>Show leading zeros for degrees</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="mFormatComboBox"/>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="mShowLeadingZerosCheckBox">
<property name="text">
<string>Show leading zeros for minutes and seconds</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Decimal places</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QgsSpinBox" name="mDecimalsSpinBox">
<property name="value">
<number>6</number>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="mShowTrailingZerosCheckBox">
<property name="text">
<string>Show trailing zeros</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsSpinBox</class>
<extends>QSpinBox</extends>
<header>qgsspinbox.h</header>
</customwidget>
<customwidget>
<class>QgsPanelWidget</class>
<extends>QWidget</extends>
<header>qgspanelwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mFormatComboBox</tabstop>
<tabstop>mShowDirectionalSuffixCheckBox</tabstop>
<tabstop>mShowLeadingZerosCheckBox</tabstop>
<tabstop>mShowLeadingZerosForDegreesCheckBox</tabstop>
<tabstop>mDecimalsSpinBox</tabstop>
<tabstop>mShowTrailingZerosCheckBox</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -22,6 +22,7 @@ from qgis.core import (QgsFallbackNumericFormat,
QgsNumericFormatRegistry,
QgsNumericFormat,
QgsFractionNumericFormat,
QgsGeographicCoordinateNumericFormat,
QgsReadWriteContext)
from qgis.testing import start_app, unittest
from qgis.PyQt.QtXml import QDomDocument
@ -769,6 +770,653 @@ class TestQgsNumericFormat(unittest.TestCase):
self.assertEqual(f3.useDedicatedUnicodeCharacters(), f.useDedicatedUnicodeCharacters())
self.assertEqual(f3.useUnicodeSuperSubscript(), f.useUnicodeSuperSubscript())
def testGeographicFormat(self):
""" test geographic formatter """
f = QgsGeographicCoordinateNumericFormat()
f.setNumberDecimalPlaces(3)
f.setShowDirectionalSuffix(True)
f.setAngleFormat(QgsGeographicCoordinateNumericFormat.AngleFormat.DegreesMinutes)
f.setShowLeadingZeros(True)
f.setShowDegreeLeadingZeros(True)
f2 = f.clone()
self.assertIsInstance(f2, QgsGeographicCoordinateNumericFormat)
self.assertEqual(f2.numberDecimalPlaces(), f.numberDecimalPlaces())
self.assertEqual(f2.showDirectionalSuffix(), f.showDirectionalSuffix())
self.assertEqual(f2.angleFormat(), f.angleFormat())
self.assertEqual(f2.showLeadingZeros(), f.showLeadingZeros())
self.assertEqual(f2.showDegreeLeadingZeros(), f.showDegreeLeadingZeros())
doc = QDomDocument("testdoc")
elem = doc.createElement("test")
f2.writeXml(elem, doc, QgsReadWriteContext())
f3 = QgsNumericFormatRegistry().createFromXml(elem, QgsReadWriteContext())
self.assertIsInstance(f3, QgsGeographicCoordinateNumericFormat)
self.assertEqual(f3.numberDecimalPlaces(), f.numberDecimalPlaces())
self.assertEqual(f3.showDirectionalSuffix(), f.showDirectionalSuffix())
self.assertEqual(f3.angleFormat(), f.angleFormat())
self.assertEqual(f3.showLeadingZeros(), f.showLeadingZeros())
self.assertEqual(f3.showDegreeLeadingZeros(), f.showDegreeLeadingZeros())
def testGeographicCoordinateFormatLongitudeDms(self):
"""Test formatting longitude as DMS"""
f = QgsGeographicCoordinateNumericFormat()
context = QgsNumericFormatContext()
context.setInterpretation(QgsNumericFormatContext.Interpretation.Longitude)
f.setAngleFormat(QgsGeographicCoordinateNumericFormat.AngleFormat.DegreesMinutesSeconds)
f.setNumberDecimalPlaces(2)
f.setShowDirectionalSuffix(True)
f.setShowTrailingZeros(True)
self.assertEqual(f.formatDouble(80, context), "80°00.00″E")
# check precision
f.setNumberDecimalPlaces(4)
self.assertEqual(f.formatDouble(80, context), "80°00.0000″E")
self.assertEqual(f.formatDouble(80.12345678, context), "80°724.4444″E")
f.setNumberDecimalPlaces(0)
self.assertEqual(f.formatDouble(80.12345678, context), "80°724″E")
# check if longitudes > 180 or <-180 wrap around
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(370, context),
"10°00.00″E")
self.assertEqual(f.formatDouble(-370, context),
"10°00.00″W")
self.assertEqual(f.formatDouble(181, context),
"179°00.00″W")
self.assertEqual(f.formatDouble(-181, context),
"179°00.00″E")
self.assertEqual(f.formatDouble(359, context),
"1°00.00″W")
self.assertEqual(f.formatDouble(-359, context),
"1°00.00″E")
# should be no directional suffixes for 0 degree coordinates
self.assertEqual(f.formatDouble(0, context),
"0°00.00″")
# should also be no directional suffix for 0 degree coordinates within specified precision
self.assertEqual(f.formatDouble(-0.000001, context),
"0°00.00″")
f.setNumberDecimalPlaces(5)
self.assertEqual(
f.formatDouble(-0.000001, context),
"0°00.00360″W")
f.setNumberDecimalPlaces(2)
self.assertEqual(
f.formatDouble(0.000001, context),
"0°00.00″")
f.setNumberDecimalPlaces(5)
self.assertEqual(
f.formatDouble(0.000001, context),
"0°00.00360″E")
# should be no directional suffixes for 180 degree longitudes
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(180, context),
"180°00.00″")
self.assertEqual(
f.formatDouble(179.999999, context),
"180°00.00″")
f.setNumberDecimalPlaces(5)
self.assertEqual(
f.formatDouble(179.999999, context),
"179°5959.99640″E")
f.setNumberDecimalPlaces(2)
self.assertEqual(
f.formatDouble(180.000001, context),
"180°00.00″")
f.setNumberDecimalPlaces(5)
self.assertEqual(
f.formatDouble(180.000001, context),
"179°5959.99640″W")
# test rounding does not create seconds >= 60
f.setNumberDecimalPlaces(2)
self.assertEqual(
f.formatDouble(99.999999, context),
"100°00.00″E")
self.assertEqual(
f.formatDouble(89.999999, context),
"90°00.00″E")
# test without direction suffix
f.setShowDirectionalSuffix(False)
self.assertEqual(f.formatDouble(80, context), "80°00.00″")
# test 0 longitude
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(0, context), "0°00.00″")
# test near zero longitude
self.assertEqual(f.formatDouble(0.000001, context), "0°00.00″")
# should be no "-" prefix for near-zero longitude when rounding to 2 decimal places
self.assertEqual(
f.formatDouble(-0.000001, context), "0°00.00″")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(0.000001, context), "0°00.00360″")
self.assertEqual(
f.formatDouble(-0.000001, context), "-0°00.00360″")
# test with padding
f.setShowLeadingZeros(True)
f.setShowDirectionalSuffix(True)
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(80, context), "80°0000.00″E")
self.assertEqual(f.formatDouble(85.44, context), "85°2624.00″E")
self.assertEqual(f.formatDouble(0, context), "0°0000.00″")
self.assertEqual(
f.formatDouble(-0.000001, context), "0°0000.00″")
self.assertEqual(f.formatDouble(0.000001, context), "0°0000.00″")
f.setNumberDecimalPlaces(5)
self.assertEqual(
f.formatDouble(-0.000001, context), "0°0000.00360″W")
self.assertEqual(f.formatDouble(0.000001, context), "0°0000.00360″E")
# with degree padding
f.setShowDegreeLeadingZeros(True)
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(100, context), "100°0000.00″E")
self.assertEqual(f.formatDouble(-100, context), "100°0000.00″W")
self.assertEqual(f.formatDouble(80, context), "080°0000.00″E")
self.assertEqual(f.formatDouble(-80, context), "080°0000.00″W")
self.assertEqual(f.formatDouble(5.44, context), "005°2624.00″E")
self.assertEqual(f.formatDouble(-5.44, context), "005°2624.00″W")
f.setShowDirectionalSuffix(False)
self.assertEqual(f.formatDouble(100, context), "100°0000.00″")
self.assertEqual(f.formatDouble(-100, context), "-100°0000.00″")
self.assertEqual(f.formatDouble(80, context), "080°0000.00″")
self.assertEqual(f.formatDouble(-80, context), "-080°0000.00″")
self.assertEqual(f.formatDouble(5.44, context), "005°2624.00″")
self.assertEqual(f.formatDouble(-5.44, context), "-005°2624.00″")
f.setShowTrailingZeros(False)
f.setShowDirectionalSuffix(True)
f.setShowDegreeLeadingZeros(False)
self.assertEqual(f.formatDouble(5.44, context), "5°2624″E")
self.assertEqual(f.formatDouble(-5.44, context), "5°2624″W")
self.assertEqual(f.formatDouble(-5.44101, context), "5°2627.64″W")
context.setDecimalSeparator('')
self.assertEqual(f.formatDouble(5.44, context), "5°2624″E")
self.assertEqual(f.formatDouble(-5.44, context), "5°2624″W")
self.assertEqual(f.formatDouble(-5.44101, context), "5°2627☕64″W")
def testGeographicCoordinateFormatLatitudeDms(self):
"""Test formatting latitude as DMS"""
f = QgsGeographicCoordinateNumericFormat()
context = QgsNumericFormatContext()
context.setInterpretation(QgsNumericFormatContext.Interpretation.Latitude)
f.setAngleFormat(QgsGeographicCoordinateNumericFormat.AngleFormat.DegreesMinutesSeconds)
f.setNumberDecimalPlaces(2)
f.setShowDirectionalSuffix(True)
f.setShowTrailingZeros(True)
self.assertEqual(f.formatDouble(20, context), "20°00.00″N")
# check precision
f.setNumberDecimalPlaces(4)
self.assertEqual(f.formatDouble(20, context), "20°00.0000″N")
self.assertEqual(f.formatDouble(20.12345678, context), "20°724.4444″N")
f.setNumberDecimalPlaces(0)
self.assertEqual(f.formatDouble(20.12345678, context), "20°724″N")
# check if latitudes > 90 or <-90 wrap around
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(190, context), "10°00.00″N")
self.assertEqual(f.formatDouble(-190, context), "10°00.00″S")
self.assertEqual(f.formatDouble(91, context), "89°00.00″S")
self.assertEqual(f.formatDouble(-91, context), "89°00.00″N")
self.assertEqual(f.formatDouble(179, context), "1°00.00″S")
self.assertEqual(f.formatDouble(-179, context), "1°00.00″N")
# should be no directional suffixes for 0 degree coordinates
self.assertEqual(f.formatDouble(0, context), "0°00.00″")
# should also be no directional suffix for 0 degree coordinates within specified precision
self.assertEqual(f.formatDouble(0.000001, context), "0°00.00″")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(0.000001, context), "0°00.00360″N")
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(-0.000001, context), "0°00.00″")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(-0.000001, context), "0°00.00360″S")
# test rounding does not create seconds >= 60
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(89.999999, context), "90°00.00″N")
# test without direction suffix
f.setShowDirectionalSuffix(False)
self.assertEqual(f.formatDouble(20, context), "20°00.00″")
# test 0 latitude
self.assertEqual(f.formatDouble(0, context), "0°00.00″")
# test near zero lat/long
self.assertEqual(f.formatDouble(0.000001, context), "0°00.00″")
# should be no "-" prefix for near-zero latitude when rounding to 2 decimal places
self.assertEqual(f.formatDouble(-0.000001, context), "0°00.00″")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(0.000001, context), "0°00.00360″")
self.assertEqual(f.formatDouble(-0.000001, context), "-0°00.00360″")
# test with padding
f.setNumberDecimalPlaces(2)
f.setShowLeadingZeros(True)
f.setShowDirectionalSuffix(True)
self.assertEqual(f.formatDouble(20, context), "20°0000.00″N")
self.assertEqual(f.formatDouble(85.44, context), "85°2624.00″N")
self.assertEqual(f.formatDouble(0, context), "0°0000.00″")
self.assertEqual(f.formatDouble(-0.000001, context), "0°0000.00″")
self.assertEqual(f.formatDouble(0.000001, context), "0°0000.00″")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(-0.000001, context), "0°0000.00360″S")
self.assertEqual(f.formatDouble(0.000001, context), "0°0000.00360″N")
# with degree padding
f.setShowDegreeLeadingZeros(True)
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(80, context), "80°0000.00″N")
self.assertEqual(f.formatDouble(-80, context), "80°0000.00″S")
self.assertEqual(f.formatDouble(5.44, context), "05°2624.00″N")
self.assertEqual(f.formatDouble(-5.44, context), "05°2624.00″S")
f.setShowDirectionalSuffix(False)
self.assertEqual(f.formatDouble(80, context), "80°0000.00″")
self.assertEqual(f.formatDouble(-80, context), "-80°0000.00″")
self.assertEqual(f.formatDouble(5.44, context), "05°2624.00″")
self.assertEqual(f.formatDouble(-5.44, context), "-05°2624.00″")
f.setShowTrailingZeros(False)
f.setShowDirectionalSuffix(True)
f.setShowDegreeLeadingZeros(False)
self.assertEqual(f.formatDouble(5.44, context), "5°2624″N")
self.assertEqual(f.formatDouble(-5.44, context), "5°2624″S")
self.assertEqual(f.formatDouble(-5.44101, context), "5°2627.64″S")
context.setDecimalSeparator('')
self.assertEqual(f.formatDouble(5.44, context), "5°2624″N")
self.assertEqual(f.formatDouble(-5.44, context), "5°2624″S")
self.assertEqual(f.formatDouble(-5.44101, context), "5°2627☕64″S")
def testGeographicCoordinateFormatLongitudeMinutes(self):
"""Test formatting longitude as DM"""
f = QgsGeographicCoordinateNumericFormat()
context = QgsNumericFormatContext()
context.setInterpretation(QgsNumericFormatContext.Interpretation.Longitude)
f.setAngleFormat(QgsGeographicCoordinateNumericFormat.AngleFormat.DegreesMinutes)
f.setNumberDecimalPlaces(2)
f.setShowDirectionalSuffix(True)
f.setShowTrailingZeros(True)
self.assertEqual(f.formatDouble(80, context), "80°0.00E")
# check precision
f.setNumberDecimalPlaces(4)
self.assertEqual(f.formatDouble(80, context), "80°0.0000E")
self.assertEqual(f.formatDouble(80.12345678, context), "80°7.4074E")
f.setNumberDecimalPlaces(0)
self.assertEqual(f.formatDouble(80.12345678, context), "80°7E")
# check if longitudes > 180 or <-180 wrap around
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(370, context), "10°0.00E")
self.assertEqual(f.formatDouble(-370, context), "10°0.00W")
self.assertEqual(f.formatDouble(181, context), "179°0.00W")
self.assertEqual(f.formatDouble(-181, context), "179°0.00E")
self.assertEqual(f.formatDouble(359, context), "1°0.00W")
self.assertEqual(f.formatDouble(-359, context), "1°0.00E")
# should be no directional suffixes for 0 degree coordinates
self.assertEqual(f.formatDouble(0, context), "0°0.00")
# should also be no directional suffix for 0 degree coordinates within specified precision
self.assertEqual(f.formatDouble(-0.000001, context), "0°0.00")
self.assertEqual(f.formatDouble(0.000001, context), "0°0.00")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(-0.000001, context), "0°0.00006W")
self.assertEqual(f.formatDouble(0.000001, context), "0°0.00006E")
# test rounding does not create minutes >= 60
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(99.999999, context), "100°0.00E")
# should be no directional suffixes for 180 degree longitudes
self.assertEqual(f.formatDouble(180, context), "180°0.00")
# should also be no directional suffix for 180 degree longitudes within specified precision
self.assertEqual(f.formatDouble(180.000001, context), "180°0.00")
self.assertEqual(f.formatDouble(179.999999, context), "180°0.00")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(180.000001, context), "179°59.99994W")
self.assertEqual(f.formatDouble(179.999999, context), "179°59.99994E")
# test without direction suffix
f.setNumberDecimalPlaces(2)
f.setShowDirectionalSuffix(False)
self.assertEqual(f.formatDouble(80, context), "80°0.00")
# test 0 longitude
self.assertEqual(f.formatDouble(0, context), "0°0.00")
# test near zero longitude
self.assertEqual(f.formatDouble(0.000001, context), "0°0.00")
# should be no "-" prefix for near-zero longitude when rounding to 2 decimal places
self.assertEqual(f.formatDouble(-0.000001, context), "0°0.00")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(0.000001, context), "0°0.00006")
self.assertEqual(f.formatDouble(-0.000001, context), "-0°0.00006")
# test with padding
f.setNumberDecimalPlaces(2)
f.setShowLeadingZeros(True)
f.setShowDirectionalSuffix(True)
self.assertEqual(f.formatDouble(80, context), "80°00.00E")
self.assertEqual(f.formatDouble(0, context), "0°00.00")
self.assertEqual(f.formatDouble(-0.000001, context), "0°00.00")
self.assertEqual(f.formatDouble(0.000001, context), "0°00.00")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(-0.000001, context), "0°00.00006W")
self.assertEqual(f.formatDouble(0.000001, context), "0°00.00006E")
# with degree padding
f.setShowDegreeLeadingZeros(True)
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(100, context), "100°00.00E")
self.assertEqual(f.formatDouble(-100, context), "100°00.00W")
self.assertEqual(f.formatDouble(80, context), "080°00.00E")
self.assertEqual(f.formatDouble(-80, context), "080°00.00W")
self.assertEqual(f.formatDouble(5.44, context), "005°26.40E")
self.assertEqual(f.formatDouble(-5.44, context), "005°26.40W")
f.setShowDirectionalSuffix(False)
self.assertEqual(f.formatDouble(100, context), "100°00.00")
self.assertEqual(f.formatDouble(-100, context), "-100°00.00")
self.assertEqual(f.formatDouble(80, context), "080°00.00")
self.assertEqual(f.formatDouble(-80, context), "-080°00.00")
self.assertEqual(f.formatDouble(5.44, context), "005°26.40")
self.assertEqual(f.formatDouble(-5.44, context), "-005°26.40")
f.setShowTrailingZeros(False)
f.setShowDirectionalSuffix(True)
f.setShowDegreeLeadingZeros(False)
self.assertEqual(f.formatDouble(5.44, context), "5°26.4E")
self.assertEqual(f.formatDouble(-5.44, context), "5°26.4W")
self.assertEqual(f.formatDouble(-5.44101, context), "5°26.46W")
context.setDecimalSeparator('')
self.assertEqual(f.formatDouble(5.44, context), "5°26☕4E")
self.assertEqual(f.formatDouble(-5.44, context), "5°26☕4W")
self.assertEqual(f.formatDouble(-5.44101, context), "5°26☕46W")
def testGeographicCoordinateFormatLatitudeMinutes(self):
"""Test formatting latitude as DM"""
f = QgsGeographicCoordinateNumericFormat()
context = QgsNumericFormatContext()
context.setInterpretation(QgsNumericFormatContext.Interpretation.Latitude)
f.setAngleFormat(QgsGeographicCoordinateNumericFormat.AngleFormat.DegreesMinutes)
f.setNumberDecimalPlaces(2)
f.setShowDirectionalSuffix(True)
f.setShowTrailingZeros(True)
self.assertEqual(f.formatDouble(20, context), "20°0.00N")
# check precision
f.setNumberDecimalPlaces(4)
self.assertEqual(f.formatDouble(20, context), "20°0.0000N")
self.assertEqual(f.formatDouble(20.12345678, context), "20°7.4074N")
f.setNumberDecimalPlaces(0)
self.assertEqual(f.formatDouble(20.12345678, context), "20°7N")
# check if latitudes > 90 or <-90 wrap around
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(190, context), "10°0.00N")
self.assertEqual(f.formatDouble(-190, context), "10°0.00S")
self.assertEqual(f.formatDouble(91, context), "89°0.00S")
self.assertEqual(f.formatDouble(-91, context), "89°0.00N")
self.assertEqual(f.formatDouble(179, context), "1°0.00S")
self.assertEqual(f.formatDouble(-179, context), "1°0.00N")
# should be no directional suffixes for 0 degree coordinates
self.assertEqual(f.formatDouble(0, context), "0°0.00")
# should also be no directional suffix for 0 degree coordinates within specified precision
self.assertEqual(f.formatDouble(-0.000001, context), "0°0.00")
self.assertEqual(f.formatDouble(0.000001, context), "0°0.00")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(-0.000001, context), "0°0.00006S")
self.assertEqual(f.formatDouble(0.000001, context), "0°0.00006N")
# test rounding does not create minutes >= 60
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(79.999999, context), "80°0.00N")
# test without direction suffix
f.setShowDirectionalSuffix(False)
self.assertEqual(f.formatDouble(20, context), "20°0.00")
# test 0 latitude
self.assertEqual(f.formatDouble(0, context), "0°0.00")
# test near zero latitude
self.assertEqual(f.formatDouble(0.000001, context), "0°0.00")
# should be no "-" prefix for near-zero latitude when rounding to 2 decimal places
self.assertEqual(f.formatDouble(-0.000001, context), "0°0.00")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(0.000001, context), "0°0.00006")
self.assertEqual(f.formatDouble(-0.000001, context), "-0°0.00006")
# test with padding
f.setNumberDecimalPlaces(2)
f.setShowLeadingZeros(True)
f.setShowDirectionalSuffix(True)
self.assertEqual(f.formatDouble(20, context), "20°00.00N")
self.assertEqual(f.formatDouble(0, context), "0°00.00")
self.assertEqual(f.formatDouble(-0.000001, context), "0°00.00")
self.assertEqual(f.formatDouble(0.000001, context), "0°00.00")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(-0.000001, context), "0°00.00006S")
self.assertEqual(f.formatDouble(0.000001, context), "0°00.00006N")
# with degree padding
f.setShowDegreeLeadingZeros(True)
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(80, context), "80°00.00N")
self.assertEqual(f.formatDouble(-80, context), "80°00.00S")
self.assertEqual(f.formatDouble(5.44, context), "05°26.40N")
self.assertEqual(f.formatDouble(-5.44, context), "05°26.40S")
f.setShowDirectionalSuffix(False)
self.assertEqual(f.formatDouble(80, context), "80°00.00")
self.assertEqual(f.formatDouble(-80, context), "-80°00.00")
self.assertEqual(f.formatDouble(5.44, context), "05°26.40")
self.assertEqual(f.formatDouble(-5.44, context), "-05°26.40")
f.setShowTrailingZeros(False)
f.setShowDirectionalSuffix(True)
f.setShowDegreeLeadingZeros(False)
self.assertEqual(f.formatDouble(5.44, context), "5°26.4N")
self.assertEqual(f.formatDouble(-5.44, context), "5°26.4S")
self.assertEqual(f.formatDouble(-5.44101, context), "5°26.46S")
context.setDecimalSeparator('')
self.assertEqual(f.formatDouble(5.44, context), "5°26☕4N")
self.assertEqual(f.formatDouble(-5.44, context), "5°26☕4S")
self.assertEqual(f.formatDouble(-5.44101, context), "5°26☕46S")
def testGeographicCoordinateFormatLongitudeDegrees(self):
"""Test formatting longitude as decimal degrees"""
f = QgsGeographicCoordinateNumericFormat()
context = QgsNumericFormatContext()
context.setInterpretation(QgsNumericFormatContext.Interpretation.Longitude)
f.setAngleFormat(QgsGeographicCoordinateNumericFormat.AngleFormat.DecimalDegrees)
f.setNumberDecimalPlaces(2)
f.setShowDirectionalSuffix(True)
f.setShowTrailingZeros(True)
self.assertEqual(f.formatDouble(80, context), "80.00°E")
# check precision
f.setNumberDecimalPlaces(4)
self.assertEqual(f.formatDouble(80, context), "80.0000°E")
self.assertEqual(f.formatDouble(80.12345678, context), "80.1235°E")
f.setNumberDecimalPlaces(0)
self.assertEqual(f.formatDouble(80.12345678, context), "80°E")
# check if longitudes > 180 or <-180 wrap around
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(370, context), "10.00°E")
self.assertEqual(f.formatDouble(-370, context), "10.00°W")
self.assertEqual(f.formatDouble(181, context), "179.00°W")
self.assertEqual(f.formatDouble(-181, context), "179.00°E")
self.assertEqual(f.formatDouble(359, context), "1.00°W")
self.assertEqual(f.formatDouble(-359, context), "1.00°E")
# should be no directional suffixes for 0 degree coordinates
self.assertEqual(f.formatDouble(0, context), "0.00°")
# should also be no directional suffix for 0 degree coordinates within specified precision
self.assertEqual(f.formatDouble(-0.00001, context), "0.00°")
self.assertEqual(f.formatDouble(0.00001, context), "0.00°")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(-0.00001, context), "0.00001°W")
self.assertEqual(f.formatDouble(0.00001, context), "0.00001°E")
# should be no directional suffixes for 180 degree longitudes
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(180, context), "180.00°")
# should also be no directional suffix for 180 degree longitudes within specified precision
self.assertEqual(f.formatDouble(180.000001, context), "180.00°")
self.assertEqual(f.formatDouble(179.999999, context), "180.00°")
f.setNumberDecimalPlaces(6)
self.assertEqual(f.formatDouble(180.000001, context), "179.999999°W")
self.assertEqual(f.formatDouble(179.999999, context), "179.999999°E")
# test without direction suffix
f.setShowDirectionalSuffix(False)
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(80, context), "80.00°")
# test 0 longitude
self.assertEqual(f.formatDouble(0, context), "0.00°")
# test near zero longitude
self.assertEqual(f.formatDouble(0.000001, context), "0.00°")
# should be no "-" prefix for near-zero longitude when rounding to 2 decimal places
self.assertEqual(f.formatDouble(-0.000001, context), "0.00°")
f.setNumberDecimalPlaces(6)
self.assertEqual(f.formatDouble(0.000001, context), "0.000001°")
self.assertEqual(f.formatDouble(-0.000001, context), "-0.000001°")
# with degree padding
f.setShowDegreeLeadingZeros(True)
f.setNumberDecimalPlaces(2)
f.setShowDirectionalSuffix(True)
self.assertEqual(f.formatDouble(100, context), "100.00°E")
self.assertEqual(f.formatDouble(-100, context), "100.00°W")
self.assertEqual(f.formatDouble(80, context), "080.00°E")
self.assertEqual(f.formatDouble(-80, context), "080.00°W")
self.assertEqual(f.formatDouble(5.44, context), "005.44°E")
self.assertEqual(f.formatDouble(-5.44, context), "005.44°W")
f.setShowDirectionalSuffix(False)
self.assertEqual(f.formatDouble(100, context), "100.00°")
self.assertEqual(f.formatDouble(-100, context), "-100.00°")
self.assertEqual(f.formatDouble(80, context), "080.00°")
self.assertEqual(f.formatDouble(-80, context), "-080.00°")
self.assertEqual(f.formatDouble(5.44, context), "005.44°")
self.assertEqual(f.formatDouble(-5.44, context), "-005.44°")
f.setShowTrailingZeros(False)
f.setShowDirectionalSuffix(True)
f.setShowDegreeLeadingZeros(False)
self.assertEqual(f.formatDouble(5.44, context), "5.44°E")
self.assertEqual(f.formatDouble(-5.44, context), "5.44°W")
self.assertEqual(f.formatDouble(-5.44101, context), "5.44°W")
context.setDecimalSeparator('')
self.assertEqual(f.formatDouble(5.44, context), "5☕44°E")
self.assertEqual(f.formatDouble(-5.44, context), "5☕44°W")
self.assertEqual(f.formatDouble(-5.44101, context), "5☕44°W")
def testGeographicCoordinateFormatLatitudeDegrees(self):
"""Test formatting latitude as decimal degrees"""
f = QgsGeographicCoordinateNumericFormat()
context = QgsNumericFormatContext()
context.setInterpretation(QgsNumericFormatContext.Interpretation.Latitude)
f.setAngleFormat(QgsGeographicCoordinateNumericFormat.AngleFormat.DecimalDegrees)
f.setNumberDecimalPlaces(2)
f.setShowDirectionalSuffix(True)
f.setShowTrailingZeros(True)
self.assertEqual(f.formatDouble(20, context), "20.00°N")
# check precision
f.setNumberDecimalPlaces(4)
self.assertEqual(f.formatDouble(20, context), "20.0000°N")
self.assertEqual(f.formatDouble(20.12345678, context), "20.1235°N")
f.setNumberDecimalPlaces(0)
self.assertEqual(f.formatDouble(20.12345678, context), "20°N")
# check if latitudes > 90 or <-90 wrap around
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(190, context), "10.00°N")
self.assertEqual(f.formatDouble(-190, context), "10.00°S")
self.assertEqual(f.formatDouble(91, context), "89.00°S")
self.assertEqual(f.formatDouble(-91, context), "89.00°N")
self.assertEqual(f.formatDouble(179, context), "1.00°S")
self.assertEqual(f.formatDouble(-179, context), "1.00°N")
# should be no directional suffixes for 0 degree coordinates
self.assertEqual(f.formatDouble(0, context), "0.00°")
# should also be no directional suffix for 0 degree coordinates within specified precision
self.assertEqual(f.formatDouble(-0.00001, context), "0.00°")
self.assertEqual(f.formatDouble(0.00001, context), "0.00°")
f.setNumberDecimalPlaces(5)
self.assertEqual(f.formatDouble(-0.00001, context), "0.00001°S")
self.assertEqual(f.formatDouble(0.00001, context), "0.00001°N")
# test without direction suffix
f.setShowDirectionalSuffix(False)
f.setNumberDecimalPlaces(2)
self.assertEqual(f.formatDouble(80, context), "80.00°")
# test 0 longitude
self.assertEqual(f.formatDouble(0, context), "0.00°")
# test near zero latitude
self.assertEqual(f.formatDouble(0.000001, context), "0.00°")
# should be no "-" prefix for near-zero latitude when rounding to 2 decimal places
self.assertEqual(f.formatDouble(-0.000001, context), "0.00°")
f.setNumberDecimalPlaces(6)
self.assertEqual(f.formatDouble(0.000001, context), "0.000001°")
self.assertEqual(f.formatDouble(-0.000001, context), "-0.000001°")
# with degree padding
f.setShowDegreeLeadingZeros(True)
f.setNumberDecimalPlaces(2)
f.setShowDirectionalSuffix(True)
self.assertEqual(f.formatDouble(80, context), "80.00°N")
self.assertEqual(f.formatDouble(-80, context), "80.00°S")
self.assertEqual(f.formatDouble(5.44, context), "05.44°N")
self.assertEqual(f.formatDouble(-5.44, context), "05.44°S")
f.setShowDirectionalSuffix(False)
self.assertEqual(f.formatDouble(80, context), "80.00°")
self.assertEqual(f.formatDouble(-80, context), "-80.00°")
self.assertEqual(f.formatDouble(5.44, context), "05.44°")
self.assertEqual(f.formatDouble(-5.44, context), "-05.44°")
f.setShowTrailingZeros(False)
f.setShowDirectionalSuffix(True)
f.setShowDegreeLeadingZeros(False)
self.assertEqual(f.formatDouble(5.44, context), "5.44°N")
self.assertEqual(f.formatDouble(-5.44, context), "5.44°S")
self.assertEqual(f.formatDouble(-5.44101, context), "5.44°S")
context.setDecimalSeparator('')
self.assertEqual(f.formatDouble(5.44, context), "5☕44°N")
self.assertEqual(f.formatDouble(-5.44, context), "5☕44°S")
self.assertEqual(f.formatDouble(-5.44101, context), "5☕44°S")
def testRegistry(self):
registry = QgsNumericFormatRegistry()
self.assertTrue(registry.formats())

View File

@ -19,6 +19,7 @@ from qgis.core import (QgsFallbackNumericFormat,
QgsPercentageNumericFormat,
QgsScientificNumericFormat,
QgsCurrencyNumericFormat,
QgsGeographicCoordinateNumericFormat,
QgsNumericFormatRegistry,
QgsNumericFormat,
QgsApplication)
@ -189,6 +190,28 @@ class TestQgsNumericFormatGui(unittest.TestCase):
self.assertEqual(new.showTrailingZeros(), original.showTrailingZeros())
self.assertEqual(new.directionFormat(), original.directionFormat())
def testGeographicCoordinateFormat(self):
w = QgsNumericFormatSelectorWidget()
original = QgsGeographicCoordinateNumericFormat()
original.setNumberDecimalPlaces(4)
original.setShowTrailingZeros(True)
original.setAngleFormat(QgsGeographicCoordinateNumericFormat.AngleFormat.DegreesMinutes)
original.setShowDirectionalSuffix(True)
original.setShowLeadingZeros(True)
original.setShowDegreeLeadingZeros(True)
w.setFormat(original)
new = w.format()
self.assertIsInstance(new, QgsGeographicCoordinateNumericFormat)
self.assertEqual(new.numberDecimalPlaces(), original.numberDecimalPlaces())
self.assertEqual(new.showTrailingZeros(), original.showTrailingZeros())
self.assertEqual(new.showDirectionalSuffix(), original.showDirectionalSuffix())
self.assertEqual(new.angleFormat(), original.angleFormat())
self.assertEqual(new.showLeadingZeros(), original.showLeadingZeros())
self.assertEqual(new.showDegreeLeadingZeros(), original.showDegreeLeadingZeros())
def testPercentageFormat(self):
w = QgsNumericFormatSelectorWidget()