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.

Alin
  • 14,809
  • 40
  • 129
  • 218
  • 1
    In the end I went with the approach from http://stackoverflow.com/questions/15700808/setting-max-zoom-level-in-google-maps-android-api-v2 and it works reasonable – Alin Jan 21 '14 at 16:11
  • 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). – user2808624 Jan 21 '14 at 20:06
  • Can you please add code sample in your answer so I can Accept it ? – Alin Jan 22 '14 at 14:39
  • OK, I have added an example. – user2808624 Jan 22 '14 at 16:57

10 Answers10

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://stackoverflow.com/a/23092644/2808624 below: Nowadays one would use SphericalUtil for moving LatLng coordinates, instead of coding it on your own.

user2808624
  • 2,502
  • 14
  • 28
  • Please also add meterToLatitude function so I can test the code. It is needed for `double latDiff = meterToLatitude(toNorth);` Thanks – Alin Jan 23 '14 at 10:59
  • Sorry, just missed that. I will add in a second. – user2808624 Jan 23 '14 at 11:23
  • 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. – Alin Jan 23 '14 at 13:19
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);
firemaples
  • 1,521
  • 13
  • 18
dvdrlee
  • 151
  • 1
  • 4
  • 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) – user2808624 Jan 26 '21 at 22:02
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.

Björn Kechel
  • 7,933
  • 3
  • 54
  • 57
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.

Community
  • 1
  • 1
Marc
  • 1,159
  • 17
  • 31
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));
Unheilig
  • 16,196
  • 193
  • 68
  • 98
user2150941
  • 49
  • 1
  • 2
3

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

GoogleMap.setMaxZoomPreference()
GoogleMap.setMinZoomPreference()
malinjir
  • 1,475
  • 2
  • 12
  • 17
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.

Paweł Brewczynski
  • 2,665
  • 3
  • 30
  • 43
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)
GLee
  • 5,003
  • 5
  • 35
  • 39
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)) 
Supriya
  • 1,940
  • 24
  • 23
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.

MaciejGórski
  • 22,187
  • 7
  • 70
  • 94