Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport release-3_40] Improve CRS searching by string #59847

Merged
merged 5 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions python/PyQt6/core/auto_additions/qgsstringutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
QgsStringUtils.htmlToMarkdown = staticmethod(QgsStringUtils.htmlToMarkdown)
QgsStringUtils.qRegExpEscape = staticmethod(QgsStringUtils.qRegExpEscape)
QgsStringUtils.truncateMiddleOfString = staticmethod(QgsStringUtils.truncateMiddleOfString)
QgsStringUtils.containsByWord = staticmethod(QgsStringUtils.containsByWord)
except (NameError, AttributeError):
pass
13 changes: 13 additions & 0 deletions python/PyQt6/core/auto_generated/qgsstringutils.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,19 @@ will be truncated by removing characters from the middle of the string
and replacing them with a horizontal ellipsis character.

.. versionadded:: 3.22
%End

static bool containsByWord( const QString &candidate, const QString &words, Qt::CaseSensitivity sensitivity = Qt::CaseInsensitive );
%Docstring
Given a ``candidate`` string, returns ``True`` if the ``candidate`` contains
all the individual words from another string, regardless of their order.

.. note::

The search does NOT need to match whole words in the ``candidate`` string,
so eg a candidate string of "Worldmap_Winkel_II" will return ``True`` for ``words`` "winkle world"

.. versionadded:: 3.42
%End

};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
QgsCoordinateReferenceSystemModel.Roles.RoleProj = QgsCoordinateReferenceSystemModel.CustomRole.Proj
QgsCoordinateReferenceSystemModel.RoleProj.is_monkey_patched = True
QgsCoordinateReferenceSystemModel.RoleProj.__doc__ = "The coordinate reference system's PROJ representation. This is only used for non-standard CRS (i.e. those not present in the database)."
QgsCoordinateReferenceSystemModel.Group = QgsCoordinateReferenceSystemModel.CustomRole.Group
QgsCoordinateReferenceSystemModel.Group.is_monkey_patched = True
QgsCoordinateReferenceSystemModel.Group.__doc__ = "Group name. \n.. versionadded:: 3.42"
QgsCoordinateReferenceSystemModel.Projection = QgsCoordinateReferenceSystemModel.CustomRole.Projection
QgsCoordinateReferenceSystemModel.Projection.is_monkey_patched = True
QgsCoordinateReferenceSystemModel.Projection.__doc__ = "Projection name. \n.. versionadded:: 3.42"
QgsCoordinateReferenceSystemModel.CustomRole.__doc__ = """Custom model roles.

.. note::
Expand Down Expand Up @@ -73,6 +79,14 @@

Available as ``QgsCoordinateReferenceSystemModel.RoleProj`` in older QGIS releases.

* ``Group``: Group name.

.. versionadded:: 3.42

* ``Projection``: Projection name.

.. versionadded:: 3.42


"""
# --
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ A tree model for display of known coordinate reference systems.
GroupId,
Wkt,
Proj,
Group,
Projection,
};

QgsCoordinateReferenceSystemModel( QObject *parent /TransferThis/ = 0 );
Expand Down
1 change: 1 addition & 0 deletions python/core/auto_additions/qgsstringutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
QgsStringUtils.htmlToMarkdown = staticmethod(QgsStringUtils.htmlToMarkdown)
QgsStringUtils.qRegExpEscape = staticmethod(QgsStringUtils.qRegExpEscape)
QgsStringUtils.truncateMiddleOfString = staticmethod(QgsStringUtils.truncateMiddleOfString)
QgsStringUtils.containsByWord = staticmethod(QgsStringUtils.containsByWord)
except (NameError, AttributeError):
pass
13 changes: 13 additions & 0 deletions python/core/auto_generated/qgsstringutils.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,19 @@ will be truncated by removing characters from the middle of the string
and replacing them with a horizontal ellipsis character.

.. versionadded:: 3.22
%End

static bool containsByWord( const QString &candidate, const QString &words, Qt::CaseSensitivity sensitivity = Qt::CaseInsensitive );
%Docstring
Given a ``candidate`` string, returns ``True`` if the ``candidate`` contains
all the individual words from another string, regardless of their order.

.. note::

The search does NOT need to match whole words in the ``candidate`` string,
so eg a candidate string of "Worldmap_Winkel_II" will return ``True`` for ``words`` "winkle world"

