Android maps V2 newLatLngBounds with bearing
Asked Answered
C

3

27

I'm using the new Android maps V2 with this layout:

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:map="http://schemas.android.com/apk/res-auto"
  android:id="@+id/map"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  class="com.google.android.gms.maps.SupportMapFragment"
  map:cameraBearing="270"/>

I'm trying to use the method newLatLngBounds (LatLngBounds bounds, int padding) for zooming and seeing all my markers in the map, however the camera bearing is set to 0.

The description on google developers documentation says:

public static CameraUpdate newLatLngBounds (LatLngBounds bounds, int padding) (...) . The returned CameraUpdate has a bearing of 0 and a tilt of 0. (...)".

How can I change the bearing value?

I tried to set a new bearing programmatically after the call of newLatLngBounds, something like this:

mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100));
mMap.moveCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition.Builder()
.target(mMap.getCameraPosition().target)
.zoom(mMap.getCameraPosition().zoom)
.bearing(270)
.build()));

But when I do this some markers won't show up.

Coco answered 31/1, 2013 at 17:10 Comment(2)
@Mike-Bell No, I didn't. I think it's not possible with the current android map library.Coco
This approach requires the map view to be visible, otherwise the camera's location is default, somewhere in London)Apteryx
C
17

I've got an alternative, I had the same issue. What I'll show is how to convert a bounds to a zoom, and then we'll simply use a CameraPosition.builder() to create the right position.

private static final double LN2 = 0.6931471805599453;
    private static final int WORLD_PX_HEIGHT = 256;
    private static final int WORLD_PX_WIDTH = 256;
    private static final int ZOOM_MAX = 21;

    public int getBoundsZoomLevel(LatLngBounds bounds, int mapWidthPx, int mapHeightPx){

        LatLng ne = bounds.northeast;
        LatLng sw = bounds.southwest;

        double latFraction = (latRad(ne.latitude) - latRad(sw.latitude)) / Math.PI;

        double lngDiff = ne.longitude - sw.longitude;
        double lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;

        double latZoom = zoom(mapHeightPx, WORLD_PX_HEIGHT, latFraction);
        double lngZoom = zoom(mapWidthPx, WORLD_PX_WIDTH, lngFraction);

        int result = Math.min((int)latZoom, (int)lngZoom);
        return Math.min(result, ZOOM_MAX);
    }

    private double latRad(double lat) {
        double sin = Math.sin(lat * Math.PI / 180);
        double radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
        return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
    }
    private double zoom(int mapPx, int worldPx, double fraction) {
        return Math.floor(Math.log(mapPx / worldPx / fraction) / LN2);
    }

Then you can use this code and add a bearing/and or tilt with the bounds:

            LatLngBounds bounds = LatLngBounds.builder()
                    .include(swCorner)
                    .include(neCorner).build();
            CameraPosition cp = new CameraPosition.Builder()
                    .bearing(randomBearing())
                    .tilt(randomTilt())
                    .target(latLng)
                    .zoom(getBoundsZoomLevel(bounds, findViewById(R.id.map).getMeasuredWidth(), findViewById(R.id.map).getMeasuredHeight()))
                    .build();
            CameraUpdate cu = CameraUpdateFactory.newCameraPosition(cp);
            mMap.animateCamera(cu);

