natario
natario

Reputation: 25204

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.

Upvotes: 4

Views: 2605

Answers (3)

K&#233;lian
K&#233;lian

Reputation: 3433

You can use
public static CameraUpdate newLatLngBounds (LatLngBounds bounds, int width, int height, int padding)

Returns a CameraUpdate that transforms the camera such that the specified latitude/longitude bounds are centered on screen within a bounding box of specified dimensions at the greatest possible zoom level. You can specify additional padding, to further restrict the size of the bounding box.

see ref

You'll define a box with a width and a height that will be centered inside the usable area of your map. Then the bounds will be centered and zoomed as much as possible inside that box.

You can "simulate" padding via that box without applying padding to the map (an then without moving map overlays such as watermark, etc). If you want an horizontal padding of 16dp, you'll set the width of your box to be : mapWidth - dpToPx(2 * 16)

  1. The 2 * is needed because, again, your box will be centered inside the map. so if you remove only 16dp, you'll have 8dp on each sides.
  2. I skipped the method to convert dpToPx but you can easily find it on SO.

The limitation of that method is that, since the box is centered, left and right padding has to be the same. (same for top and bottom).

Final thoughts : When the map has padding something weird occurres. Either the padding of the map is applied to the box you are defining, or something else happens.
When I was defining the height of the box as mapHeight - verticalPaddingOfTheMap the result was not as expected. I finally put mapHeight and it was all good.
Keep that in mind if you manipulate a map that has padding

Upvotes: 0

Philemon Khor
Philemon Khor

Reputation: 417

There's actually an easier and shorter solution for this, only three lines of codes needed. Thanks https://stackoverflow.com/a/27937256/10035225 for the solution.

First, convert the toolbar and bottom card size into pixels.

Then, add these three lines of codes:

map.setPadding(left, top, right, bottom);
map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0));
map.setPadding(0,0,0,0);

First line: the top and bottom padding would be your toolbar and bottom card size plus some extra, left and right could be any padding as long as it looks nice.

Second line: move/animate your camera to the bounds and remember to set the padding to 0 because you wouldn't want any extra padding since you have set it in the first line.

Third line: set the map padding back to original padding (0 for my case) because the map padding set in the first line is permanent.

That's it and there you go! Hope it helps!

Upvotes: 6

natario
natario

Reputation: 25204

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;
    }
}

Upvotes: 4

Related Questions