Add optional viewport property for geocoder results

Allows specifying an optional recommended viewport bounds for
displaying the geocode result (e.g. the canvas extent to zoom
to for showing the results)
This commit is contained in:
Nyall Dawson 2020-11-03 08:50:48 +10:00
parent e70caeb3e1
commit cbaeb99a72
7 changed files with 95 additions and 3 deletions

View File

@ -99,6 +99,32 @@ Sets the coordinate reference system for the calculated :py:func:`~QgsGeocoderRe
.. seealso:: :py:func:`crs` .. seealso:: :py:func:`crs`
.. seealso:: :py:func:`geometry` .. seealso:: :py:func:`geometry`
%End
QgsRectangle viewport() const;
%Docstring
Returns the suggested viewport for the result, which reflects a recommended
map extent for displaying the result.
This is an optional property, and will return a null rectangle if a recommended viewport
is not available (or not appropriate).
The viewport CRS will match the CRS of :py:func:`~QgsGeocoderResult.geometry`, and can be retrieved via the :py:func:`~QgsGeocoderResult.crs` method.
.. seealso:: :py:func:`setViewport`
%End
void setViewport( const QgsRectangle &viewport );
%Docstring
Sets the suggested ``viewport`` for the result, which reflects a recommended
map extent for displaying the result.
This is an optional property, and can be set to a null rectangle if a recommended viewport
is not available (or not appropriate).
The viewport CRS must match the CRS of geometry()d.
.. seealso:: :py:func:`viewport`
%End %End
QVariantMap additionalAttributes() const; QVariantMap additionalAttributes() const;

View File

@ -76,6 +76,7 @@ QgsGeocoderResult QgsAbstractGeocoderLocatorFilter::locatorResultToGeocoderResul
attrs.value( QStringLiteral( "geom" ) ).value< QgsGeometry >(), attrs.value( QStringLiteral( "geom" ) ).value< QgsGeometry >(),
attrs.value( QStringLiteral( "crs" ) ).value< QgsCoordinateReferenceSystem >() ); attrs.value( QStringLiteral( "crs" ) ).value< QgsCoordinateReferenceSystem >() );
geocodeResult.setAdditionalAttributes( attrs.value( QStringLiteral( "attributes" ) ).toMap() ); geocodeResult.setAdditionalAttributes( attrs.value( QStringLiteral( "attributes" ) ).toMap() );
geocodeResult.setViewport( attrs.value( QStringLiteral( "viewport" ) ).value< QgsRectangle >() );
return geocodeResult; return geocodeResult;
} }
@ -84,6 +85,7 @@ QgsLocatorResult QgsAbstractGeocoderLocatorFilter::geocoderResultToLocatorResult
QVariantMap attrs; QVariantMap attrs;
attrs.insert( QStringLiteral( "identifier" ), result.identifier() ); attrs.insert( QStringLiteral( "identifier" ), result.identifier() );
attrs.insert( QStringLiteral( "geom" ), result.geometry() ); attrs.insert( QStringLiteral( "geom" ), result.geometry() );
attrs.insert( QStringLiteral( "viewport" ), result.viewport() );
attrs.insert( QStringLiteral( "crs" ), result.crs() ); attrs.insert( QStringLiteral( "crs" ), result.crs() );
attrs.insert( QStringLiteral( "attributes" ), result.additionalAttributes() ); attrs.insert( QStringLiteral( "attributes" ), result.additionalAttributes() );
return QgsLocatorResult( this, result.identifier(), attrs ); return QgsLocatorResult( this, result.identifier(), attrs );

View File

@ -107,6 +107,32 @@ class CORE_EXPORT QgsGeocoderResult
*/ */
void setCrs( const QgsCoordinateReferenceSystem &crs ) { mCrs = crs; } void setCrs( const QgsCoordinateReferenceSystem &crs ) { mCrs = crs; }
/**
* Returns the suggested viewport for the result, which reflects a recommended
* map extent for displaying the result.
*
* This is an optional property, and will return a null rectangle if a recommended viewport
* is not available (or not appropriate).
*
* The viewport CRS will match the CRS of geometry(), and can be retrieved via the crs() method.
*
* \see setViewport()
*/
QgsRectangle viewport() const { return mViewport; }
/**
* Sets the suggested \a viewport for the result, which reflects a recommended
* map extent for displaying the result.
*
* This is an optional property, and can be set to a null rectangle if a recommended viewport
* is not available (or not appropriate).
*
* The viewport CRS must match the CRS of geometry()d.
*
* \see viewport()
*/
void setViewport( const QgsRectangle &viewport ) { mViewport = viewport; }
/** /**
* Contains additional attributes generated during the geocode, * Contains additional attributes generated during the geocode,
* which may be added to features being geocoded. * which may be added to features being geocoded.
@ -133,6 +159,7 @@ class CORE_EXPORT QgsGeocoderResult
QString mIdentifier; QString mIdentifier;
QgsGeometry mGeometry; QgsGeometry mGeometry;
QgsCoordinateReferenceSystem mCrs; QgsCoordinateReferenceSystem mCrs;
QgsRectangle mViewport;
QVariantMap mAdditionalAttributes; QVariantMap mAdditionalAttributes;
}; };

View File

@ -243,6 +243,18 @@ QgsGeocoderResult QgsGoogleMapsGeocoder::jsonToResult( const QVariantMap &json )
} }
} }
if ( geometry.contains( QStringLiteral( "viewport" ) ) )
{
const QVariantMap viewport = geometry.value( QStringLiteral( "viewport" ) ).toMap();
const QVariantMap northEast = viewport.value( QStringLiteral( "northeast" ) ).toMap();
const QVariantMap southWest = viewport.value( QStringLiteral( "southwest" ) ).toMap();
res.setViewport( QgsRectangle( southWest.value( QStringLiteral( "lng" ) ).toDouble(),
southWest.value( QStringLiteral( "lat" ) ).toDouble(),
northEast.value( QStringLiteral( "lng" ) ).toDouble(),
northEast.value( QStringLiteral( "lat" ) ).toDouble()
) );
}
res.setAdditionalAttributes( attributes ); res.setAdditionalAttributes( attributes );
return res; return res;
} }

View File

@ -34,10 +34,19 @@ void QgsGeocoderLocatorFilter::handleGeocodeResult( const QgsGeocoderResult &res
{ {
QgsCoordinateTransform ct( result.crs(), mCanvas->mapSettings().destinationCrs(), mCanvas->mapSettings().transformContext() ); QgsCoordinateTransform ct( result.crs(), mCanvas->mapSettings().destinationCrs(), mCanvas->mapSettings().transformContext() );
QgsGeometry g = result.geometry(); QgsGeometry g = result.geometry();
const QgsRectangle viewport = result.viewport();
try try
{ {
g.transform( ct ); QgsRectangle bounds;
QgsRectangle bounds = g.boundingBox(); if ( viewport.isNull() )
{
g.transform( ct );
bounds = g.boundingBox();
}
else
{
bounds = ct.transformBoundingBox( viewport );
}
mCanvas->zoomToFeatureExtent( bounds ); mCanvas->zoomToFeatureExtent( bounds );
mCanvas->flashGeometries( QList< QgsGeometry >() << g ); mCanvas->flashGeometries( QList< QgsGeometry >() << g );

View File

@ -22,7 +22,8 @@ from qgis.core import (
QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem,
QgsLocatorContext, QgsLocatorContext,
QgsFeedback, QgsFeedback,
QgsGeocoderContext QgsGeocoderContext,
QgsRectangle
) )
from qgis.gui import ( from qgis.gui import (
QgsMapCanvas, QgsMapCanvas,
@ -55,6 +56,7 @@ class TestGeocoder(QgsGeocoderInterface):
result2 = QgsGeocoderResult('res 2', QgsGeometry.fromPointXY(QgsPointXY(13, 14)), result2 = QgsGeocoderResult('res 2', QgsGeometry.fromPointXY(QgsPointXY(13, 14)),
QgsCoordinateReferenceSystem('EPSG:3857')) QgsCoordinateReferenceSystem('EPSG:3857'))
result2.setAdditionalAttributes({'d': 456}) result2.setAdditionalAttributes({'d': 456})
result2.setViewport(QgsRectangle(1, 2, 3, 4))
return [result1, result2] return [result1, result2]
return [] return []
@ -93,6 +95,7 @@ class TestQgsGeocoderLocatorFilter(unittest.TestCase):
self.assertEqual(geocode_result.geometry().asWkt(), 'Point (1 2)') self.assertEqual(geocode_result.geometry().asWkt(), 'Point (1 2)')
self.assertEqual(geocode_result.crs().authid(), 'EPSG:4326') self.assertEqual(geocode_result.crs().authid(), 'EPSG:4326')
self.assertEqual(geocode_result.additionalAttributes(), {'b': 123, 'c': 'xyz'}) self.assertEqual(geocode_result.additionalAttributes(), {'b': 123, 'c': 'xyz'})
self.assertTrue(geocode_result.viewport().isNull())
# two possible results # two possible results
filter.fetchResults('b', context, feedback) filter.fetchResults('b', context, feedback)
@ -105,12 +108,14 @@ class TestQgsGeocoderLocatorFilter(unittest.TestCase):
self.assertEqual(geocode_result.geometry().asWkt(), 'Point (11 12)') self.assertEqual(geocode_result.geometry().asWkt(), 'Point (11 12)')
self.assertEqual(geocode_result.crs().authid(), 'EPSG:4326') self.assertEqual(geocode_result.crs().authid(), 'EPSG:4326')
self.assertEqual(geocode_result.additionalAttributes(), {'b': 123, 'c': 'xyz'}) self.assertEqual(geocode_result.additionalAttributes(), {'b': 123, 'c': 'xyz'})
self.assertTrue(geocode_result.viewport().isNull())
self.assertEqual(res2.displayString, 'res 2') self.assertEqual(res2.displayString, 'res 2')
geocode_result = filter.locatorResultToGeocoderResult(res2) geocode_result = filter.locatorResultToGeocoderResult(res2)
self.assertEqual(geocode_result.identifier(), 'res 2') self.assertEqual(geocode_result.identifier(), 'res 2')
self.assertEqual(geocode_result.geometry().asWkt(), 'Point (13 14)') self.assertEqual(geocode_result.geometry().asWkt(), 'Point (13 14)')
self.assertEqual(geocode_result.crs().authid(), 'EPSG:3857') self.assertEqual(geocode_result.crs().authid(), 'EPSG:3857')
self.assertEqual(geocode_result.additionalAttributes(), {'d': 456}) self.assertEqual(geocode_result.additionalAttributes(), {'d': 456})
self.assertEqual(geocode_result.viewport(), QgsRectangle(1, 2, 3, 4))
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -144,6 +144,16 @@ class TestQgsGeocoderLocatorFilter(unittest.TestCase):
"lng": -83.55521200000001 "lng": -83.55521200000001
}, },
"location_type": "APPROXIMATE", "location_type": "APPROXIMATE",
"viewport": {
"northeast": {
"lat": 42.1,
"lng": -87.7
},
"southwest": {
"lat": 42.0,
"lng": -87.7
}
}
}, },
"place_id": "ChIJeU4e_C2HO4gRRcM6RZ_IPHw", "place_id": "ChIJeU4e_C2HO4gRRcM6RZ_IPHw",
"types": ["locality", "political"] "types": ["locality", "political"]
@ -159,6 +169,7 @@ class TestQgsGeocoderLocatorFilter(unittest.TestCase):
'locality': 'Mountain View', 'location_type': 'APPROXIMATE', 'locality': 'Mountain View', 'location_type': 'APPROXIMATE',
'place_id': 'ChIJeU4e_C2HO4gRRcM6RZ_IPHw', 'postal_code': '94043', 'place_id': 'ChIJeU4e_C2HO4gRRcM6RZ_IPHw', 'postal_code': '94043',
'route': 'Amphitheatre Pkwy', 'street_number': '1600'}) 'route': 'Amphitheatre Pkwy', 'street_number': '1600'})
self.assertEqual(res.viewport(), QgsRectangle(-87.7, 42, -87.7, 42.1))
def test_geocode(self): def test_geocode(self):
geocoder = QgsGoogleMapsGeocoder('my key') geocoder = QgsGoogleMapsGeocoder('my key')