Skip to content

Commit

Permalink
[OAPIF] Fix wrong extent when not advertized in collection metadata a…
Browse files Browse the repository at this point in the history
…nd storageCrs != CRS84, and fix potential infinite feature query when featureCount not advertized by server

Fixes https://lists.osgeo.org/pipermail/qgis-user/2023-September/053400.html
  • Loading branch information
rouault authored and nyalldawson committed Oct 3, 2023
1 parent e81e5e8 commit 941477b
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 2 deletions.
19 changes: 18 additions & 1 deletion src/providers/wfs/oapif/qgsoapifprovider.cpp
Expand Up @@ -224,6 +224,21 @@ bool QgsOapifProvider::init()
if ( mShared->mCapabilityExtent.isNull() )
{
mShared->mCapabilityExtent = itemsRequest.bbox();
if ( !mShared->mCapabilityExtent.isNull() )
{
QgsCoordinateReferenceSystem defaultCrs =
QgsCoordinateReferenceSystem::fromOgcWmsCrs(
QgsOapifProvider::OAPIF_PROVIDER_DEFAULT_CRS );
if ( defaultCrs != mShared->mSourceCrs )
{
QgsCoordinateTransform ct( defaultCrs, mShared->mSourceCrs, transformContext() );
ct.setBallparkTransformsAreAppropriate( true );
QgsDebugMsgLevel( "before ext:" + mShared->mCapabilityExtent.toString(), 4 );
mShared->mCapabilityExtent = ct.transformBoundingBox( mShared->mCapabilityExtent );
QgsDebugMsgLevel( "after ext:" + mShared->mCapabilityExtent.toString(), 4 );
}
}

}

mShared->mFields = itemsRequest.fields();
Expand Down Expand Up @@ -261,12 +276,14 @@ long long QgsOapifProvider::featureCount() const
QgsFeature f;
QgsFeatureRequest request;
request.setNoAttributes();
constexpr int MAX_FEATURES = 1000;
request.setLimit( MAX_FEATURES + 1 );
auto iter = getFeatures( request );
long long count = 0;
bool countExact = true;
while ( iter.nextFeature( f ) )
{
if ( count == 1000 ) // to avoid too long processing time
if ( count == MAX_FEATURES ) // to avoid too long processing time
{
countExact = false;
break;
Expand Down
74 changes: 73 additions & 1 deletion tests/src/python/test_provider_oapif.py
Expand Up @@ -25,6 +25,7 @@
QgsRectangle,
QgsFeatureRequest,
QgsApplication,
QgsGeometry,
QgsSettings,
QgsBox3d
)
Expand Down Expand Up @@ -129,6 +130,8 @@ def add_params(x, y):
}
}
}
if bbox is None:
del collection["extent"]
if storageCrs:
collection["storageCrs"] = storageCrs
if crsList:
Expand Down Expand Up @@ -874,9 +877,78 @@ def testCRS2056(self):

self.assertEqual(source.sourceCrs().authid(), 'OGC:CRS84')

def testFeatureCountFallbackAndNoBboxInCollection(self):

# On Windows we must make sure that any backslash in the path is
# replaced by a forward slash so that QUrl can process it
basetestpath = tempfile.mkdtemp().replace('\\', '/')
endpoint = basetestpath + '/fake_qgis_http_endpoint_feature_count_fallback'

create_landing_page_api_collection(endpoint, storageCrs="http://www.opengis.net/def/crs/EPSG/0/2056", bbox=None)

items = {
"type": "FeatureCollection",
"features": [],
"links": [
# should not be hit
{"href": "http://" + endpoint + "/next_page", "rel": "next"}
]
}
for i in range(10):
items["features"].append({"type": "Feature", "id": f"feat.{i}",
"properties": {},
"geometry": {"type": "Point", "coordinates": [23, 63]}})

# first items
with open(sanitize(endpoint, '/collections/mycollection/items?limit=1&' + ACCEPT_ITEMS), 'wb') as f:
f.write(json.dumps(items).encode('UTF-8'))

# first items
with open(sanitize(endpoint, '/collections/mycollection/items?limit=10&' + ACCEPT_ITEMS), 'wb') as f:
f.write(json.dumps(items).encode('UTF-8'))

# real page

items = {
"type": "FeatureCollection",
"features": [],
"links": [
# should not be hit
{"href": "http://" + endpoint + "/next_page", "rel": "next"}
]
}
for i in range(1001):
items["features"].append({"type": "Feature", "id": f"feat.{i}",
"properties": {},
"geometry": None})

with open(sanitize(endpoint, '/collections/mycollection/items?limit=1000&crs=http://www.opengis.net/def/crs/EPSG/0/2056&' + ACCEPT_ITEMS), 'wb') as f:
f.write(json.dumps(items).encode('UTF-8'))

# Create test layer

vl = QgsVectorLayer("url='http://" + endpoint + "' typename='mycollection'", 'test', 'OAPIF')
assert vl.isValid()
source = vl.dataProvider()

# Extent got from first fetched features
reference = QgsGeometry.fromRect(
QgsRectangle(3415684, 3094884,
3415684, 3094884))
vl_extent = QgsGeometry.fromRect(vl.extent())
assert QgsGeometry.compare(vl_extent.asPolygon()[0], reference.asPolygon()[0],
10), f'Expected {reference.asWkt()}, got {vl_extent.asWkt()}'

app_log = QgsApplication.messageLog()
# signals should be emitted by application log
app_spy = QSignalSpy(app_log.messageReceived)

self.assertEqual(source.featureCount(), 1000)

self.assertEqual(len(app_spy), 0, list(app_spy))

# GDAL 3.5.0 is required since it is the first version that tags "complex"
# fields as OFSTJSON

@unittest.skipIf(int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(3, 5, 0), "GDAL 3.5.0 required")
def testFeatureComplexAttribute(self):

Expand Down

0 comments on commit 941477b

Please sign in to comment.