.. versionadded:: 3.42
%End

};
Expand Down
14 changes: 14 additions & 0 deletions python/gui/auto_additions/qgscoordinatereferencesystemmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
QgsCoordinateReferenceSystemModel.Roles.RoleProj = QgsCoordinateReferenceSystemModel.CustomRole.Proj
QgsCoordinateReferenceSystemModel.RoleProj.is_monkey_patched = True
QgsCoordinateReferenceSystemModel.RoleProj.__doc__ = "The coordinate reference system's PROJ representation. This is only used for non-standard CRS (i.e. those not present in the database)."
QgsCoordinateReferenceSystemModel.Group = QgsCoordinateReferenceSystemModel.CustomRole.Group
QgsCoordinateReferenceSystemModel.Group.is_monkey_patched = True
QgsCoordinateReferenceSystemModel.Group.__doc__ = "Group name. \n.. versionadded:: 3.42"
QgsCoordinateReferenceSystemModel.Projection = QgsCoordinateReferenceSystemModel.CustomRole.Projection
QgsCoordinateReferenceSystemModel.Projection.is_monkey_patched = True
QgsCoordinateReferenceSystemModel.Projection.__doc__ = "Projection name. \n.. versionadded:: 3.42"
QgsCoordinateReferenceSystemModel.CustomRole.__doc__ = """Custom model roles.

.. note::
Expand Down Expand Up @@ -73,6 +79,14 @@

Available as ``QgsCoordinateReferenceSystemModel.RoleProj`` in older QGIS releases.

* ``Group``: Group name.

.. versionadded:: 3.42

* ``Projection``: Projection name.

.. versionadded:: 3.42


"""
# --
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ A tree model for display of known coordinate reference systems.
GroupId,
Wkt,
Proj,
Group,
Projection,
};

QgsCoordinateReferenceSystemModel( QObject *parent /TransferThis/ = 0 );
Expand Down
17 changes: 17 additions & 0 deletions src/core/qgsstringutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,23 @@ QString QgsStringUtils::truncateMiddleOfString( const QString &string, int maxLe
#endif
}

bool QgsStringUtils::containsByWord( const QString &candidate, const QString &words, Qt::CaseSensitivity sensitivity )
{
if ( candidate.trimmed().isEmpty() )
return false;

const thread_local QRegularExpression rxWhitespace( QStringLiteral( "\\s+" ) );
const QStringList parts = words.split( rxWhitespace, Qt::SkipEmptyParts );
if ( parts.empty() )
return false;
for ( const QString &word : parts )
{
if ( !candidate.contains( word, sensitivity ) )
return false;
}
return true;
}

QgsStringReplacement::QgsStringReplacement( const QString &match, const QString &replacement, bool caseSensitive, bool wholeWordOnly )
: mMatch( match )
, mReplacement( replacement )
Expand Down
11 changes: 11 additions & 0 deletions src/core/qgsstringutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,17 @@ class CORE_EXPORT QgsStringUtils
*/
static QString truncateMiddleOfString( const QString &string, int maxLength );

/**
* Given a \a candidate string, returns TRUE if the \a candidate contains
* all the individual words from another string, regardless of their order.
*
* \note The search does NOT need to match whole words in the \a candidate string,
* so eg a candidate string of "Worldmap_Winkel_II" will return TRUE for \a words "winkle world"
*
* \since QGIS 3.42
*/
static bool containsByWord( const QString &candidate, const QString &words, Qt::CaseSensitivity sensitivity = Qt::CaseInsensitive );

};

#endif //QGSSTRINGUTILS_H
20 changes: 19 additions & 1 deletion src/gui/proj/qgscoordinatereferencesystemmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "moc_qgscoordinatereferencesystemmodel.cpp"
#include "qgscoordinatereferencesystemutils.h"
#include "qgsapplication.h"
#include "qgsstringutils.h"

#include <QFont>

Expand Down Expand Up @@ -157,6 +158,12 @@ QVariant QgsCoordinateReferenceSystemModel::data( const QModelIndex &index, int
case static_cast<int>( CustomRole::Proj ):
return crsNode->proj();

case static_cast<int>( CustomRole::Group ):
return crsNode->group();

case static_cast<int>( CustomRole::Projection ):
return crsNode->projection();

default:
break;
}
Expand Down Expand Up @@ -423,6 +430,7 @@ QgsCoordinateReferenceSystemModelCrsNode *QgsCoordinateReferenceSystemModel::add
break;
}
}
crsNode->setGroup( groupName );

