Search code examples
androidgoogle-mapsgoogle-maps-markersgoogle-maps-android-api-2android-maps-utils

Create CameraUpdate from points and padding to show markers nicely


I have a list of LatLng points that I want to show as markers on a GoogleMap. I am currently computing a camera view that fits all of my points the following way:

  1. First I compute a LatLngBounds with all my points.

    Iterator<LatLng> i = items.iterator();
    LatLngBounds.Builder builder = LatLngBounds.builder();
    while (i.hasNext()) {
        builder.include(i.next());
    }
    LatLngBounds bounds = builder.build();
    
  2. Then I use the appropriate method:

    CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds, padding);
    

The update offers a good view over all my points, with some nice padding.

Question

I’d like to apply different values for paddingTop, paddingBottom, paddingLeft and paddingRight. Namely, I have nothing on both sides, then I have a floating toolbar at the top (about 50dp) and a floating card at the bottom (about 200dp).

If I add no padding, some of the points will be covered by the top toolbar or by the bottom card.

If I add the maximum padding (200dp), then there are huge gaps on top, left and right. Any idea?

__________________________
| ___________o__________ |
| |                    | |
| |____________________|o|
|         o              |
|                  o     |
|    o                   |
|          MAP           |
|     o                  |
|                   o    |
| ______________________ |
| |                    | |
|o|                    | |
| |                    |o|
| |____________________| |
|____________________o___|

My only idea right now is, get the map width in pixels, get the map width in Lng (not well defined), find the pixels/lng ratio, convert desired padding from pixels to lng, and recompute LatLngBounds adding a fake point (one for each side) that is that distant from my original bounds.


Solution

  • This seems to work, but I’ll wait for a better solution if any.

    You need to pass width and height (in pixels) of the view holding the map, so that we can compute a pixel to projected coordinates coefficient. Then you pass desired padding in a Rect object.

    If you have lots of points this is probably best suited for running in a background task.

    public static CameraUpdate computeCameraView(final List<LatLng> items,
                                                 final GoogleMap map, final Rect padding,
                                                 final int viewWidth, final int viewHeight) {
    
        // Compute bounds without padding
        Iterator<LatLng> i = items.iterator();
        LatLngBounds.Builder builder = LatLngBounds.builder();
        while (i.hasNext()) {
            builder.include(i.next());
        }
        LatLngBounds bounds = builder.build();
    
        // Create a first CameraUpdate and apply
        CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds, 0);
        map.moveCamera(cameraUpdate);
    
        // Convert padding to lat/lng based on current projection
        bounds = map.getProjection().getVisibleRegion().latLngBounds;
        double mapWidth = bounds.northeast.longitude - bounds.southwest.longitude;
        double mapHeight = bounds.northeast.latitude - bounds.southwest.latitude;
        double pixelToLng = mapWidth / viewWidth;
        double pixelToLat = mapHeight / viewHeight;
        padding.top = (int) (padding.top * pixelToLat);
        padding.bottom = (int) (padding.bottom * pixelToLat);
        padding.left = (int) (padding.left * pixelToLng);
        padding.right = (int) (padding.right * pixelToLng);
    
        // Now padding holds insets in lat/lng values.
        // Let's create two fake points and bound
        LatLng northEast = new LatLng(bounds.northeast.latitude + padding.top,
                bounds.northeast.longitude + padding.right);
        LatLng southWest = new LatLng(bounds.southwest.latitude - padding.bottom,
                bounds.southwest.longitude - padding.left);
        LatLngBounds newBounds = new LatLngBounds.Builder()
                .include(northEast).include(southWest).build();
    
        return CameraUpdateFactory.newLatLngBounds(newBounds, 0);
    }
    

    The weak point here is double mapWidth = bounds.northeast.longitude - bounds.southwest.longitude. You can’t compute longitude like that, because it’s a circular variable (i.e. there are going to be issues if the visible region crosses the 180 meridian). I’ll look into it.

    This should be enough:

    public static double computeLngDistance(LatLng east, LatLng west) {
        if (east.longitude <= 0 && west.longitude >= 0) {
            // We are crossing the 180 line.
            return (east.longitude + 180d) + (180d - west.longitude);
        } else {
            return east.longitude - west.longitude;
        }
    }