Android map v2 zoom to show all the markers
Asked Answered
C

13

316

I have 10 markers in the GoogleMap. I want to zoom in as much as possible and keep all markers in view? In the earlier version this can be achieved from zoomToSpan() but in v2 I have no idea how about doing that. Further, I know the radius of the circle that needs to be visible.

Currish answered 12/2, 2013 at 8:17 Comment(0)
F
874

You should use the CameraUpdate class to do (probably) all programmatic map movements.

To do this, first calculate the bounds of all the markers like so:

LatLngBounds.Builder builder = new LatLngBounds.Builder();
for (Marker marker : markers) {
    builder.include(marker.getPosition());
}
LatLngBounds bounds = builder.build();

Then obtain a movement description object by using the factory: CameraUpdateFactory:

int padding = 0; // offset from edges of the map in pixels
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding);

Finally move the map:

googleMap.moveCamera(cu);

Or if you want an animation:

googleMap.animateCamera(cu);

That's all :)

Clarification 1

Almost all movement methods require the Map object to have passed the layout process. You can wait for this to happen using the addOnGlobalLayoutListener construct. Details can be found in comments to this answer and remaining answers. You can also find a complete code for setting map extent using addOnGlobalLayoutListener here.

Clarification 2

One comment notes that using this method for only one marker results in map zoom set to a "bizarre" zoom level (which I believe to be maximum zoom level available for given location). I think this is expected because:

  1. The LatLngBounds bounds instance will have northeast property equal to southwest, meaning that the portion of area of the earth covered by this bounds is exactly zero. (This is logical since a single marker has no area.)
  2. By passing bounds to CameraUpdateFactory.newLatLngBounds you essentially request a calculation of such a zoom level that bounds (having zero area) will cover the whole map view.
  3. You can actually perform this calculation on a piece of paper. The theoretical zoom level that is the answer is +∞ (positive infinity). In practice the Map object doesn't support this value so it is clamped to a more reasonable maximum level allowed for given location.

Another way to put it: how can Map object know what zoom level should it choose for a single location? Maybe the optimal value should be 20 (if it represents a specific address). Or maybe 11 (if it represents a town). Or maybe 6 (if it represents a country). API isn't that smart and the decision is up to you.

So, you should simply check if markers has only one location and if so, use one of:

  • CameraUpdate cu = CameraUpdateFactory.newLatLng(marker.getPosition()) - go to marker position, leave current zoom level intact.
  • CameraUpdate cu = CameraUpdateFactory.newLatLngZoom(marker.getPosition(), 12F) - go to marker position, set zoom level to arbitrarily chosen value 12.
Felon answered 12/2, 2013 at 8:53 Comment(11)
It should be noted that the move cannot happen from the onCreate calls, the view must be created. I needed to use addOnGlobalLayoutListener to get the appropriate sample.Leveridge
@Bar Well, this is partly true. To be precise: some movement methods won't work right after creating the map object. The reason for this is the map object hasn't been measured yet i.e. it has not undergone the layout process. A typical fix is to use addOnGlobalLayoutListener() or post() with an appropriate Runnable. This is exactly the reason why getting marker screen coordinates can't be done in onCreate - see https://mcmap.net/q/101116/-how-to-get-screen-coordinates-from-marker-in-google-maps-v2-android/1820695 However you can use some methods even before layout has happened - eg. CameraUpdateFactory.newLatLngBounds() with 4 params.Felon
googleMap .setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { @Override public void onMapLoaded() { googleMap.moveCamera(cu); } }); This will avoid the error on measure.Tohubohu
it works otherwise but when there is a single marker on map it zooms to some bizarre level that the map becomes to blurred. How to fix this?Tereus
isnt there a way to set a max limit to the zoom level in map irrespective of the the number of markers?Tereus
@user2103379 unfortunately no. see here for ugly workaround. although I once had an idea (which I never tested but you can): you save current map position to CameraPosition p1. then you proceed with calculating CameraUpdate cu like above and do map.moveCamera(cu). you save current position to p2 and check if its zoom is out of desired range - if so, you build new CameraPosition p3 based on p2 but with zoom corrected and issue map.moveCamera(p1); map.animateCamera(CameraUpdateFactory.newCameraPosition(p3)); - warning: never tested.Felon
This code gave me the error: "the size of the map should not be 0". So what I had to do was to set the width and height of the map like this: final int width = getResources().getDisplayMetrics().widthPixels; final int height = getResources().getDisplayMetrics().heightPixels; final CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(latLngBounds, width, height, padding);.Simba
Thanks. If someone has a problem with app crash on googleMap.moveCamera(cu); checkout these answers #13693079Shepard
@Felon **Error**com.google.maps.api.android.lib6.common.apiexception.c: Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. Most likely, layout has not yet occured for the map view. Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions. How can i solve can you help me pleaseAllenaallenby
@GowthamanM this is already explained - see Clarification 1, Clarification 2 and my 2nd comment to the answerFelon
This does not work if the markers are very distantDeoxyribose
I
140