if ( QgsCoordinateReferenceSystemModelGroupNode *group = parentNode->getChildGroupNode( groupId ) )
{
Expand All @@ -440,6 +448,8 @@ QgsCoordinateReferenceSystemModelCrsNode *QgsCoordinateReferenceSystemModel::add
QString projectionName = QgsCoordinateReferenceSystemUtils::translateProjection( record.projectionAcronym );
if ( projectionName.isEmpty() )
projectionName = tr( "Other" );
else
crsNode->setProjection( projectionName );

if ( QgsCoordinateReferenceSystemModelGroupNode *group = parentNode->getChildGroupNode( record.projectionAcronym ) )
{
Expand Down Expand Up @@ -706,7 +716,15 @@ bool QgsCoordinateReferenceSystemProxyModel::filterAcceptsRow( int sourceRow, co
if ( !mFilterString.trimmed().isEmpty() )
{
const QString name = sourceModel()->data( sourceIndex, static_cast<int>( QgsCoordinateReferenceSystemModel::CustomRole::Name ) ).toString();
if ( !( name.contains( mFilterString, Qt::CaseInsensitive )
QString candidate = name;
const QString groupName = sourceModel()->data( sourceIndex, static_cast<int>( QgsCoordinateReferenceSystemModel::CustomRole::Group ) ).toString();
if ( !groupName.isEmpty() )
candidate += ' ' + groupName;
const QString projectionName = sourceModel()->data( sourceIndex, static_cast<int>( QgsCoordinateReferenceSystemModel::CustomRole::Projection ) ).toString();
if ( !projectionName.isEmpty() )
candidate += ' ' + projectionName;

if ( !( QgsStringUtils::containsByWord( candidate, mFilterString )
|| authid.contains( mFilterString, Qt::CaseInsensitive ) ) )
return false;
}
Expand Down
36 changes: 36 additions & 0 deletions src/gui/proj/qgscoordinatereferencesystemmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,44 @@ class GUI_EXPORT QgsCoordinateReferenceSystemModelCrsNode : public QgsCoordinate
*/
QString proj() const { return mProj; }

/**
* Sets the CRS's group name.
*
* \see group()
* \since QGIS 3.42
*/
void setGroup( const QString &group ) { mGroup = group; }

/**
* Returns the CRS's group name.
*
* \see setGroup()
* \since QGIS 3.42
*/
QString group() const { return mGroup; }

/**
* Sets the CRS's projection name.
*
* \see projection()
* \since QGIS 3.42
*/
void setProjection( const QString &projection ) { mProjection = projection; }

/**
* Returns the CRS's projection name.
*
* \see setProjection()
* \since QGIS 3.42
*/
QString projection() const { return mProjection; }

private:
const QgsCrsDbRecord mRecord;
QString mWkt;
QString mProj;
QString mGroup;
QString mProjection;
};

#endif
Expand Down Expand Up @@ -232,6 +266,8 @@ class GUI_EXPORT QgsCoordinateReferenceSystemModel : public QAbstractItemModel
GroupId SIP_MONKEYPATCH_COMPAT_NAME( RoleGroupId ) = Qt::UserRole + 5, //!< The node ID (for group nodes)
Wkt SIP_MONKEYPATCH_COMPAT_NAME( RoleWkt ) = Qt::UserRole + 6, //!< The coordinate reference system's WKT representation. This is only used for non-standard CRS (i.e. those not present in the database).
Proj SIP_MONKEYPATCH_COMPAT_NAME( RoleProj ) = Qt::UserRole + 7, //!< The coordinate reference system's PROJ representation. This is only used for non-standard CRS (i.e. those not present in the database).
Group = Qt::UserRole + 8, //!< Group name. \since QGIS 3.42
Projection = Qt::UserRole + 9, //!< Projection name. \since QGIS 3.42
};
Q_ENUM( CustomRole )
// *INDENT-ON*
Expand Down
2 changes: 2 additions & 0 deletions src/gui/proj/qgsprojectionselectiontreewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ QgsProjectionSelectionTreeWidget::QgsProjectionSelectionTreeWidget( QWidget *par
connect( leSearch, &QgsFilterLineEdit::textChanged, this, [=]( const QString &filter ) {
mCrsModel->setFilterString( filter );
mRecentCrsModel->setFilterString( filter );
if ( filter.length() >= 3 )
lstCoordinateSystems->expandAll();
} );

mAreaCanvas->setVisible( mShowMap );
Expand Down
39 changes: 39 additions & 0 deletions tests/src/python/test_qgscoordinatereferencesystemmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,16 @@ def test_model(self):
epsg_3577_index, QgsCoordinateReferenceSystemModel.Roles.RoleProj
)
)
self.assertEqual(
model.data(epsg_3577_index, QgsCoordinateReferenceSystemModel.Roles.Group),
"Projected",
)
self.assertEqual(
model.data(
epsg_3577_index, QgsCoordinateReferenceSystemModel.Roles.Projection
),
"Albers Equal Area",
)

# check that same result is returned by authIdToIndex
self.assertEqual(model.authIdToIndex("EPSG:3577"), epsg_3577_index)
Expand Down Expand Up @@ -765,6 +775,35 @@ def test_proxy_model(self):
][0]
self.assertTrue(epsg_4347_index.isValid())

model.setFilterString("equal gda2020 Area")
projected_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(
model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.Roles.RoleGroupId,
)
== "Projected"
][0]
aae_index = [
model.index(row, 0, projected_index)
for row in range(model.rowCount(projected_index))
if model.data(
model.index(row, 0, projected_index),
Qt.ItemDataRole.DisplayRole,
)
== "Albers Equal Area"
][0]
epsg_9473_index = [
model.index(row, 0, aae_index)
for row in range(model.rowCount(aae_index))
if model.data(
model.index(row, 0, aae_index),
QgsCoordinateReferenceSystemModel.Roles.RoleAuthId,
)
== "EPSG:9473"
][0]

model.setFilterString("")

# set filtered list of crs to show
Expand Down
Loading
Loading