This will work (you'll have to change mMap to your actually map variable and the id to your map id).

Good luck! I got the boundZoomLevel from post: How do I determine the zoom level of a LatLngBounds before using map.fitBounds? Go like that answer too if you have time!

Cundiff answered 15/9, 2014 at 17:57 Comment(4)
This is great; thanks! But I wonder two things. I think the WORLD_PX constants are actually 256 dp whereas the view dimensions are retured as px, which could result in mis-scaling. Also, after rotation, the 'corners' of the bounds may be rotated off the map. Ideally, co-ordinate rotation should occur before determining bounds. (And I'm not sure the Math.floor is needed or beneficial.)Kekkonen
Peter is correct, WORLD PX needs to be corrected for density => (int) (GLOBE_WIDTH * context.getResources().getDisplayMetrics().density)Viridity
@Viridity could you clarify, It's been a long time since I wrote this and don't really remember what I was doing. Which PX should be replaced? Both width and height with this variable?Cundiff
i've added a gist with a current working kotlin snipped gist.github.com/kibotu/f9bda6831573e8226fc3298df8ac73beRescript
L
5

yes you can! just use: https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap#animateCamera(com.google.android.gms.maps.CameraUpdate, int, com.google.android.gms.maps.GoogleMap.CancelableCallback)

and implement a CancelableCallback and put in the onFinish() method: https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.CancelableCallback#onFinish()

the code to change the bearing. Of course you will not get a single animation but a chain of two, but it will work!

mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100)/*,DURATION_IN_MS_IF_NEEDED*/,new GoogleMap.CancelableCallback(){
    @Override
    public void onCancel() {
        //DO SOMETHING HERE IF YOU WANT TO REACT TO A USER TOUCH WHILE ANIMATING
    }
    @Override
    public void onFinish() {
        mMap.animateCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition.Builder()
/*.target(mMap.getCameraPosition().target)
.zoom(mMap.getCameraPosition().zoom)*/
.bearing(270)
.build()));
    }
});

I don't think you need target and zoom, i kept them commented out since it should be managed by the previous animation

EDIT Looking better at the API, I have a method that could work (I can't test it for now, please take a look at it: having a LatLngBounds you can get what is the zoom level which best fits the area (it depends on the device dimensions, so you have to get view dimensions at runtime), and then, when you have the zoom (you have also the center of the LatLngBounds), you can do a camera update with one unique animation: https://developers.google.com/android/reference/com/google/android/gms/maps/model/CameraPosition, the constructor allows the set of the values, or you can use the builder as previously seen.

You can find online various ways to do that, for example (just a quick search online obtained a javascript version https://mcmap.net/q/142238/-google-maps-v3-how-to-calculate-the-zoom-level-for-a-given-bounds )

Hope this can help you with your issue!

Lindley answered 3/6, 2015 at 9:58 Comment(9)
The whole point of this question is to create a single map animation that does both thingsVeer
it does both things concatenated. There's no other way to do it simultaneosuly!Lindley
Also I need to see all markers that's why I use newLatLngBound(). If I change bearing and not zoom some markers will be hidden after that animation.Coco
@Coco does the code works if you decomment target and zoom?Lindley
App crashes when we leave target as nullCunningham
you should not pass null target, try to avoid adding ".target()" if you have nullLindley
I did the same but Still Application is crashing for the same reasonCunningham
Are you sure that the error is it? "Application is crashing" means almost nothing, you should check logcat and read the errorsLindley
It will not work because the animations will always bounce from one to another when receiving location updates every few seconds...Husband
B
-4

bad issue on Google's CameraUpdateFactory i solved it with time, tiny timespan!

public void zoomToBounds() {
    LatLngBounds latLngBounds = getBoundsOfInput(inputPoints);
    final int PADDING = 20;

    final CameraPosition now = map.getCameraPosition();
    final float tilt = map.getCameraPosition().tilt;
    final float bearing = map.getCameraPosition().bearing;
    final LatLng[] target = new LatLng[1];
    final float[] zoom = new float[1];

    map.animateCamera(CameraUpdateFactory.newLatLngBounds(latLngBounds, PADDING), 1, null);

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            target[0] = map.getCameraPosition().target;
            zoom[0] = map.getCameraPosition().zoom;
        }
    }, 10);
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            map.animateCamera(CameraUpdateFactory.newCameraPosition(now), 1, null);
        }
    }, 30);

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            map.animateCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(target[0], zoom[0], tilt, bearing)));
        }
    }, 60);
}

looks dirty, but works

3 Handler do the job, every postdelay must be greater than the one before

Bitterroot answered 30/8, 2013 at 9:35 Comment(1)
using timespans is not a good idea, rotation and external events can lead to unpredicted behavioursLindley

© 2022 - 2024 — McMap. All rights reserved.