Google Map V2

The following solution works for Android Marshmallow 6 (API 23, API 24, API 25, API 26, API 27, API 28). It also works in Xamarin.

LatLngBounds.Builder builder = new LatLngBounds.Builder();

//the include method will calculate the min and max bound.
builder.include(marker1.getPosition());
builder.include(marker2.getPosition());
builder.include(marker3.getPosition());
builder.include(marker4.getPosition());

LatLngBounds bounds = builder.build();

int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
int padding = (int) (width * 0.10); // offset from edges of the map 10% of screen

CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, width, height, padding);

mMap.animateCamera(cu);
Impregnate answered 24/6, 2016 at 12:19 Comment(3)
For better-looking padding, use height instead of width and take 20% instead of 10% of it.Boar
Accepted answer sometimes cause weird zooming. This works like a charm. This is a better answer.Dealfish
This answer works better than the accepted one, the accepted one causes some weired behavior.Scute
D
14

I couldnt use the onGlobalLayoutlistener, so here is another solution to prevent the "Map size can't be 0. Most likely, layout has not yet occured for the map view. Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions." error:

mMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { 
@Override 
public void onMapLoaded() { 
    mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 15));
 } 
});
Doorknob answered 15/7, 2014 at 8:31 Comment(2)
This one takes way longer than the OnGlobalLayoutListener, so for me it still showed the maps default area/zoom first, before moving the camera to my needsDetector
How to give some padding as my 2 markers are touched with map bounds.Bramble
S
13

So

I needed to use addOnGlobalLayoutListener to get the appropriate sample

for example, your Google Map is inside RelativeLayout:

RelativeLayout mapLayout = (RelativeLayout)findViewById(R.id.map_layout);
mapLayout.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            //and write code, which you can see in answer above
        }
    });
Sycosis answered 4/1, 2014 at 11:43 Comment(1)
Came here from the Udemy tutorial, great work man, they used your answer to solve the problem.Ol
T
9

Working fine for me.

From this code, I am displaying multiple markers with particular zoom on map screen.

// Declared variables

private LatLngBounds bounds;
private LatLngBounds.Builder builder;

// Method for adding multiple marker points with drawable icon

private void drawMarker(LatLng point, String text) {

        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(point).title(text).icon(BitmapDescriptorFactory.fromResource(R.drawable.icon));
        mMap.addMarker(markerOptions);
        builder.include(markerOptions.getPosition());

    }

// For adding multiple markers visible on map

@Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
        builder = new LatLngBounds.Builder();
    for (int i = 0; i < locationList.size(); i++) {

        drawMarker(new LatLng(Double.parseDouble(locationList.get(i).getLatitude()), Double.parseDouble(locationList.get(i).getLongitude())), locationList.get(i).getNo());

     }
     bounds = builder.build();
     CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, 0);
     mMap.animateCamera(cu);
Track answered 5/8, 2016 at 11:20 Comment(0)
K
5

Note - This is not a solution to the original question. This is a solution to one of the subproblems discussed above.

Solution to @andr Clarification 2 -

Its really problematic when there's only one marker in the bounds and due to it the zoom level is set to a very high level (level 21). And Google does not provide any way to set the max zoom level at this point. This can also happen when there are more than 1 marker but they are all pretty close to each other. Then also the same problem will occur.

Solution - Suppose you want your Map to never go beyond 16 zoom level. Then after doing -

CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding);
mMap.moveCamera(cu);

Check if your zoom level has crossed level 16(or whatever you want) -

float currentZoom = mMap.getCameraPosition().zoom;

And if this level is greater than 16, which it will only be if there are very less markers or all the markers are very close to each other, then simply zoom out your map at that particular position only by seting the zoom level to 16.

mMap.moveCamera(CameraUpdateFactory.zoomTo(16));

This way you'll never have the problem of "bizarre" zoom level explained very well by @andr too.

Kavanaugh answered 2/3, 2016 at 11:32 Comment(2)
good solution but if you put "cu" object inside onMapReady(), then there is a misbehavior in this case: location received -> zoom is large so make it 16 -> then the user zooms in -> location received again and camera is put back to level 16 . This can be a problem when location is received at almost real-time as it'll keep returning to level 16.Ecosphere
" And Google does not provide any way to set the max zoom level at this point. " - Not true: developers.google.com/android/reference/com/google/android/gms/…Pinna
W
5

