mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-15 00:07:25 -05:00
Implement API for selecting features in a vector tile layer
This commit is contained in:
parent
b33fc8ac95
commit
721bcaab66
@ -749,6 +749,12 @@ QgsVectorLayer.RemoveFromSelection.__doc__ = "Remove from current selection"
|
||||
Qgis.SelectBehavior.__doc__ = 'Specifies how a selection should be applied.\n\n.. versionadded:: 3.22\n\n' + '* ``SetSelection``: ' + Qgis.SelectBehavior.SetSelection.__doc__ + '\n' + '* ``AddToSelection``: ' + Qgis.SelectBehavior.AddToSelection.__doc__ + '\n' + '* ``IntersectSelection``: ' + Qgis.SelectBehavior.IntersectSelection.__doc__ + '\n' + '* ``RemoveFromSelection``: ' + Qgis.SelectBehavior.RemoveFromSelection.__doc__
|
||||
# --
|
||||
Qgis.SelectBehavior.baseClass = Qgis
|
||||
# monkey patching scoped based enum
|
||||
Qgis.SelectGeometryRelationship.Intersect.__doc__ = "Select where features intersect the reference geometry"
|
||||
Qgis.SelectGeometryRelationship.Within.__doc__ = "Select where features are within the reference geometry"
|
||||
Qgis.SelectGeometryRelationship.__doc__ = 'Geometry relationship test to apply for selecting features.\n\n.. versionadded:: 3.28\n\n' + '* ``Intersect``: ' + Qgis.SelectGeometryRelationship.Intersect.__doc__ + '\n' + '* ``Within``: ' + Qgis.SelectGeometryRelationship.Within.__doc__
|
||||
# --
|
||||
Qgis.SelectGeometryRelationship.baseClass = Qgis
|
||||
QgsVectorLayer.EditResult = Qgis.VectorEditResult
|
||||
# monkey patching scoped based enum
|
||||
QgsVectorLayer.Success = Qgis.VectorEditResult.Success
|
||||
|
||||
@ -485,6 +485,12 @@ The development version
|
||||
RemoveFromSelection,
|
||||
};
|
||||
|
||||
enum class SelectGeometryRelationship
|
||||
{
|
||||
Intersect,
|
||||
Within,
|
||||
};
|
||||
|
||||
enum class VectorEditResult
|
||||
{
|
||||
Success,
|
||||
|
||||
53
python/core/auto_generated/qgsselectioncontext.sip.in
Normal file
53
python/core/auto_generated/qgsselectioncontext.sip.in
Normal file
@ -0,0 +1,53 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/qgsselectioncontext.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
class QgsSelectionContext
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
|
||||
Encapsulates the context of a layer selection operation.
|
||||
|
||||
.. versionadded:: 3.28
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsselectioncontext.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
double scale() const;
|
||||
%Docstring
|
||||
Returns the map scale at which the selection should occur.
|
||||
|
||||
The scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map.
|
||||
|
||||
.. seealso:: :py:func:`setScale`
|
||||
%End
|
||||
|
||||
void setScale( double scale );
|
||||
%Docstring
|
||||
Sets the map ``scale`` at which the selection should occur.
|
||||
|
||||
The scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map.
|
||||
|
||||
.. seealso:: :py:func:`scale`
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/qgsselectioncontext.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
@ -189,6 +189,73 @@ Sets whether to render also borders of tiles (useful for debugging)
|
||||
bool isTileBorderRenderingEnabled() const;
|
||||
%Docstring
|
||||
Returns whether to render also borders of tiles (useful for debugging)
|
||||
%End
|
||||
|
||||
QList< QgsFeature > selectedFeatures() const;
|
||||
%Docstring
|
||||
Returns the list of features currently selected in the layer.
|
||||
|
||||
.. seealso:: :py:func:`selectedFeatureCount`
|
||||
|
||||
.. seealso:: :py:func:`selectByGeometry`
|
||||
|
||||
.. seealso:: :py:func:`removeSelection`
|
||||
|
||||
.. seealso:: :py:func:`selectionChanged`
|
||||
|
||||
.. versionadded:: 3.28
|
||||
%End
|
||||
|
||||
int selectedFeatureCount() const;
|
||||
%Docstring
|
||||
Returns the number of features that are selected in this layer.
|
||||
|
||||
.. seealso:: :py:func:`selectedFeatures`
|
||||
|
||||
.. seealso:: :py:func:`selectByGeometry`
|
||||
|
||||
.. seealso:: :py:func:`removeSelection`
|
||||
|
||||
.. seealso:: :py:func:`selectionChanged`
|
||||
|
||||
.. versionadded:: 3.28
|
||||
%End
|
||||
|
||||
void selectByGeometry( const QgsGeometry &geometry, const QgsSelectionContext &context,
|
||||
Qgis::SelectBehavior behavior = Qgis::SelectBehavior::SetSelection,
|
||||
Qgis::SelectGeometryRelationship relationship = Qgis::SelectGeometryRelationship::Intersect );
|
||||
%Docstring
|
||||
Selects features found within the search ``geometry`` (in layer's coordinates).
|
||||
|
||||
.. seealso:: :py:func:`selectedFeatures`
|
||||
|
||||
.. seealso:: :py:func:`removeSelection`
|
||||
|
||||
.. seealso:: :py:func:`selectionChanged`
|
||||
|
||||
.. versionadded:: 3.28
|
||||
%End
|
||||
|
||||
public slots:
|
||||
|
||||
void removeSelection();
|
||||
%Docstring
|
||||
Clear selection
|
||||
|
||||
.. seealso:: :py:func:`selectByGeometry`
|
||||
|
||||
.. seealso:: :py:func:`selectionChanged`
|
||||
|
||||
.. versionadded:: 3.28
|
||||
%End
|
||||
|
||||
signals:
|
||||
|
||||
void selectionChanged();
|
||||
%Docstring
|
||||
Emitted whenever the selected features in the layer are changed.
|
||||
|
||||
.. versionadded:: 3.28
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
@ -164,6 +164,7 @@
|
||||
%Include auto_generated/qgsruntimeprofiler.sip
|
||||
%Include auto_generated/qgsscalecalculator.sip
|
||||
%Include auto_generated/qgsscaleutils.sip
|
||||
%Include auto_generated/qgsselectioncontext.sip
|
||||
%Include auto_generated/qgssimplifymethod.sip
|
||||
%Include auto_generated/qgssingleitemmodel.sip
|
||||
%Include auto_generated/qgssnappingconfig.sip
|
||||
|
||||
@ -482,6 +482,7 @@ set(QGIS_CORE_SRCS
|
||||
qgsruntimeprofiler.cpp
|
||||
qgsscalecalculator.cpp
|
||||
qgsscaleutils.cpp
|
||||
qgsselectioncontext.cpp
|
||||
qgsshapegenerator.cpp
|
||||
qgssimplifymethod.cpp
|
||||
qgssingleitemmodel.cpp
|
||||
@ -1158,6 +1159,7 @@ set(QGIS_CORE_HDRS
|
||||
qgsruntimeprofiler.h
|
||||
qgsscalecalculator.h
|
||||
qgsscaleutils.h
|
||||
qgsselectioncontext.h
|
||||
qgsshapegenerator.h
|
||||
qgssimplifymethod.h
|
||||
qgssingleitemmodel.h
|
||||
|
||||
@ -783,6 +783,18 @@ class CORE_EXPORT Qgis
|
||||
};
|
||||
Q_ENUM( SelectBehavior )
|
||||
|
||||
/**
|
||||
* Geometry relationship test to apply for selecting features.
|
||||
*
|
||||
* \since QGIS 3.28
|
||||
*/
|
||||
enum class SelectGeometryRelationship : int
|
||||
{
|
||||
Intersect, //!< Select where features intersect the reference geometry
|
||||
Within, //!< Select where features are within the reference geometry
|
||||
};
|
||||
Q_ENUM( SelectGeometryRelationship )
|
||||
|
||||
/**
|
||||
* Specifies the result of a vector layer edit operation
|
||||
*
|
||||
|
||||
27
src/core/qgsselectioncontext.cpp
Normal file
27
src/core/qgsselectioncontext.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
/***************************************************************************
|
||||
qgsselectioncontext.cpp
|
||||
--------------------------
|
||||
begin : June 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 "qgsselectioncontext.h"
|
||||
|
||||
void QgsSelectionContext::setScale( double scale )
|
||||
{
|
||||
mScale = scale;
|
||||
}
|
||||
|
||||
double QgsSelectionContext::scale() const
|
||||
{
|
||||
return mScale;
|
||||
}
|
||||
59
src/core/qgsselectioncontext.h
Normal file
59
src/core/qgsselectioncontext.h
Normal file
@ -0,0 +1,59 @@
|
||||
/***************************************************************************
|
||||
qgsselectioncontext.h
|
||||
--------------------------
|
||||
begin : June 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 QGSSELECTIONCONTEXT_H
|
||||
#define QGSSELECTIONCONTEXT_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
|
||||
/**
|
||||
* \class QgsSelectionContext
|
||||
* \ingroup core
|
||||
*
|
||||
* \brief Encapsulates the context of a layer selection operation.
|
||||
*
|
||||
* \since QGIS 3.28
|
||||
*/
|
||||
class CORE_EXPORT QgsSelectionContext
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Returns the map scale at which the selection should occur.
|
||||
*
|
||||
* The scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map.
|
||||
*
|
||||
* \see setScale()
|
||||
*/
|
||||
double scale() const;
|
||||
|
||||
/**
|
||||
* Sets the map \a scale at which the selection should occur.
|
||||
*
|
||||
* The scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map.
|
||||
*
|
||||
* \see scale()
|
||||
*/
|
||||
void setScale( double scale );
|
||||
|
||||
private:
|
||||
|
||||
double mScale = 0;
|
||||
};
|
||||
|
||||
#endif //QGSELECTIONCONTEXT_H
|
||||
|
||||
|
||||
|
||||
@ -34,6 +34,9 @@
|
||||
#include "qgspainting.h"
|
||||
#include "qgsmaplayerfactory.h"
|
||||
#include "qgsarcgisrestutils.h"
|
||||
#include "qgsselectioncontext.h"
|
||||
#include "qgsgeometryengine.h"
|
||||
#include "qgsvectortilemvtdecoder.h"
|
||||
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
@ -841,6 +844,176 @@ QgsVectorTileLabeling *QgsVectorTileLayer::labeling() const
|
||||
return mLabeling.get();
|
||||
}
|
||||
|
||||
QList<QgsFeature> QgsVectorTileLayer::selectedFeatures() const
|
||||
{
|
||||
QList< QgsFeature > res;
|
||||
res.reserve( mSelectedFeatures.size() );
|
||||
for ( auto it = mSelectedFeatures.begin(); it != mSelectedFeatures.end(); ++it )
|
||||
res.append( it.value() );
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int QgsVectorTileLayer::selectedFeatureCount() const
|
||||
{
|
||||
return mSelectedFeatures.size();
|
||||
}
|
||||
|
||||
void QgsVectorTileLayer::selectByGeometry( const QgsGeometry &geometry, const QgsSelectionContext &context, Qgis::SelectBehavior behavior, Qgis::SelectGeometryRelationship relationship )
|
||||
{
|
||||
if ( !isInScaleRange( context.scale() ) )
|
||||
{
|
||||
QgsDebugMsgLevel( QStringLiteral( "Out of scale limits" ), 2 );
|
||||
return;
|
||||
}
|
||||
|
||||
QSet< QgsFeatureId > prevSelection;
|
||||
prevSelection.reserve( mSelectedFeatures.size() );
|
||||
for ( auto it = mSelectedFeatures.begin(); it != mSelectedFeatures.end(); ++it )
|
||||
prevSelection.insert( it.key() );
|
||||
|
||||
switch ( behavior )
|
||||
{
|
||||
case Qgis::SelectBehavior::SetSelection:
|
||||
case Qgis::SelectBehavior::IntersectSelection:
|
||||
mSelectedFeatures.clear();
|
||||
break;
|
||||
|
||||
case Qgis::SelectBehavior::AddToSelection:
|
||||
case Qgis::SelectBehavior::RemoveFromSelection:
|
||||
break;
|
||||
}
|
||||
|
||||
QgsGeometry selectionGeom = geometry;
|
||||
bool isPointOrRectangle;
|
||||
QgsPointXY point;
|
||||
bool isSinglePoint = selectionGeom.type() == QgsWkbTypes::PointGeometry;
|
||||
if ( isSinglePoint )
|
||||
{
|
||||
isPointOrRectangle = true;
|
||||
point = selectionGeom.asPoint();
|
||||
relationship = Qgis::SelectGeometryRelationship::Intersect;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have a polygon - maybe it is a rectangle - in such case we can avoid costly instersection tests later
|
||||
isPointOrRectangle = QgsGeometry::fromRect( selectionGeom.boundingBox() ).isGeosEqual( selectionGeom );
|
||||
}
|
||||
|
||||
std::unique_ptr<QgsGeometryEngine> selectionGeomPrepared;
|
||||
|
||||
QgsRectangle r;
|
||||
if ( isSinglePoint )
|
||||
{
|
||||
r = QgsRectangle( point.x(), point.y(), point.x(), point.y() );
|
||||
}
|
||||
else
|
||||
{
|
||||
r = selectionGeom.boundingBox();
|
||||
|
||||
if ( !isPointOrRectangle || relationship == Qgis::SelectGeometryRelationship::Within )
|
||||
{
|
||||
// use prepared geometry for faster intersection test
|
||||
selectionGeomPrepared.reset( QgsGeometry::createGeometryEngine( selectionGeom.constGet() ) );
|
||||
}
|
||||
}
|
||||
|
||||
const int tileZoom = tileMatrixSet().scaleToZoomLevel( context.scale() );
|
||||
const QgsTileMatrix tileMatrix = tileMatrixSet().tileMatrix( tileZoom );
|
||||
const QgsTileRange tileRange = tileMatrix.tileRangeFromExtent( r );
|
||||
|
||||
for ( int row = tileRange.startRow(); row <= tileRange.endRow(); ++row )
|
||||
{
|
||||
for ( int col = tileRange.startColumn(); col <= tileRange.endColumn(); ++col )
|
||||
{
|
||||
QgsTileXYZ tileID( col, row, tileZoom );
|
||||
QByteArray data = getRawTile( tileID );
|
||||
if ( data.isEmpty() )
|
||||
continue; // failed to get data
|
||||
|
||||
QgsVectorTileMVTDecoder decoder( tileMatrixSet() );
|
||||
if ( !decoder.decode( tileID, data ) )
|
||||
continue; // failed to decode
|
||||
|
||||
QMap<QString, QgsFields> perLayerFields;
|
||||
const QStringList layerNames = decoder.layers();
|
||||
for ( const QString &layerName : layerNames )
|
||||
{
|
||||
QSet<QString> fieldNames = qgis::listToSet( decoder.layerFieldNames( layerName ) );
|
||||
perLayerFields[layerName] = QgsVectorTileUtils::makeQgisFields( fieldNames );
|
||||
}
|
||||
|
||||
const QgsVectorTileFeatures features = decoder.layerFeatures( perLayerFields, QgsCoordinateTransform() );
|
||||
const QStringList featuresLayerNames = features.keys();
|
||||
for ( const QString &layerName : featuresLayerNames )
|
||||
{
|
||||
const QgsFields fFields = perLayerFields[layerName];
|
||||
const QVector<QgsFeature> &layerFeatures = features[layerName];
|
||||
for ( const QgsFeature &f : layerFeatures )
|
||||
{
|
||||
if ( f.geometry().intersects( r ) )
|
||||
{
|
||||
bool selectFeature = true;
|
||||
if ( selectionGeomPrepared )
|
||||
{
|
||||
switch ( relationship )
|
||||
{
|
||||
case Qgis::SelectGeometryRelationship::Intersect:
|
||||
selectFeature = selectionGeomPrepared->intersects( f.geometry().constGet() );
|
||||
break;
|
||||
case Qgis::SelectGeometryRelationship::Within:
|
||||
selectFeature = selectionGeomPrepared->contains( f.geometry().constGet() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( selectFeature )
|
||||
{
|
||||
switch ( behavior )
|
||||
{
|
||||
case Qgis::SelectBehavior::SetSelection:
|
||||
case Qgis::SelectBehavior::AddToSelection:
|
||||
mSelectedFeatures.insert( f.id(), f );
|
||||
break;
|
||||
|
||||
case Qgis::SelectBehavior::IntersectSelection:
|
||||
{
|
||||
if ( prevSelection.contains( f.id() ) )
|
||||
mSelectedFeatures.insert( f.id(), f );
|
||||
break;
|
||||
}
|
||||
|
||||
case Qgis::SelectBehavior::RemoveFromSelection:
|
||||
{
|
||||
mSelectedFeatures.remove( f.id() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QSet< QgsFeatureId > newSelection;
|
||||
newSelection.reserve( mSelectedFeatures.size() );
|
||||
for ( auto it = mSelectedFeatures.begin(); it != mSelectedFeatures.end(); ++it )
|
||||
newSelection.insert( it.key() );
|
||||
|
||||
// signal
|
||||
if ( prevSelection != newSelection )
|
||||
emit selectionChanged();
|
||||
}
|
||||
|
||||
void QgsVectorTileLayer::removeSelection()
|
||||
{
|
||||
if ( mSelectedFeatures.empty() )
|
||||
return;
|
||||
|
||||
mSelectedFeatures.clear();
|
||||
emit selectionChanged();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
|
||||
@ -21,11 +21,15 @@
|
||||
|
||||
#include "qgsmaplayer.h"
|
||||
#include "qgsvectortilematrixset.h"
|
||||
#include "qgsfeatureid.h"
|
||||
|
||||
class QgsVectorTileLabeling;
|
||||
class QgsVectorTileRenderer;
|
||||
|
||||
class QgsTileXYZ;
|
||||
class QgsFeature;
|
||||
class QgsGeometry;
|
||||
class QgsSelectionContext;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -203,6 +207,60 @@ class CORE_EXPORT QgsVectorTileLayer : public QgsMapLayer
|
||||
//! Returns whether to render also borders of tiles (useful for debugging)
|
||||
bool isTileBorderRenderingEnabled() const { return mTileBorderRendering; }
|
||||
|
||||
/**
|
||||
* Returns the list of features currently selected in the layer.
|
||||
*
|
||||
* \see selectedFeatureCount()
|
||||
* \see selectByGeometry()
|
||||
* \see removeSelection()
|
||||
* \see selectionChanged()
|
||||
* \since QGIS 3.28
|
||||
*/
|
||||
QList< QgsFeature > selectedFeatures() const;
|
||||
|
||||
/**
|
||||
* Returns the number of features that are selected in this layer.
|
||||
*
|
||||
* \see selectedFeatures()
|
||||
* \see selectByGeometry()
|
||||
* \see removeSelection()
|
||||
* \see selectionChanged()
|
||||
* \since QGIS 3.28
|
||||
*/
|
||||
int selectedFeatureCount() const;
|
||||
|
||||
/**
|
||||
* Selects features found within the search \a geometry (in layer's coordinates).
|
||||
*
|
||||
* \see selectedFeatures()
|
||||
* \see removeSelection()
|
||||
* \see selectionChanged()
|
||||
* \since QGIS 3.28
|
||||
*/
|
||||
void selectByGeometry( const QgsGeometry &geometry, const QgsSelectionContext &context,
|
||||
Qgis::SelectBehavior behavior = Qgis::SelectBehavior::SetSelection,
|
||||
Qgis::SelectGeometryRelationship relationship = Qgis::SelectGeometryRelationship::Intersect );
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
* Clear selection
|
||||
*
|
||||
* \see selectByGeometry()
|
||||
* \see selectionChanged()
|
||||
* \since QGIS 3.28
|
||||
*/
|
||||
void removeSelection();
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
* Emitted whenever the selected features in the layer are changed.
|
||||
*
|
||||
* \since QGIS 3.28
|
||||
*/
|
||||
void selectionChanged();
|
||||
|
||||
private:
|
||||
bool loadDataSource();
|
||||
|
||||
@ -227,6 +285,8 @@ class CORE_EXPORT QgsVectorTileLayer : public QgsMapLayer
|
||||
|
||||
std::unique_ptr< QgsDataProvider > mDataProvider;
|
||||
|
||||
QHash< QgsFeatureId, QgsFeature > mSelectedFeatures;
|
||||
|
||||
bool setupArcgisVectorTileServiceConnection( const QString &uri, const QgsDataSourceUri &dataSourceUri );
|
||||
|
||||
void setDataSourcePrivate( const QString &dataSource, const QString &baseName, const QString &provider,
|
||||
|
||||
@ -328,6 +328,7 @@ ADD_PYTHON_TEST(PyQgsOGRProvider test_provider_ogr.py)
|
||||
ADD_PYTHON_TEST(PyQgsReadWriteContext test_qgsreadwritecontext.py)
|
||||
ADD_PYTHON_TEST(PyQgsSearchWidgetToolButton test_qgssearchwidgettoolbutton.py)
|
||||
ADD_PYTHON_TEST(PyQgsSearchWidgetWrapper test_qgssearchwidgetwrapper.py)
|
||||
ADD_PYTHON_TEST(PyQgsSelectionContext test_qgsselectioncontext.py)
|
||||
ADD_PYTHON_TEST(PyQgsShortcutsManager test_qgsshortcutsmanager.py)
|
||||
ADD_PYTHON_TEST(PyQgsSimpleFillSymbolLayer test_qgssimplefillsymbollayer.py)
|
||||
ADD_PYTHON_TEST(PyQgsSimpleLineSymbolLayer test_qgssimplelinesymbollayer.py)
|
||||
|
||||
27
tests/src/python/test_qgsselectioncontext.py
Normal file
27
tests/src/python/test_qgsselectioncontext.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""QGIS Unit tests for QgsSelectionContext.
|
||||
|
||||
.. note:: 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.
|
||||
"""
|
||||
|
||||
import qgis # NOQA
|
||||
|
||||
from qgis.core import (
|
||||
QgsSelectionContext,
|
||||
)
|
||||
from qgis.testing import unittest
|
||||
|
||||
|
||||
class TestQgsSelectionContext(unittest.TestCase):
|
||||
|
||||
def testBasic(self):
|
||||
context = QgsSelectionContext()
|
||||
context.setScale(1000)
|
||||
self.assertEqual(context.scale(), 1000)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@ -15,27 +15,29 @@ test_vectortile.py
|
||||
***************************************************************************/
|
||||
'''
|
||||
|
||||
import qgis # NOQA
|
||||
import tempfile
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from qgis.testing import unittest, start_app
|
||||
from utilities import unitTestDataPath
|
||||
|
||||
import qgis # NOQA
|
||||
from qgis.PyQt.QtCore import QUrl
|
||||
from qgis.PyQt.QtTest import QSignalSpy
|
||||
from qgis.core import (QgsVectorLayer,
|
||||
QgsVectorTileWriter,
|
||||
QgsDataSourceUri,
|
||||
QgsTileXYZ,
|
||||
QgsProviderRegistry,
|
||||
QgsVectorTileLayer,
|
||||
QgsProviderMetadata)
|
||||
|
||||
from pathlib import Path
|
||||
QgsProviderMetadata,
|
||||
QgsGeometry,
|
||||
QgsSelectionContext,
|
||||
Qgis
|
||||
)
|
||||
from qgis.testing import unittest, start_app
|
||||
from utilities import unitTestDataPath
|
||||
|
||||
TEST_DATA_PATH = Path(unitTestDataPath())
|
||||
|
||||
|
||||
start_app()
|
||||
|
||||
|
||||
@ -112,21 +114,137 @@ class TestVectorTile(unittest.TestCase):
|
||||
|
||||
uri = 'type=xyz&serviceType=arcgis&url=https://fake.server/%7Bx%7D/%7By%7D/%7Bz%7D.png&zmax=2&http-header:referer=https://qgis.org/&styleUrl=https://qgis.org/'
|
||||
parts = md.decodeUri(uri)
|
||||
self.assertEqual(parts, {'type': 'xyz', 'serviceType': 'arcgis', 'url': 'https://fake.server/{x}/{y}/{z}.png', 'zmax': '2', 'http-header:referer': 'https://qgis.org/', 'referer': 'https://qgis.org/', 'styleUrl': 'https://qgis.org/'})
|
||||
self.assertEqual(parts, {'type': 'xyz', 'serviceType': 'arcgis', 'url': 'https://fake.server/{x}/{y}/{z}.png',
|
||||
'zmax': '2', 'http-header:referer': 'https://qgis.org/',
|
||||
'referer': 'https://qgis.org/', 'styleUrl': 'https://qgis.org/'})
|
||||
|
||||
parts['url'] = 'https://fake.new.server/{x}/{y}/{z}.png'
|
||||
uri = md.encodeUri(parts)
|
||||
self.assertEqual(uri, 'serviceType=arcgis&styleUrl=https://qgis.org/&type=xyz&url=https://fake.new.server/%7Bx%7D/%7By%7D/%7Bz%7D.png&zmax=2&http-header:referer=https://qgis.org/')
|
||||
self.assertEqual(uri,
|
||||
'serviceType=arcgis&styleUrl=https://qgis.org/&type=xyz&url=https://fake.new.server/%7Bx%7D/%7By%7D/%7Bz%7D.png&zmax=2&http-header:referer=https://qgis.org/')
|
||||
|
||||
def testZoomRange(self):
|
||||
"""
|
||||
Test retrieval of zoom range from URI
|
||||
"""
|
||||
vl = QgsVectorTileLayer('type=xyz&url=https://wxs.ign.fr/parcellaire/geoportail/tms/1.0.0/PCI/%7Bz%7D/%7Bx%7D/%7By%7D.pbf&zmax=19&zmin=5', 'test')
|
||||
vl = QgsVectorTileLayer(
|
||||
'type=xyz&url=https://wxs.ign.fr/parcellaire/geoportail/tms/1.0.0/PCI/%7Bz%7D/%7Bx%7D/%7By%7D.pbf&zmax=19&zmin=5',
|
||||
'test')
|
||||
self.assertTrue(vl.isValid())
|
||||
self.assertEqual(vl.sourceMinZoom(), 5)
|
||||
self.assertEqual(vl.sourceMaxZoom(), 19)
|
||||
|
||||
def testSelection(self):
|
||||
layer = QgsVectorTileLayer('type=vtpk&url={}'.format(unitTestDataPath() + '/testvtpk.vtpk'), 'tiles')
|
||||
self.assertTrue(layer.isValid())
|
||||
|
||||
self.assertFalse(layer.selectedFeatures())
|
||||
spy = QSignalSpy(layer.selectionChanged)
|
||||
|
||||
# select by polygon
|
||||
selection_geometry = QgsGeometry.fromWkt(
|
||||
'Polygon ((-12225020.2316580843180418 6030602.60334861185401678, -13521860.30317855626344681 5526975.39110779482871294, -12976264.15658413991332054 4821897.29397048708051443, -12019372.45332629978656769 4884850.69550053123384714, -11650045.83101624809205532 4754746.99900492746382952, -11469579.41329662501811981 5535369.17797833122313023, -11792740.20781794004142284 6110343.57862004917114973, -12225020.2316580843180418 6030602.60334861185401678))')
|
||||
context = QgsSelectionContext()
|
||||
context.setScale(17991708)
|
||||
layer.selectByGeometry(selection_geometry, context, Qgis.SelectBehavior.SetSelection,
|
||||
Qgis.SelectGeometryRelationship.Intersect)
|
||||
|
||||
self.assertEqual(layer.selectedFeatureCount(), 8)
|
||||
self.assertCountEqual(set(f.id() for f in layer.selectedFeatures()), {3320043274240,
|
||||
3320026497024,
|
||||
2220498092032,
|
||||
2224826613760,
|
||||
3320043274243,
|
||||
2220514869248,
|
||||
3324338241541,
|
||||
3324338241536})
|
||||
self.assertCountEqual(set(f.geometry().asWkt(-3) for f in layer.selectedFeatures()), {
|
||||
'Polygon ((-13222000 4970000, -13203000 5004000, -13189000 5073000, -13164000 5127000, -13145000 5205000, -13106000 5239000, -13047000 5274000, -12964000 5269000, -12910000 5195000, -12885000 5185000, -12846000 5185000, -12802000 5151000, -12758000 5093000, -12685000 5093000, -12611000 5107000, -12484000 5103000, -12484000 4970000, -13222000 4970000))',
|
||||
'MultiPolygon (((-12157000 5694000, -12132000 5738000, -12093000 5782000, -12039000 5817000, -11956000 5836000, -11853000 5836000, -11795000 5792000, -11785000 5763000, -11760000 5738000, -11736000 5621000, -11716000 5587000, -11653000 5528000, -11643000 5503000, -11643000 5366000, -11653000 5337000, -11692000 5278000, -11731000 5249000, -11741000 5239000, -11839000 5205000, -11863000 5220000, -11927000 5288000, -11941000 5440000, -11951000 5450000, -12005000 5450000, -12020000 5450000, -12029000 5435000, -12054000 5435000, -12093000 5450000, -12132000 5503000, -12157000 5518000, -12166000 5543000, -12166000 5650000, -12157000 5694000),(-11995000 5680000, -12015000 5626000, -12005000 5582000, -11990000 5562000, -11927000 5547000, -11907000 5528000, -11883000 5479000, -11863000 5469000, -11829000 5469000, -11809000 5489000, -11814000 5577000, -11863000 5626000, -11912000 5704000, -11966000 5704000, -11995000 5680000)),((-11521000 5773000, -11496000 5851000, -11413000 5900000, -11389000 5890000, -11325000 5787000, -11320000 5724000, -11364000 5645000, -11418000 5640000, -11462000 5655000, -11491000 5699000, -11506000 5709000, -11521000 5773000)))',
|
||||
'Polygon ((-12563000 5105000, -12470000 5102000, -12411000 4990000, -12411000 4970000, -12563000 4970000, -12563000 5105000))',
|
||||
'Polygon ((-11868000 4750000, -11809000 4882000, -11702000 4956000, -11638000 4956000, -11609000 4916000, -11574000 4892000, -11452000 4882000, -11354000 4833000, -11325000 4794000, -11291000 4765000, -11212000 4638000, -11217000 4579000, -11261000 4486000, -11266000 4432000, -11281000 4393000, -11296000 4383000, -11310000 4339000, -11398000 4300000, -11501000 4305000, -11530000 4320000, -11579000 4359000, -11633000 4427000, -11687000 4466000, -11790000 4515000, -11834000 4569000, -11873000 4652000, -11868000 4750000))',
|
||||
'Polygon ((-13228000 4960000, -13203000 5004000, -13194000 5049000, -12484000 5049000, -12484000 4869000, -12587000 4814000, -12631000 4765000, -12641000 4716000, -12680000 4652000, -12714000 4618000, -12837000 4589000, -12900000 4589000, -12944000 4608000, -12949000 4618000, -13027000 4623000, -13062000 4647000, -13135000 4662000, -13189000 4691000, -13228000 4779000, -13238000 4819000, -13238000 4907000, -13228000 4960000))',
|
||||
'MultiLineString ((-11662000 5797000, -11633000 5704000, -11589000 5640000, -11398000 5518000, -11325000 5420000, -11276000 5127000, -11247000 5053000, -11208000 4990000, -11207000 4970000),(-11305000 5337000, -11261000 5381000, -11227000 5459000, -11178000 5518000, -11105000 5562000, -11051000 5670000, -10997000 5724000, -10953000 5812000))',
|
||||
'Polygon ((-12442000 5049000, -12411000 4990000, -12411000 4956000, -12426000 4916000, -12450000 4887000, -12563000 4827000, -12563000 5049000, -12442000 5049000))',
|
||||
'Point (-12714000 5220000)'})
|
||||
self.assertEqual(len(spy), 1)
|
||||
|
||||
# select same again, should be no new signal
|
||||
layer.selectByGeometry(selection_geometry, context, Qgis.SelectBehavior.SetSelection,
|
||||
Qgis.SelectGeometryRelationship.Intersect)
|
||||
self.assertEqual(len(spy), 1)
|
||||
|
||||
# select within
|
||||
layer.selectByGeometry(selection_geometry, context, Qgis.SelectBehavior.SetSelection,
|
||||
Qgis.SelectGeometryRelationship.Within)
|
||||
self.assertEqual(len(spy), 2)
|
||||
self.assertCountEqual(set(f.id() for f in layer.selectedFeatures()), {2220498092032, 3320043274243})
|
||||
self.assertCountEqual(set(f.geometry().asWkt(-3) for f in layer.selectedFeatures()),
|
||||
{'Point (-12714000 5220000)',
|
||||
'Polygon ((-12563000 5105000, -12470000 5102000, -12411000 4990000, -12411000 4970000, -12563000 4970000, -12563000 5105000))'})
|
||||
|
||||
# add to selection
|
||||
selection_geometry = QgsGeometry.fromWkt(
|
||||
'Polygon ((-11104449.68442200869321823 6041094.83693683333694935, -11461185.62642602622509003 5822856.37829908169806004, -11054086.96319791302084923 5419954.60850630886852741, -10793879.57020674645900726 5835447.05860510468482971, -11104449.68442200869321823 6041094.83693683333694935))')
|
||||
layer.selectByGeometry(selection_geometry, context, Qgis.SelectBehavior.AddToSelection,
|
||||
Qgis.SelectGeometryRelationship.Intersect)
|
||||
self.assertEqual(len(spy), 3)
|
||||
self.assertCountEqual(set(f.id() for f in layer.selectedFeatures()),
|
||||
{2220498092032, 3320043274243, 3320043274240, 3320026497024})
|
||||
self.assertCountEqual(set(f.geometry().asWkt(-3) for f in layer.selectedFeatures()),
|
||||
{
|
||||
'Polygon ((-12563000 5105000, -12470000 5102000, -12411000 4990000, -12411000 4970000, -12563000 4970000, -12563000 5105000))',
|
||||
'MultiLineString ((-11662000 5797000, -11633000 5704000, -11589000 5640000, -11398000 5518000, -11325000 5420000, -11276000 5127000, -11247000 5053000, -11208000 4990000, -11207000 4970000),(-11305000 5337000, -11261000 5381000, -11227000 5459000, -11178000 5518000, -11105000 5562000, -11051000 5670000, -10997000 5724000, -10953000 5812000))',
|
||||
'Point (-12714000 5220000)',
|
||||
'MultiPolygon (((-12157000 5694000, -12132000 5738000, -12093000 5782000, -12039000 5817000, -11956000 5836000, -11853000 5836000, -11795000 5792000, -11785000 5763000, -11760000 5738000, -11736000 5621000, -11716000 5587000, -11653000 5528000, -11643000 5503000, -11643000 5366000, -11653000 5337000, -11692000 5278000, -11731000 5249000, -11741000 5239000, -11839000 5205000, -11863000 5220000, -11927000 5288000, -11941000 5440000, -11951000 5450000, -12005000 5450000, -12020000 5450000, -12029000 5435000, -12054000 5435000, -12093000 5450000, -12132000 5503000, -12157000 5518000, -12166000 5543000, -12166000 5650000, -12157000 5694000),(-11995000 5680000, -12015000 5626000, -12005000 5582000, -11990000 5562000, -11927000 5547000, -11907000 5528000, -11883000 5479000, -11863000 5469000, -11829000 5469000, -11809000 5489000, -11814000 5577000, -11863000 5626000, -11912000 5704000, -11966000 5704000, -11995000 5680000)),((-11521000 5773000, -11496000 5851000, -11413000 5900000, -11389000 5890000, -11325000 5787000, -11320000 5724000, -11364000 5645000, -11418000 5640000, -11462000 5655000, -11491000 5699000, -11506000 5709000, -11521000 5773000)))'})
|
||||
|
||||
# remove from selection
|
||||
layer.selectByGeometry(selection_geometry, context, Qgis.SelectBehavior.RemoveFromSelection,
|
||||
Qgis.SelectGeometryRelationship.Intersect)
|
||||
self.assertEqual(len(spy), 4)
|
||||
self.assertCountEqual(set(f.id() for f in layer.selectedFeatures()), {2220498092032, 3320043274243})
|
||||
self.assertCountEqual(set(f.geometry().asWkt(-3) for f in layer.selectedFeatures()),
|
||||
{'Point (-12714000 5220000)',
|
||||
'Polygon ((-12563000 5105000, -12470000 5102000, -12411000 4990000, -12411000 4970000, -12563000 4970000, -12563000 5105000))'})
|
||||
|
||||
# intersect selection
|
||||
selection_geometry = QgsGeometry.fromWkt(
|
||||
'Polygon ((-12632118.89488627389073372 5457726.64942438062280416, -12862948.03383005037903786 5310835.37918743211776018, -12850357.35352402552962303 5046431.09276092518121004, -12716056.76359310187399387 4987674.58466614596545696, -12434864.90342522785067558 5113581.38772638700902462, -12632118.89488627389073372 5457726.64942438062280416))')
|
||||
|
||||
layer.selectByGeometry(selection_geometry, context, Qgis.SelectBehavior.IntersectSelection,
|
||||
Qgis.SelectGeometryRelationship.Within)
|
||||
self.assertEqual(len(spy), 5)
|
||||
self.assertCountEqual(set(f.id() for f in layer.selectedFeatures()), {2220498092032})
|
||||
self.assertCountEqual(set(f.geometry().asWkt(-3) for f in layer.selectedFeatures()),
|
||||
{'Point (-12714000 5220000)'})
|
||||
|
||||
# selection should depend on tile scale
|
||||
selection_geometry = QgsGeometry.fromWkt(
|
||||
'Polygon ((-12225020.2316580843180418 6030602.60334861185401678, -13521860.30317855626344681 5526975.39110779482871294, -12976264.15658413991332054 4821897.29397048708051443, -12019372.45332629978656769 4884850.69550053123384714, -11650045.83101624809205532 4754746.99900492746382952, -11469579.41329662501811981 5535369.17797833122313023, -11792740.20781794004142284 6110343.57862004917114973, -12225020.2316580843180418 6030602.60334861185401678))')
|
||||
context = QgsSelectionContext()
|
||||
context.setScale(137882080)
|
||||
layer.selectByGeometry(selection_geometry, context, Qgis.SelectBehavior.SetSelection,
|
||||
Qgis.SelectGeometryRelationship.Intersect)
|
||||
|
||||
self.assertEqual(len(layer.selectedFeatures()), 5)
|
||||
self.assertCountEqual(set(f.id() for f in layer.selectedFeatures()),
|
||||
{33554432, 16777216, 0, 33554433, 33554438})
|
||||
self.assertCountEqual(set(f.geometry().asWkt(-3) for f in layer.selectedFeatures()), {
|
||||
'MultiPolygon (((-12152000 5694000, -12132000 5733000, -12093000 5792000, -12034000 5812000, -11956000 5831000, -11858000 5831000, -11799000 5792000, -11780000 5773000, -11760000 5733000, -11741000 5616000, -11721000 5596000, -11662000 5538000, -11643000 5499000, -11643000 5362000, -11662000 5342000, -11682000 5283000, -11721000 5244000, -11741000 5244000, -11839000 5205000, -11858000 5225000, -11917000 5283000, -11936000 5440000, -11956000 5459000, -11995000 5459000, -12015000 5459000, -12034000 5440000, -12054000 5440000, -12093000 5459000, -12132000 5499000, -12152000 5518000, -12171000 5538000, -12171000 5655000, -12152000 5694000),(-11995000 5675000, -12015000 5636000, -11995000 5577000, -11995000 5557000, -11917000 5557000, -11917000 5538000, -11878000 5479000, -11858000 5479000, -11839000 5479000, -11799000 5499000, -11819000 5577000, -11858000 5636000, -11917000 5714000, -11956000 5714000, -11995000 5675000)),((-11525000 5773000, -11486000 5851000, -11408000 5909000, -11389000 5890000, -11330000 5792000, -11310000 5733000, -11369000 5655000, -11408000 5636000, -11467000 5655000, -11486000 5694000, -11506000 5714000, -11525000 5773000)))',
|
||||
'MultiPoint ((-13052000 4471000),(-9275000 4016000),(-12201000 4261000),(-10885000 4143000),(-9828000 3992000),(-9534000 4711000),(-10371000 4965000),(-11403000 5049000),(-12499000 4775000),(-11501000 2607000),(-11452000 3703000),(-11085000 4951000),(-10493000 5919000),(-12714000 5220000),(-12607000 4290000),(-12249000 3703000),(-11687000 3331000))',
|
||||
'MultiLineString ((-13091000 4188000, -13032000 4207000, -12974000 4285000, -12915000 4364000, -12778000 4442000, -12621000 4501000, -12445000 4481000, -12367000 4461000, -12328000 4461000, -12191000 4403000, -12113000 4344000, -11995000 4285000, -11858000 4285000, -11760000 4246000, -11467000 4227000, -11291000 4227000, -11173000 4285000, -11134000 4305000, -11017000 4305000, -10821000 4246000, -10645000 4246000, -10508000 4285000, -10430000 4344000, -10351000 4422000, -10195000 4520000, -10058000 4559000, -10058000 4579000, -9960000 4559000, -9921000 4540000, -9843000 4520000, -9745000 4461000, -9706000 4442000, -9549000 4422000, -9353000 4442000, -9236000 4520000, -9197000 4520000),(-12347000 4461000, -12367000 4403000, -12406000 4325000, -12426000 4305000, -12445000 4266000, -12465000 4207000, -12504000 4148000, -12621000 4109000, -12758000 4109000, -12797000 4090000, -12817000 4051000, -12856000 3992000, -12856000 3914000, -12856000 3816000, -12837000 3796000, -12797000 3777000),(-11662000 5792000, -11623000 5714000, -11584000 5636000, -11408000 5518000, -11330000 5420000, -11271000 5127000, -11252000 5049000, -11212000 4990000, -11193000 4872000, -11193000 4833000, -11193000 4814000, -11154000 4775000, -11056000 4696000, -11036000 4638000, -11075000 4559000, -11115000 4520000, -11134000 4442000, -11134000 4422000, -11056000 4227000, -11075000 4168000, -11095000 4148000, -11134000 4090000, -11134000 4031000, -11173000 3992000, -11212000 3914000, -11212000 3816000, -11193000 3757000, -11115000 3659000, -11075000 3542000, -11017000 3503000, -10978000 3444000, -10899000 3366000, -10860000 3327000, -10684000 3190000, -10664000 3150000, -10645000 3131000, -10645000 3053000, -10704000 2974000, -10723000 2955000, -10723000 2896000, -10704000 2818000, -10606000 2700000, -10528000 2661000),(-11310000 5342000, -11252000 5381000, -11232000 5459000, -11173000 5518000, -11115000 5557000, -11056000 5675000, -10997000 5733000, -10958000 5812000),(-10273000 4461000, -10254000 4364000, -10234000 4364000, -10214000 4344000, -10175000 4305000, -10097000 4305000, -10058000 4285000, -10038000 4227000, -10019000 4129000, -10019000 4109000, -9999000 4070000, -9921000 3953000, -9901000 3953000, -9804000 3933000, -9745000 3914000, -9706000 3835000, -9686000 3816000, -9627000 3796000, -9549000 3777000, -9530000 3757000, -9432000 3698000, -9353000 3679000, -9314000 3600000, -9256000 3561000),(-9843000 4520000, -9843000 4579000, -9823000 4598000, -9725000 4716000, -9725000 4735000, -9706000 4814000, -9647000 4912000, -9549000 5029000, -9530000 5146000, -9393000 5440000, -9314000 5479000, -9158000 5499000))',
|
||||
'Polygon ((-11858000 4755000, -11799000 4892000, -11702000 4951000, -11643000 4951000, -11604000 4912000, -11565000 4892000, -11447000 4892000, -11349000 4833000, -11330000 4794000, -11291000 4775000, -11212000 4638000, -11212000 4579000, -11252000 4481000, -11271000 4442000, -11271000 4403000, -11291000 4383000, -11310000 4344000, -11408000 4305000, -11506000 4305000, -11525000 4325000, -11584000 4364000, -11623000 4422000, -11682000 4461000, -11780000 4520000, -11839000 4579000, -11878000 4657000, -11858000 4755000))',
|
||||
'Polygon ((-13228000 4970000, -13208000 5009000, -13189000 5068000, -13169000 5127000, -13150000 5205000, -13110000 5244000, -13052000 5283000, -12954000 5264000, -12915000 5205000, -12876000 5185000, -12856000 5185000, -12797000 5146000, -12758000 5088000, -12680000 5088000, -12602000 5107000, -12465000 5107000, -12406000 4990000, -12406000 4951000, -12426000 4912000, -12445000 4892000, -12582000 4814000, -12621000 4775000, -12641000 4716000, -12680000 4657000, -12719000 4618000, -12837000 4598000, -12895000 4598000, -12934000 4618000, -12954000 4618000, -13032000 4618000, -13052000 4657000, -13130000 4657000, -13189000 4696000, -13228000 4775000, -13228000 4814000, -13228000 4912000, -13228000 4970000))'})
|
||||
self.assertEqual(len(spy), 6)
|
||||
|
||||
layer.removeSelection()
|
||||
self.assertFalse(layer.selectedFeatures())
|
||||
self.assertEqual(len(spy), 7)
|
||||
|
||||
layer.removeSelection()
|
||||
self.assertFalse(layer.selectedFeatures())
|
||||
self.assertEqual(len(spy), 7)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user