Set a max zoom level on LatLngBounds builder
Asked Answered
M

10

28

I haven't found an answer in my search, there are a few answers on SO but they didn't work for me.

I have 2 markers on the map and I am using LatLngBounds builder in order to let the camera zoom to the right zoom level to include them both. Everything works as expected aside one thing, when the 2 markers are really close to each other, the map is very very zoomed and well, it doesn't make any sense to have this level of zooming.

LatLngBounds.Builder builder = new LatLngBounds.Builder();
builder.include(firstMarker.getPosition());
builder.include(secondMarker.getPosition());
LatLngBounds bounds = builder.build();

CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, markerPadding);

Is there a way to force a certain level of zoom, after which the camera won't zoom ? This would avoid having the map too zoomed in/out. Basically I am using 15.0f as level of zoom. If the points are too far away I want the zoom to fit them both. If points are getting close I don't want the zoom level to go beyond 15.0f.

Mortmain answered 13/12, 2013 at 16:59 Comment(4)
In the end I went with the approach from #15701308 and it works reasonableMortmain
Alin, my proposal below is similar to the answer you are referring to, with one main difference: The same longitude difference translates into very different distances (in kilometers) depending on the latitude where it is measured. With my approach you do not have this variation. The smallest zoom level will always be defined by the same map diagonal measured in kilometers independent on whether you show markers on the equator or the poles (o.k. I know, you can not really show the poles in Google maps).Reinwald
Can you please add code sample in your answer so I can Accept it ?Mortmain
OK, I have added an example.Reinwald
R
47

I have a similar case, where I want at least a map diagonal of 2 kilometers. Therefore I just add two additional points to the builder: the first 1 km north-west of the center of the original bounds and the second 1 km south-east of the center. If these are inside the bounds, they do not change anything. If they are outside of the original bounds, they increase the bounds to a meaningful size.

Here is a full code example:

public LatLngBounds createBoundsWithMinDiagonal(MarkerOptions firstMarker, MarkerOptions secondMarker) {
    LatLngBounds.Builder builder = new LatLngBounds.Builder();
    builder.include(firstMarker.getPosition());
    builder.include(secondMarker.getPosition());
    
    LatLngBounds tmpBounds = builder.build();
    /** Add 2 points 1000m northEast and southWest of the center.
     * They increase the bounds only, if they are not already larger
     * than this. 
     * 1000m on the diagonal translates into about 709m to each direction. */
    LatLng center = tmpBounds.getCenter();
    LatLng northEast = move(center, 709, 709);
    LatLng southWest = move(center, -709, -709);
    builder.include(southWest);
    builder.include(northEast);
    return builder.build();     
}

private static final double EARTHRADIUS = 6366198;
/**
 * Create a new LatLng which lies toNorth meters north and toEast meters
 * east of startLL
 */
private static LatLng move(LatLng startLL, double toNorth, double toEast) {
    double lonDiff = meterToLongitude(toEast, startLL.latitude);
    double latDiff = meterToLatitude(toNorth);
    return new LatLng(startLL.latitude + latDiff, startLL.longitude
            + lonDiff);
}

private static double meterToLongitude(double meterToEast, double latitude) {
    double latArc = Math.toRadians(latitude);
    double radius = Math.cos(latArc) * EARTHRADIUS;
    double rad = meterToEast / radius;
    return Math.toDegrees(rad);
}


private static double meterToLatitude(double meterToNorth) {
    double rad = meterToNorth / EARTHRADIUS;
    return Math.toDegrees(rad);
}

Edit, as this seems to be still well liked:

Please see https://mcmap.net/q/412331/-set-a-max-zoom-level-on-latlngbounds-builder below: Nowadays one would use SphericalUtil for moving LatLng coordinates, instead of coding it on your own.

Reinwald answered 14/12, 2013 at 21:31 Comment(3)
Please also add meterToLatitude function so I can test the code. It is needed for double latDiff = meterToLatitude(toNorth); ThanksMortmain
Sorry, just missed that. I will add in a second.Reinwald
Thanks a lot. It really works better than the previous solution. The only thing I noticed was base on screed dpi the distance might be too big, so in the end I set different distance value for different densities to fit my needs.Mortmain
K
11

I implemented this solution according to the answer by user2808624, but using the SphericalUtil from the Android Maps Utility Library to do the calculations.

final double HEADING_NORTH_EAST = 45;
final double HEADING_SOUTH_WEST = 225;
LatLng center = tmpBounds.getCenter();
LatLng northEast = SphericalUtil.computeOffset(center, 709, HEADING_NORTH_EAST);
LatLng southWest = SphericalUtil.computeOffset(center, 709, HEADING_SOUTH_WEST);
Kathlenekathlin answered 15/4, 2014 at 19:25 Comment(1)
If you want a diagonal of 2000m, I guess you have to use 1000 meter in both directions instead of 709 meter. The 709 in the answer you are referring to is needed only if you go at first 709m straight to north and then 709 meter straight to east (resulting in about 1000m in diagonal)Reinwald
C
8