this would help.. from google apis demos

private List<Marker> markerList = new ArrayList<>();
Marker marker = mGoogleMap.addMarker(new MarkerOptions().position(geoLatLng)
                .title(title));
markerList.add(marker);
    // Pan to see all markers in view.
    // Cannot zoom to bounds until the map has a size.
    final View mapView = getSupportFragmentManager().findFragmentById(R.id.map).getView();
    if (mapView!=null) {
        if (mapView.getViewTreeObserver().isAlive()) {
            mapView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @SuppressWarnings("deprecation") // We use the new method when supported
                @SuppressLint("NewApi") // We check which build version we are using.
                @Override
                public void onGlobalLayout() {
                    //Calculate the markers to get their position
                    LatLngBounds.Builder b = new LatLngBounds.Builder();
                    for (Marker m : markerList) {
                        b.include(m.getPosition());
                    }
                    // also include current location to include in the view
                    b.include(new LatLng(mLocation.getLatitude(),mLocation.getLongitude()));

                    LatLngBounds bounds = b.build();
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                        mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    } else {
                        mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    }
                    mGoogleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));
                }
            });
        }
    }

for clear info look at this url. https://github.com/googlemaps/android-samples/blob/master/ApiDemos/app/src/main/java/com/example/mapdemo/MarkerDemoActivity.java

Weller answered 29/3, 2016 at 11:10 Comment(1)
Thanks, It's working even in a loop.Strumpet
S
3

Show All Markers with Google map

In these Methods store all Markers and automatically zoom to show all markers in google map.

// Declare the Markers List.
List<MarkerOptions> markerList;
private BitmapDescriptor vnrPoint,banPoint;


public void storeAllMarkers()
{
      markerList=new ArrayList<>();
      markerList.removeAll(markerList);


      // latitude and longitude of Virudhunagar

      double latitude1=9.587209;
      double longitude1=77.951431;
   vnrPoint=BitmapDescriptorFactory.fromResource(R.drawable.location_icon_1);
      LatLng vnr = new LatLng(latitude1, longitude1);
      MarkerOptions vnrMarker = new MarkerOptions();
      vnrMarker.position(vnr);
      vnrMarker.icon(vnrPoint);
      markerList.add(vnrMarker);

      // latitude and longitude of Bengaluru

      double latitude2=12.972442;
      double longitude2=77.580643;

    banPoint=BitmapDescriptorFactory.fromResource(R.drawable.location_icon_2);

      LatLng ban = new LatLng(latitude2, longitude2);
      MarkerOptions bengalureMarker = new MarkerOptions();
      bengalureMarker.position(ban);
      bengalureMarker.icon(banPoint);
      markerList.add(bengalureMarker);

      // You can add any numbers of MarkerOptions like this.

     showAllMarkers();

 }


public void showAllMarkers()
{
    LatLngBounds.Builder builder = new LatLngBounds.Builder();

    for (MarkerOptions m : markerList) {
        builder.include(m.getPosition());
    }

    LatLngBounds bounds = builder.build();

    int width = getResources().getDisplayMetrics().widthPixels;
    int height = getResources().getDisplayMetrics().heightPixels;
    int padding = (int) (width * 0.30); 

    // Zoom and animate the google map to show all markers

    CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, width, height, padding);
    googleMap.animateCamera(cu);
}
Sensualism answered 16/10, 2018 at 10:26 Comment(0)
I
1

I had similar problem, using following code solved the issue:

CameraUpdateFactory.newLatLngBounds(bounds, 200, 200, 5) generally the location differences in my case are no more than two neighbour cities.

zoom to fit all markers on map google maps v2

Isaac answered 12/6, 2017 at 12:17 Comment(0)
O
1

I have one other way to do this same thing works perfectly. so the idea behind to show all markers on the screen we need a center lat long and zoom level. here is the function which will give you both and need all marker's Latlng objects as input.

 public Pair<LatLng, Integer> getCenterWithZoomLevel(LatLng... l) {
    float max = 0;

    if (l == null || l.length == 0) {
        return null;
    }
    LatLngBounds.Builder b = new LatLngBounds.Builder();
    for (int count = 0; count < l.length; count++) {
        if (l[count] == null) {
            continue;
        }
        b.include(l[count]);
    }

    LatLng center = b.build().getCenter();

    float distance = 0;
    for (int count = 0; count < l.length; count++) {
        if (l[count] == null) {
            continue;
        }
        distance = distance(center, l[count]);
        if (distance > max) {
            max = distance;
        }
    }

    double scale = max / 1000;
    int zoom = ((int) (16 - Math.log(scale) / Math.log(2)));
    return new Pair<LatLng, Integer>(center, zoom);
}

