Android maps V2 newLatLngBounds with bearing

2019-03-24 03:40发布

问题:

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.

回答1:

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!



回答2:

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://stackoverflow.com/a/13274361/4120431 )

Hope this can help you with your issue!



回答3:

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