Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

cameraForLatLngs fits to untilted bounds while in perspective view #2259

Open
ansis opened this issue Sep 4, 2015 · 11 comments
Open

cameraForLatLngs fits to untilted bounds while in perspective view #2259

ansis opened this issue Sep 4, 2015 · 11 comments
Labels
bug Core The cross-platform C++ core, aka mbgl

Comments

@ansis
Copy link
Contributor

ansis commented Sep 4, 2015

cc @1ec5

@1ec5
Copy link
Contributor

1ec5 commented Oct 16, 2015

To recap: fitBounds does not account for the trapezoidal shape of the bounds when the map is tilted. As a result, the map’s camera fails to round-trip correctly (tending to zoom out) when the map starts out tilted.

@1ec5 1ec5 changed the title fix fitBounds in perspective view cameraForLatLngs fits to untilted bounds while in perspective view Oct 20, 2015
@1ec5 1ec5 assigned adam-mapbox and unassigned 1ec5 Oct 20, 2015
@1ec5
Copy link
Contributor

1ec5 commented Oct 20, 2015

This issue tracks the work to make Map::cameraForLatLngs() visually fit to the passed-in bounds when the map is tilted. To illustrate the problem, I rigged iosapp to:

  1. Start out unrotated and untilted, and note the map view’s visibleCoordinateBounds
  2. Add a polygon annotation that covers those bounds
  3. Tilt the map by 60° (by taking the current camera, adjusting its pitch, and setting camera to that modified camera)
  4. Pass the bounds from (1) into -[MGLMapView setVisibleCoordinateBounds:animated:]

The result is that the map is zoomed much farther out than it should be:

trapezoid

@ansis
Copy link
Contributor Author

ansis commented Oct 23, 2015

There are a couple possible behaviours in some cases. Imagine a box that's a horizontal line. It has a large width and small height. You could:

  • show the map at the highest possible zoom by putting the box at the top edge of the map
  • or you could vertically center the box and use a lower zoom level

Which is better? I prefer the second.

@1ec5
Copy link
Contributor

1ec5 commented Oct 23, 2015

Absent tilt, you can think of the current behavior as a) centering the shape, then b) zooming to fit (but not panning or rotating). So I’d prefer (2) as well. In the future, we could choose an optimal rotation and pitch for a given object, similar to what the Bing Maps API does, but that would have to be a separate method.

@1ec5
Copy link
Contributor

1ec5 commented Jan 27, 2016

On iOS, when the map is tilted and you enter targeted course tracking mode (in which both the user location and a target coordinate are fitted to opposite ends of the map view), cameraForLatLngs() underzooms, so the map bounces back and forth as it iteratively settles on the correct bounds.

@jfirebaugh jfirebaugh removed the P2 label Mar 24, 2016
@1ec5 1ec5 added the Core The cross-platform C++ core, aka mbgl label Apr 22, 2016
@cammace
Copy link
Contributor

cammace commented Jun 29, 2016

Confirmed an issue on Android as well. When the map is tilted and you try fitting the camera within a bounds, it doesn't take into account the tilt.

LatLngBounds latLngBounds = new LatLngBounds.Builder()
                                .include(new LatLng(36.532128, -93.489121)) // Northeast
                                .include(new LatLng(25.837058, -106.646234)) // Southwest
                                .build();

                        mapboxMap.easeCamera(CameraUpdateFactory.newLatLngBounds(latLngBounds, 50), 5000);

Full code can be found in the demo app.

This gif is suppose to fit the entire state of Texas within view but cuts it off only when the map is tilted before the animate camera is called:
ezgif com-video-to-gif 2

cc: @zugaldia @ivovandongen

@altafrm3d
Copy link

following this issue for: CameraUpdateFactory.newLatLngBounds() zooming to greatest possible zoom

@Reflejo
Copy link

Reflejo commented Jul 19, 2017

We were facing a similar challenge; this is: How to transform the camera such that the bounds are centered on screen at the greatest possible zoom level while the map is tilted. I'm gonna share the solution I came up with in case is of any use to anyone. My example is using google maps (sorry!) and implemented on iOS but I think it might be useful regardless.

Warning: For simplicity I'm assuming the map bearing is 0 (facing north), solving it for other bearings would just mean to add one more rotation matrix on y

Please let me know if there is a simpler solution I didn't see :)

EDIT: Fixed the math to use meters per pixel instead of calculate the % which leads to an almost pixel perfect implementation (see the preview) and moved the implementation to an example project. I also added the math for calculating the height fit cases

Preview

zoom

Implementation (example)

See this view controller

Explanation

The way I approached this problem is by transformation matrices. Similar to what CATransform3D does with CALayers. So the way I formulated the problem is: assuming the rect (coord bounds) is centered on the screen at the greatest possible zoom with pitch=0 what transformation we need to apply so after applying the rotation, the rect ends up vertically centered and the resulting width equals the map width.

So the transformation needed would be: (Translation x Scale x Rotation x Perspective); where:

solution-transform-matrix

... t and s are the values we need to solve our system of equations for, p can be eye balled to 1 / -(view.height * 1.875) and a is the pitch angle in degrees. Note that the z dimension is zero on the scale matrix which will simplify the math a bit and we don't need the z dimension here.

One more thing before moving on: because the rotation point needs to be in the center, we need to translate the matrix before transforming it (and after), so the resulting system would be:

solution-transform-matrix-full

... where h is the height of the bounds in pixels. After resolving this matrix we get:

solution-matrix

Next in order to get the projection of a point from the non-tilted rect to the tilted rect we use a function P(x, y) which looks like:

solution-px-and-py

Now the base equations for solving t and s are:

To fit to the width

  1. The top-left point of the new transformed rect (in 2D) should match (height / 2 - newRectHeight / 2) to be in the center.
  2. The width of the new transformed rect should match the container view.

solution-system-width

.. where:

  • w is the width of the rect to be transformed
  • W is the width of the container view
  • h is the height of the rect to be transformed

Now if we solve the system of equations with all this information we obtain:

solution-for-t-s-width

To fit to the height

  1. The top-left point of new transformed rect should be (0, 0)
  2. The height of the transformed rect should match the container view's height

solution-system-height

... where h is the height of the rect.

solution-for-t-s-height

These four equations is all the math we need to calculate the translation and the scale values. With this we can apply the scale to the bounds distance and calculate the zoom and offset the center using t (see implementation)

@d-prukop
Copy link
Contributor

d-prukop commented Oct 2, 2018

see #13007. This may work for many of these cases.

@1ec5
Copy link
Contributor

1ec5 commented Oct 3, 2018

I don’t think #13007 addresses this issue at all. mbgl::Map::cameraForGeometry() is just a convenience wrapper for cameraForLatLngs(), so it suffers from the same inaccuracy when the map is tilted.

@stale
Copy link

stale bot commented Apr 1, 2019

This issue has been automatically detected as stale because it has not had recent activity and will be archived. Thank you for your contributions.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Core The cross-platform C++ core, aka mbgl
Projects
None yet
Development

No branches or pull requests

9 participants