Coming late but maybe someone has the same issue I had: I received bounds from a Place, not from a LatLngBounds.Builder:

I used Location.distanceBetween(...) to find out if the bounds were too small, and then use a minimum zoom level with the center of the bounds:

if (areBoundsTooSmall(bounds, 300)) {
    mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(bounds.getCenter(), 17));
} else {
    mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 20));
}

The check method is:

private boolean areBoundsTooSmall(LatLngBounds bounds, int minDistanceInMeter) {
    float[] result = new float[1];
    Location.distanceBetween(bounds.southwest.latitude, bounds.southwest.longitude, bounds.northeast.latitude, bounds.northeast.longitude, result);
    return result[0] < minDistanceInMeter;
}

I used min zoom level 17 and padding 20 for bounds that are too small, too small is definded here by min distance in meter 300. You can adjust the numbers as you need.

Chairmanship answered 8/7, 2016 at 11:25 Comment(2)
tried more than one solution... this one did it. ThanksSemidome
this is the bestMortgagor
R
6

Here is a much shorter version based on user2808624's answer.

        LatLngBounds bounds = builder.build();
        LatLng center = bounds.getCenter();
        builder.include(new LatLng(center.latitude-0.001f,center.longitude-0.001f));
        builder.include(new LatLng(center.latitude+0.001f,center.longitude+0.001f));
        bounds = builder.build();

Its a bit sloppy, but you can use 0.001 to zoom no less than a city block, 0.01 for no less than a city view. Only advantage is its only 4 extra lines of code.

Rapscallion answered 15/12, 2016 at 22:28 Comment(0)
S
4

It is just simple:

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

LatLng sydney1 = new LatLng(57.149651, -2.099075);

LatLng sydney2 = new LatLng(51.621441, -3.943646);

latlngBuilder.include(sydney1);
latlngBuilder.include(sydney2);

googlemap.animateCamera(CameraUpdateFactory.newLatLngBounds(latlngBuilder.build(), 100));
Sosna answered 7/5, 2017 at 3:59 Comment(0)
P
3

Google has added an api for limiting max/min zoom levels.

GoogleMap.setMaxZoomPreference()
GoogleMap.setMinZoomPreference()
Protohistory answered 1/11, 2016 at 7:48 Comment(1)
Notice: This will also restrict the user to zoom outside these limits.Clari
P
3

Quick fix is:

gMap.setOnMapLoadedCallback(this);
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding);
gMap.setMaxZoomPreference(10);
gMap.animateCamera(cu);


@Override
public void onMapLoaded() {
    gMap.resetMinMaxZoomPreference();
}

Beware that it could halt zoom until map is fully reloaded. But as a quick fix it works.

Paracasein answered 17/11, 2016 at 10:21 Comment(0)
P
1

Have you considered making a map fragment that is hidden, and using that to determine your zoom/bounds? It's obviously not the best computationally, but it's probably the easiest/most accurate solution. For instance, it's complex to make a mathematical solution that works near the poles and across 0 degrees longitude (if you care). If you use a map, that's all built in already. You could also obviously use this solution on your existing map, but the user would see a bounce.

Here's how you'd do it:

  1. Go to the bounds as you've defined above
  2. Get the zoom using map.getCameraPosition().zoom.
  3. If zoom > 15 zoom out to 15 using CameraUpdateFactory.zoomTo(15)
Procure answered 22/1, 2014 at 23:21 Comment(0)
R
1

Following from the answers by user2808624 and dvdrlee, I wrote an extension in Kotlin:

fun LatLngBounds.Builder.createBoundsWithZoomMaintained(bounds: LatLngBounds) : LatLngBounds  {
    val HEADING_NORTH_EAST = 45.0   //NorthEast angle
    val HEADING_SOUTH_WEST = 215.0  //SouthWest angle
    val center = bounds.center
    val northEast = SphericalUtil.computeOffset(center, 709.0, HEADING_NORTH_EAST)  //1000 metres on the diagonal translates to 709 metres on either side.
    val southWest = SphericalUtil.computeOffset(center, 709.0, HEADING_SOUTH_WEST)
    this.include(northEast).include(southWest)
    return this.build()
}

This would typically be in your Utils or Extensions file, and then you can use it from anywhere simply like this:

var bounds = builder.build()  //To include all your existing points.
bounds = builder.createBoundsWithZoomMaintained(bounds)  //Updated bounds.
mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 10)) 
Rora answered 4/1, 2018 at 10:13 Comment(0)
G
0

There is no way to limit zoom levels as of 4.0.30, but you may track this issue.

The easy way would be to force zoom level like on this video: http://www.youtube.com/watch?v=-nCZ37HdheY
Basically they are calling animateCamera when certain zoom level is reached.

That would look a bit weird, because you then have 2 animations that are not started by the user.

The hard way would be doing the math yourself. Try working with CameraUpdateFactory.newLatLngZoom with LatLng as a center between these points and your max zoom when you detect points are close to each other.

Geoffreygeoffry answered 14/12, 2013 at 19:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.