This function return Pair object which you can use like

Pair pair = getCenterWithZoomLevel(l1,l2,l3..); mGoogleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(pair.first, pair.second));

you can instead of using padding to keep away your markers from screen boundaries, you can adjust zoom by -1.

Obstinacy answered 14/12, 2017 at 9:18 Comment(0)
A
1

I worked the same problem for showing multiple markers in Kotlin using a fragment

first declare a list of markers

private lateinit var markers: MutableList<Marker>

initialize this in the oncreate method of the frament

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
                         ): View? {
    //initialize markers list

    markers = mutableListOf()
   
    return inflater.inflate(R.layout.fragment_driver_map, container, false)
}

on the OnMapReadyCallback add the markers to the markers list

private val callback = OnMapReadyCallback { googleMap ->

    map = googleMap

    markers.add(
        map.addMarker(
            MarkerOptions().position(riderLatLng)
                .title("Driver")
                .snippet("Driver")
                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))))


    markers.add(
        map.addMarker(
            MarkerOptions().position(driverLatLng)
                .title("Driver")
                .snippet("Driver")
                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN))))

Still on the callback

//create builder
    val builder = LatLngBounds.builder()

    //loop through the markers list
    for (marker in markers) {

        builder.include(marker.position)
    }
    //create a bound
    val bounds = builder.build()

    //set a 200 pixels padding from the edge of the screen
    val cu = CameraUpdateFactory.newLatLngBounds(bounds,200)
    
    //move and animate the camera
    map.moveCamera(cu)
    //animate camera by providing zoom and duration args, callBack set to null
    map.animateCamera(CameraUpdateFactory.zoomTo(10f), 2000, null)

Merry coding guys

Attendant answered 15/9, 2020 at 5:50 Comment(0)
S
0

Use the method "getCenterCoordinate" to obtain the center coordinate and use in CameraPosition.

private void setUpMap() {
    mMap.setMyLocationEnabled(true);
    mMap.getUiSettings().setScrollGesturesEnabled(true);
    mMap.getUiSettings().setTiltGesturesEnabled(true);
    mMap.getUiSettings().setRotateGesturesEnabled(true);

    clientMarker = mMap.addMarker(new MarkerOptions()
            .position(new LatLng(Double.valueOf(-12.1024174), Double.valueOf(-77.0262274)))
            .icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_taxi))
    );
    clientMarker = mMap.addMarker(new MarkerOptions()
            .position(new LatLng(Double.valueOf(-12.1024637), Double.valueOf(-77.0242617)))
            .icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_location))
    );

    camPos = new CameraPosition.Builder()
            .target(getCenterCoordinate())
            .zoom(17)
            .build();
    camUpd3 = CameraUpdateFactory.newCameraPosition(camPos);
    mMap.animateCamera(camUpd3);
}


public LatLng getCenterCoordinate(){
    LatLngBounds.Builder builder = new LatLngBounds.Builder();
    builder.include(new LatLng(Double.valueOf(-12.1024174), Double.valueOf(-77.0262274)));
    builder.include(new LatLng(Double.valueOf(-12.1024637), Double.valueOf(-77.0242617)));
    LatLngBounds bounds = builder.build();
    return bounds.getCenter();
}
Shingle answered 23/12, 2015 at 19:8 Comment(0)
R
-2
   //For adding a marker in Google map
        MarkerOptions mp = new MarkerOptions();
        mp.position(new LatLng(Double.parseDouble(latitude), Double.parseDouble(longitude)));
        mp.snippet(strAddress);
        map.addMarker(mp);

        try {

            b = new LatLngBounds.Builder();

            if (MapDetailsList.list != null && MapDetailsList.list.size() > 0) {

                for (int i = 0; i < MapDetailsList.list.size(); i++) {

                    b.include(new LatLng(Double.parseDouble(MapDetailsList.list.get(i).getLatitude()),
                            Double.parseDouble(MapDetailsList.list.get(i).getLongitude())));

                }
                LatLngBounds bounds = b.build();

                DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
                int width = displayMetrics.widthPixels;
                int height = displayMetrics.heightPixels;

                // Change the padding as per needed
                CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, width-200, height-200, 5);
                // map.setCenter(bounds.getCenter());

                map.animateCamera(cu);

            }

        } catch (Exception e) {

        }

http://i64.tinypic.com/2qjybh4.png

http://i63.tinypic.com/flzwus.png

http://i63.tinypic.com/112g5fm.png

Riggins answered 5/1, 2016 at 7:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.