Restrict results to ONLY those matching the selected geometry types

This commit is contained in:
Nyall Dawson 2024-02-14 11:52:47 +10:00
parent 8ad47c01f3
commit edc26aeb0b
6 changed files with 87 additions and 20 deletions

View File

@ -55,6 +55,12 @@ Returns the geometry field for a specified entity ``type``.
static bool entityTypeHasGeometry( Qgis::SensorThingsEntity type );
%Docstring
Returns ``True`` if the specified entity ``type`` can have geometry attached.
%End
static QString filterForWkbType( Qgis::WkbType type );
%Docstring
Returns a filter string which restricts results to those matching the specified
WKB ``type``.
%End
};

View File

@ -55,6 +55,12 @@ Returns the geometry field for a specified entity ``type``.
static bool entityTypeHasGeometry( Qgis::SensorThingsEntity type );
%Docstring
Returns ``True`` if the specified entity ``type`` can have geometry attached.
%End
static QString filterForWkbType( Qgis::WkbType type );
%Docstring
Returns a filter string which restricts results to those matching the specified
WKB ``type``.
%End
};

View File

@ -140,7 +140,12 @@ long long QgsSensorThingsSharedData::featureCount( QgsFeedback *feedback ) const
mError.clear();
// return no features, just the total count
const QUrl url = parseUrl( QUrl( QStringLiteral( "%1?$top=0&$count=true" ).arg( mEntityBaseUri ) ) );
QString countUri = QStringLiteral( "%1?$top=0&$count=true" ).arg( mEntityBaseUri );
const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( mGeometryType );
if ( !typeFilter.isEmpty() )
countUri += '&' + typeFilter;
const QUrl url = parseUrl( QUrl( countUri ) );
QNetworkRequest request( url );
QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsSharedData" ) );
@ -209,6 +214,9 @@ bool QgsSensorThingsSharedData::getFeature( QgsFeatureId id, QgsFeature &f, QgsF
{
locker.changeMode( QgsReadWriteLocker::Write );
mNextPage = QStringLiteral( "%1?$top=%2&$count=false" ).arg( mEntityBaseUri ).arg( mMaximumPageSize );
const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( mGeometryType );
if ( !typeFilter.isEmpty() )
mNextPage += '&' + typeFilter;
}
locker.unlock();
@ -245,7 +253,8 @@ QgsFeatureIds QgsSensorThingsSharedData::getFeatureIdsInExtent( const QgsRectang
}
// TODO -- is using 'geography' always correct here?
QString queryUrl = !thisPage.isEmpty() ? thisPage : QStringLiteral( "%1?$filter=geo.intersects(%2, geography'%3')&$top=%4&$count=false" ).arg( mEntityBaseUri, mGeometryField, extent.asWktPolygon() ).arg( mMaximumPageSize );
const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( mGeometryType );
QString queryUrl = !thisPage.isEmpty() ? thisPage : QStringLiteral( "%1?$filter=geo.intersects(%2, geography'%3')&$top=%4&$count=false%5" ).arg( mEntityBaseUri, mGeometryField, extent.asWktPolygon() ).arg( mMaximumPageSize ).arg( typeFilter.isEmpty() ? QString() : ( QStringLiteral( "&" ) + typeFilter ) );
if ( thisPage.isEmpty() && mCachedExtent.intersects( extentGeom ) )
{

View File

@ -16,6 +16,7 @@
#include "qgssensorthingsutils.h"
#include "qgsfield.h"
#include "qgsfields.h"
#include "qgswkbtypes.h"
Qgis::SensorThingsEntity QgsSensorThingsUtils::stringToEntity( const QString &type )
{
@ -217,3 +218,21 @@ bool QgsSensorThingsUtils::entityTypeHasGeometry( Qgis::SensorThingsEntity type
}
BUILTIN_UNREACHABLE
}
QString QgsSensorThingsUtils::filterForWkbType( Qgis::WkbType type )
{
switch ( QgsWkbTypes::geometryType( type ) )
{
case Qgis::GeometryType::Point:
return QStringLiteral( "$filter=location/type eq 'Point'" );
case Qgis::GeometryType::Polygon:
return QStringLiteral( "$filter=location/type eq 'Polygon'" );
case Qgis::GeometryType::Line:
// TODO -- confirm
return QStringLiteral( "$filter=location/type eq 'LineString'" );
case Qgis::GeometryType::Unknown:
case Qgis::GeometryType::Null:
break;
}
return QString();
}

View File

@ -68,6 +68,12 @@ class CORE_EXPORT QgsSensorThingsUtils
*/
static bool entityTypeHasGeometry( Qgis::SensorThingsEntity type );
/**
* Returns a filter string which restricts results to those matching the specified
* WKB \a type.
*/
static QString filterForWkbType( Qgis::WkbType type );
};
#endif // QGSSENSORTHINGSUTILS_H

View File

@ -28,9 +28,13 @@ from qgis.testing import start_app, QgisTestCase
def sanitize(endpoint, x):
if x.startswith("/Locations"):
x = x[len("/Locations"):]
endpoint = endpoint + "_Locations"
for prefix in ('/Locations',
'/HistoricalLocations',
'/Things',
'/FeaturesOfInterest'):
if x.startswith(prefix):
x = x[len(prefix):]
endpoint = endpoint + "_" + prefix[1:]
if len(endpoint + x) > 150:
ret = endpoint + hashlib.md5(x.encode()).hexdigest()
@ -74,6 +78,23 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
super().tearDownClass()
def test_filter_for_wkb_type(self):
self.assertEqual(
QgsSensorThingsUtils.filterForWkbType(Qgis.WkbType.Point), "$filter=location/type eq 'Point'"
)
self.assertEqual(
QgsSensorThingsUtils.filterForWkbType(Qgis.WkbType.PointZ), "$filter=location/type eq 'Point'"
)
self.assertEqual(
QgsSensorThingsUtils.filterForWkbType(Qgis.WkbType.Polygon), "$filter=location/type eq 'Polygon'"
)
# TODO -- there is NO documentation on what the type must be for line filtering,
# and I can't find any public servers with line geometries to test with!
# Find some way to confirm if this is 'Line' or 'LineString' or ...
self.assertEqual(
QgsSensorThingsUtils.filterForWkbType(Qgis.WkbType.LineString), "$filter=location/type eq 'LineString'"
)
def test_utils_string_to_entity(self):
self.assertEqual(
QgsSensorThingsUtils.stringToEntity("x"), Qgis.SensorThingsEntity.Invalid
@ -230,7 +251,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
with open(
sanitize(endpoint, "/Locations?$top=0&$count=true"),
sanitize(endpoint, "/Locations?$top=0&$count=true&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f:
@ -449,14 +470,14 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
with open(
sanitize(endpoint, "/Locations?$top=0&$count=true"),
sanitize(endpoint, "/Locations?$top=0&$count=true&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f:
f.write("""{"@iot.count":3,"value":[]}""")
with open(
sanitize(endpoint, "/Locations?$top=2&$count=false"),
sanitize(endpoint, "/Locations?$top=2&$count=false&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f:
@ -504,7 +525,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
}
],
"@iot.nextLink": "endpoint/Locations?$top=2&$skip=2"
"@iot.nextLink": "endpoint/Locations?$top=2&$skip=2&$filter=location/type eq 'Point'"
}
""".replace(
"endpoint", "http://" + endpoint
@ -512,7 +533,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
with open(
sanitize(endpoint, "/Locations?$top=2&$skip=2"),
sanitize(endpoint, "/Locations?$top=2&$skip=2&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f:
@ -626,14 +647,14 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
with open(
sanitize(endpoint, "/Locations?$top=0&$count=true"),
sanitize(endpoint, "/Locations?$top=0&$count=true&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f:
f.write("""{"@iot.count":3,"value":[]}""")
with open(
sanitize(endpoint, "/Locations?$top=2&$count=false"),
sanitize(endpoint, "/Locations?$top=2&$count=false&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f:
@ -681,7 +702,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
}
],
"@iot.nextLink": "endpoint/Locations?$top=2&$skip=2"
"@iot.nextLink": "endpoint/Locations?$top=2&$skip=2&$filter=location/type eq 'Point'"
}
""".replace(
"endpoint", "http://" + endpoint
@ -689,7 +710,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
with open(
sanitize(endpoint, "/Locations?$top=2&$skip=2"),
sanitize(endpoint, "/Locations?$top=2&$skip=2&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f:
@ -724,7 +745,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
with open(
sanitize(endpoint, "/Locations?$filter=geo.intersects(location, geography'POLYGON((1 0, 10 0, 10 80, 1 80, 1 0))')&$top=2&$count=false"),
sanitize(endpoint, "/Locations?$filter=geo.intersects(location, geography'POLYGON((1 0, 10 0, 10 80, 1 80, 1 0))')&$top=2&$count=false&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f:
@ -777,7 +798,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
with open(
sanitize(endpoint, "/Locations?$filter=geo.intersects(location, geography'POLYGON((10 0, 20 0, 20 80, 10 80, 10 0))')&$top=2&$count=false"),
sanitize(endpoint, "/Locations?$filter=geo.intersects(location, geography'POLYGON((10 0, 20 0, 20 80, 10 80, 10 0))')&$top=2&$count=false&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f:
@ -1841,14 +1862,14 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
with open(
sanitize(endpoint, "/FeaturesOfInterest?$top=0&$count=true"),
sanitize(endpoint, "/FeaturesOfInterest?$top=0&$count=true&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f:
f.write("""{"@iot.count":3,"value":[]}""")
with open(
sanitize(endpoint, "/FeaturesOfInterest?$top=2&$count=false"),
sanitize(endpoint, "/FeaturesOfInterest?$top=2&$count=false&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f:
@ -1901,7 +1922,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
}
],
"@iot.nextLink": "endpoint/FeaturesOfInterest?$top=2&$skip=2"
"@iot.nextLink": "endpoint/FeaturesOfInterest?$top=2&$skip=2&$filter=location/type eq 'Point'"
}
""".replace(
"endpoint", "http://" + endpoint
@ -1909,7 +1930,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
with open(
sanitize(endpoint, "/FeaturesOfInterest?$top=2&$skip=2"),
sanitize(endpoint, "/FeaturesOfInterest?$top=2&$skip=2&$filter=location/type eq 'Point'"),
"wt",
encoding="utf8",
) as f: