diff options
author | Shawn Rutledge <shawn.rutledge@qt.io> | 2022-11-15 20:32:32 +0100 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2023-01-28 22:56:11 +0100 |
commit | eaab46307c04004649eaa481ff9ba35972d9b967 (patch) | |
tree | 0ec21072cd833af628066893016d7486385df226 /examples | |
parent | 4b81f1a34c6936b7b5a752ed4555f65847b13337 (diff) | |
download | qtlocation-eaab46307c04004649eaa481ff9ba35972d9b967.tar.gz |
Add MapView
This has nearly complete functionality now.
A couple of approaches were possible: one way is to have the Map larger
than the MapView, so that the MapView defines a viewport. This allows
the handlers to function more "normally": DragHandler already knows how
to move the Map in the viewport, etc. But then you can pan off the edge
of the Map; so we needed a recenter() function to calculate and set the
map.center property, and call that at the right times. This needs to be
done when the MapView is resized too, and that turned out to be tricky
to get right. Another advantage though would be that we could ensure Map
doesn't re-generate geometry any more often than necessary: small
changes to the center and scale of the map would often merely change one
QSGTransformNode. We could still try this approach again later on, but
perhaps Map should be doing more of the work to make it possible; and
the new ItemObservesViewport flag ought to be useful.
But for now, we do it the existing way: Map does its own viewporting.
Thus, we assume that Map is optimized to limit geometry re-generation
internally. In practice, redrawing while executing a pinch gesture feels
fast enough.
One of the main reasons we needed the recent changes in handlers is to
get deltas. We cannot use bindings directly from handler properties to
Map properties, because there are multiple ways to modify each property
(you can zoom by pinch or wheel in MapView, and probably via a keyboard
shortcut in the application), so we need to increment the zoomLevel and
bearing properties rather than binding them. When it comes to panning:
instead of a property, Map has a pan(dx, dy) invokable function; so we
call that in response to the DragHandler.translationChanged signal,
which now carries a delta-vector argument. The alignCoordinateToPoint()
function turned out to be ideal to make the pinch gesture zoom into and
rotate the map around the centroid (the point between the touchpoints).
Since we call that function when either the rotation or scale changes,
we do not need an onTranslationChanged(), because you can't do a pinch
gesture that only pans without also changing scale and rotation
slightly. All three signals are firing constantly, so handling two of
them is enough. The Vector3dAnimation turned out to be a good fit to get
flicking momentum (let the panning continue a little while after the
finger is released); needing to use the pan() function here is a little
clumsy, but we can live with it. Handlers and Animations would both
prefer to set properties directly. But if there were a property, it
would tend to have type QVector2D or QPointF, and the Vector3dAnimation
wouldn't know how to set it anyway (but that could be hacked in, or we
could write a Vector2dAnimation).
Calculating the limits for zooming seems to be tricky:
Map.minimumZoomLevel is zero, but in practice the minimum zoom depends
on the size (because we cannot zoom out so far that the map no longer
fills the viewport, but if the viewport is smaller, then you can zoom
out further). So PinchHandler currently limits the max zoom fairly well,
but when you try to zoom out too far, it is Map rather than PinchHandler
that applies its own runtime limit. That makes
PinchHandler.persistentScale useless; but now PinchHandler applies only
incremental zoom deltas, so it doesn't matter. But WheelHandler cannot
apply limits on its own, so currently it lets you zoom in too far. Map
stops you from zooming past level 30, which is strange, since it already
knows that OSM maps are limited to level 18. So either we need to figure
out how to calculate both the min and max accurately so that we can
apply BoundaryRule (which will also replace the use of PinchHandler's
own limits, and will depend on a fix for PinchHandler to work with
BoundaryRule), or we can get Map to enforce the lower limit: 18 instead
of 30. A little bit of zooming beyond 18 is ok (for example to 20), but
if you go even further, the rendering suddenly disappears. This could
be done in a followup patch, and a couple of autotests need fixing then.
The incremental zooming is treated as base-2-logarithmmic, although
that's an approximation: https://wiki.openstreetmap.org/wiki/Zoom_levels
tells us that sometimes one zoom level corresponds exactly to
zooming 2X, but going from zoom level 16 to 15 is an 8:15 ratio. It's
close enough to feel smooth anyway; and it turns out that Map is
rendering fractional zoom levels well enough already. If that were not
the case, we'd need to bring the Item.scale property into play.
Now that the Map is the same size as the MapView, we have a choice
whether the root of MapView should be a wrapper Item or not.
The root could be Map itself, with handlers inside; the upside would be
that all Map properties are left exposed. The downsides would be losing
the opportunity to go back to the other architecture later on (with the
root defining a viewport, and rendering a larger map inside, but
re-rendering less often), and losing the opportunity to make the view's
minimumZoomLevel and maximumZoomLevel different than those in Map
itself. As explained above, minimumZoomLevel should depend on viewport
size. So perhaps it's better to keep it like this: we have control over
which Map properties to expose directly, and for the rest, the user can
bind to things like mapview.map.center instead of mapview.center.
The geojson example is updated to use MapView, whereas minimal_map
applies its own handlers to the Map. Other examples still need updating.
[ChangeLog][MapView] MapView is the interactive replacement for the Map
item in earlier releases. It wraps a Map instance with pointer handlers
to make it interactive.
Pick-to: 6.5
Fixes: QTBUG-68108
Change-Id: Ibf6bcf71fa7588fcf8cf117e213f35cebd105639
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Diffstat (limited to 'examples')
-rw-r--r-- | examples/location/geojson_viewer/main.qml | 30 | ||||
-rw-r--r-- | examples/location/minimal_map/main.qml | 42 |
2 files changed, 59 insertions, 13 deletions
diff --git a/examples/location/geojson_viewer/main.qml b/examples/location/geojson_viewer/main.qml index 85d0e07d..2aab296e 100644 --- a/examples/location/geojson_viewer/main.qml +++ b/examples/location/geojson_viewer/main.qml @@ -63,7 +63,10 @@ ApplicationWindow { width: 1024 height: 1024 menuBar: mainMenu - title: qsTr("GeoJSON Viewer") + title: qsTr("GeoJSON Viewer: ") + view.map.center + + " zoom " + view.map.zoomLevel.toFixed(3) + + " min " + view.map.minimumZoomLevel + + " max " + view.map.maximumZoomLevel FileDialog { visible: false @@ -147,23 +150,26 @@ ApplicationWindow { } Shortcut { - sequence: "Ctrl+P" - onActivated: { - - console.log("Center : QtPositioning.coordinate(",map.center.latitude,",",map.center.longitude,")") - console.log("Zoom : ",map.zoomLevel) - } + enabled: view.map.zoomLevel < view.map.maximumZoomLevel + sequence: StandardKey.ZoomIn + onActivated: view.map.zoomLevel = Math.round(view.map.zoomLevel + 1) + } + Shortcut { + enabled: view.map.zoomLevel > view.map.minimumZoomLevel + sequence: StandardKey.ZoomOut + onActivated: view.map.zoomLevel = Math.round(view.map.zoomLevel - 1) } - Map { - id: map + MapView { + id: view anchors.fill: parent - center: QtPositioning.coordinate(43.59, 13.50) // Starting coordinates: Ancona, the city where I am studying :) - plugin: Plugin { name: "osm" } - zoomLevel: 4 + map.center: QtPositioning.coordinate(43.59, 13.50) // Ancona, Italy + map.plugin: Plugin { name: "osm" } + map.zoomLevel: 4 MapItemView { id: miv + parent: view.map model: geoJsoner.model delegate: GeoJsonDelegate { } diff --git a/examples/location/minimal_map/main.qml b/examples/location/minimal_map/main.qml index 92230f76..be78f00d 100644 --- a/examples/location/minimal_map/main.qml +++ b/examples/location/minimal_map/main.qml @@ -49,7 +49,6 @@ ****************************************************************************/ import QtQuick -import QtQuick.Window import QtLocation import QtPositioning @@ -57,6 +56,8 @@ Window { width: Qt.platform.os == "android" ? Screen.width : 512 height: Qt.platform.os == "android" ? Screen.height : 512 visible: true + title: map.center + " zoom " + map.zoomLevel.toFixed(3) + + " min " + map.minimumZoomLevel + " max " + map.maximumZoomLevel Plugin { id: mapPlugin @@ -69,9 +70,48 @@ Window { } Map { + id: map anchors.fill: parent plugin: mapPlugin center: QtPositioning.coordinate(59.91, 10.75) // Oslo zoomLevel: 14 + property geoCoordinate startCentroid + + PinchHandler { + id: pinch + target: null + onActiveChanged: if (active) { + map.startCentroid = map.toCoordinate(pinch.centroid.position, false) + } + onScaleChanged: (delta) => { + map.zoomLevel += Math.log2(delta) + map.alignCoordinateToPoint(map.startCentroid, pinch.centroid.position) + } + onRotationChanged: (delta) => { + map.bearing -= delta + map.alignCoordinateToPoint(map.startCentroid, pinch.centroid.position) + } + grabPermissions: PointerHandler.TakeOverForbidden + } + WheelHandler { + id: wheel + rotationScale: 1/120 + property: "zoomLevel" + } + DragHandler { + id: drag + target: null + onTranslationChanged: (delta) => map.pan(-delta.x, -delta.y) + } + Shortcut { + enabled: map.zoomLevel < map.maximumZoomLevel + sequence: StandardKey.ZoomIn + onActivated: map.zoomLevel = Math.round(map.zoomLevel + 1) + } + Shortcut { + enabled: map.zoomLevel > map.minimumZoomLevel + sequence: StandardKey.ZoomOut + onActivated: map.zoomLevel = Math.round(map.zoomLevel - 1) + } } } |