Calculate Zoom Level for Google Map for two LatLong values - android

I am using com.google.android.gms.maps.GoogleMap in SherlockFragmentActivity.
XML code is this :
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="fill_parent"
android:layout_height="150dip" />
int zoomLevel = ? // How I can calculate the zoom level for two diffrent latlong values
as android map v3 need to tell zoom level as int
map.setZoom(zoomLevel);
I have start and destination values as com.google.android.gms.maps.model.LatLng
LatLng start , end;
I am adding a pligon like GoogleLocation.addPolyLineOnGMap(mMap, startPoint, endPoint, startMarker, endMarker)
My problem is how I can calculate zoom level for Google map so it can show both marker appropriately on map.

Use LatLngBounds.Builder add all the bounds in it and build it, Then create the CameraUpdate object and pass the bounds in it updatefactory with padding. Use this CameraUpdate object to animate the map camera.
LatLngBounds.Builder builder = new LatLngBounds.Builder();
for (Marker m : markers) {
builder.include(m.getPosition());
}
LatLngBounds bounds = builder.build();
int padding = ((width * 10) / 100); // offset from edges of the map
// in pixels
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds,
padding);
mMap.animateCamera(cu);

For me, i need to calculate the zoom for initial map setup by GoogleMapOptions, so using LatLngBounds.Builder
would not work and not optimized. This is how I calculate the zoom based on a city's northeast and southwest coordinates
It's referencing here and this answer, you can simply put the code below to your helper class:
final static int GLOBE_WIDTH = 256; // a constant in Google's map projection
final static int ZOOM_MAX = 21;
public static int getBoundsZoomLevel(LatLng northeast,LatLng southwest,
int width, int height) {
double latFraction = (latRad(northeast.latitude) - latRad(southwest.latitude)) / Math.PI;
double lngDiff = northeast.longitude - southwest.longitude;
double lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
double latZoom = zoom(height, GLOBE_WIDTH, latFraction);
double lngZoom = zoom(width, GLOBE_WIDTH, lngFraction);
double zoom = Math.min(Math.min(latZoom, lngZoom),ZOOM_MAX);
return (int)(zoom);
}
private static double latRad(double lat) {
double sin = Math.sin(lat * Math.PI / 180);
double radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
}
private static double zoom(double mapPx, double worldPx, double fraction) {
final double LN2 = .693147180559945309417;
return (Math.log(mapPx / worldPx / fraction) / LN2);
}
Creating LatLng simply by new LatLng(lat-double, lng-double)
width and height is the map layout size in pixels

in Android:
LatLngBounds group = new LatLngBounds.Builder()
.include(tokio) // LatLgn object1
.include(sydney) // LatLgn object2
.build();
mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(group, 100)); // Set Padding and that's all!

Related

Shrink LatLngBounds by 5%

Alright so after reading Antonio's comment, I am with this in my code. Now regardless of what I submit as my percentage it still thinks my object is outside the bounding box.
My Position is the marker passed in.
LatLngBounds bounds = mMap.getProjection().getVisibleRegion().latLngBounds;
LatLngBounds newBounds = reduceBy(bounds, 0.05d);
if(newBounds.contains(myPosition.getPosition())) {
//If the item is within the the bounds of the screen
} else{
//If the marker is off screen
zoomLevel -= 1;}
}
return zoomLevel;
}
public LatLngBounds reduceBy(LatLngBounds bounds, double percentage) {
double distance = SphericalUtil.computeDistanceBetween(bounds.northeast, bounds.southwest);
double reduced = distance * percentage;
double headingNESW = SphericalUtil.computeHeading(bounds.northeast, bounds.southwest);
LatLng newNE = SphericalUtil.computeOffset(bounds.northeast, reduced/2d, headingNESW);
double headingSWNE = SphericalUtil.computeHeading(bounds.southwest, bounds.northeast);
LatLng newSW = SphericalUtil.computeOffset(bounds.southwest, reduced/2d, headingSWNE);
return LatLngBounds.builder().include(newNE).include(newSW).build();
}
}
I have all the zoom levels set but sometimes I run into spots such as this where it is still in bounds except the marker is off screen. I want to have a slightly smaller bounding box to detect this and then zoom out one level on only these situations.
You can use SphericalUtil class from the Google Maps API Utility Library to make the calculations:
public LatLngBounds reduceBy(LatLngBounds bounds, double percentage) {
double distance = SphericalUtil.computeDistanceBetween(bounds.northeast, bounds.southwest);
double reduced = distance * percentage;
double headingNESW = SphericalUtil.computeHeading(bounds.northeast, bounds.southwest);
LatLng newNE = SphericalUtil.computeOffset(bounds.northeast, reduced/2d, headingNESW);
double headingSWNE = SphericalUtil.computeHeading(bounds.southwest, bounds.northeast);
LatLng newSW = SphericalUtil.computeOffset(bounds.southwest, reduced/2d, headingSWNE);
return LatLngBounds.builder().include(newNE).include(newSW).build();
}
To reduce your bounds by a 5% (diagonal) you can do:
LatLngBounds newBounds = reduceBy(bounds, 0.05d);
Depending on your requirements for precision, you might want to just use simple interpolation like here:
public LatLngBounds reduceBounds(LatLngBounds bounds, double percentage) {
double north = bounds.northeast.latitude;
double south = bounds.southwest.latitude;
double east = bounds.northeast.longitude;
double west = bounds.southwest.longitude;
double lowerFactor = percentage / 2 / 100;
double upperFactor = (100 - percentage / 2) / 100;
return new LatLngBounds(new LatLng(south + (north - south) * lowerFactor, west + (east - west) * lowerFactor),
new LatLng(south + (north - south) * upperFactor, west + (east - west) * upperFactor));
}
This is very simple Math using +-*/ and doesn't cost a lot of performance.
To reduce your bounds dimensions by 10% you do:
LatLngBounds newBounds = reduceBounds(bounds, 10);
Add error checking and border case handling as needed

Specific range of map in android by using mapView

how to show the mapBox map view in specific range in android just like i need to show only Asia pacific in mapView only ,not display full world in mapView android?
You can manually set the zoom level of the map (radius is the radius of the map in meter):
double radius = 500.0;
Circle circle = map.addCircle(new CircleOptions().center(latLng).radius(radius).strokeColor(Color.RED));
circle.setVisible(false);
int zoomLevel = getZoomLevel(circle);
map.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoomLevel));
private int getZoomLevel(Circle circle) {
int zoomLevel = 15;
if (circle != null) {
double radius = circle.getRadius();
double scale = radius / 500;
zoomLevel = (int) (16 - Math.log(scale) / Math.log(2));
}
return zoomLevel;
}

Android Maps v2 - animate camera to include most markers

I have a set of points coming from a webservice that need to be displayed on a map.
I have a current solution working nicely for most cases, using the well-known LatLngBounds.Builder, CameraUpdateFactory.newLatLngBounds and map.animateCamera.
I have some cases which give problems though: when the points are too far away, the map centers on max zoom level on the barycenter of those points. For example: I have 10 points in France and 2 points in Hawai. Maps centers more or less on the caribeans at min zoom level. Hence on screen I got nothing shown, the user has to scroll to actually see something is there.
So my question is:
is there a way to get the map to zoom out far enough so that I can see all points (that would be prefered)
Or: which would be the best way to filter out those cases where just a few points are very far away from the majority and pick a set of point to zoom on (in my example, I would choose to zoom on the 10 points in France and forget about the ones in Hawai).
Put all the LatLng of the markers in the list and pass them to this method and at the last line in the newLatLngBounds(bounds, 50)) the 50 represent the padding between the map edge and the most outer marker in each side
public void centerIncidentRouteOnMap(List<LatLng> copiedPoints) {
double minLat = Integer.MAX_VALUE;
double maxLat = Integer.MIN_VALUE;
double minLon = Integer.MAX_VALUE;
double maxLon = Integer.MIN_VALUE;
for (LatLng point : copiedPoints) {
maxLat = Math.max(point.latitude, maxLat);
minLat = Math.min(point.latitude, minLat);
maxLon = Math.max(point.longitude, maxLon);
minLon = Math.min(point.longitude, minLon);
}
final LatLngBounds bounds = new LatLngBounds.Builder().include(new LatLng(maxLat, maxLon)).include(new LatLng(minLat, minLon)).build();
mapFragment.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));
}
Spotted an error in my previous code and decided to sit down and rewrite it.
I have done something similar before where I had ~4500 markers and wanted to select those within a certain distance of a specific location. Took that code and generalized it to be used with any kind of Markers.
The code that I will post below contains two methods that you can use:
selectLowDistanceMarkers
Measures distance between each and every marker and selects only those that does not have a long distance to any of the other markers. This requires a O(n+n^2) runtime due to the comparison between every marker and an examination afterwards.
getSurroundingMarkers
If you already know a postition that you would like to zoom in to, then this method does the same as above. This method is way less CPU heavy as it only has to do a O(n) run through all the markers and compare them to the given position.
private List<Marker> selectLowDistanceMarkers(List<Marker> markers,
int maxDistanceMeters) {
List<Marker> acceptedMarkers = new ArrayList<Marker>();
if (markers == null) return acceptedMarkers;
Map<Marker, Float> longestDist = new HashMap<Marker, Float>();
for (Marker marker1 : markers) {
// in this for loop we remember the max distance for each marker
// think of a map with a flight company's routes from an airport
// these lines is drawn for each airport
// marker1 being the airport and marker2 destinations
for (Marker marker2 : markers) {
if (!marker1.equals(marker2)) {
float distance = distBetween(marker1.getPosition(),
marker2.getPosition());
if (longestDist.containsKey(marker1)) {
// possible we have a longer distance
if (distance > longestDist.get(marker1))
longestDist.put(marker1, distance);
} else {
// first distance
longestDist.put(marker1, distance);
}
}
}
}
// examine the distances collected
for (Marker marker: longestDist.keySet()) {
if (longestDist.get(marker) <= maxDistanceMeters) acceptedMarkers.add(marker);
}
return acceptedMarkers;
}
private List<Marker> getSurroundingMarkers(List<Marker> markers,
LatLng origin, int maxDistanceMeters) {
List<Marker> surroundingMarkers = surroundingMarkers = new ArrayList<Marker>();
if (markers == null) return surroundingMarkers ;
for (Marker marker : markers) {
double dist = distBetween(origin, marker.getPosition());
if (dist < getHydrantsLoadradius()) {
surroundingMarkers.add(marker);
}
}
return surroundingMarkers;
}
private float distBetween(LatLng pos1, LatLng pos2) {
return distBetween(pos1.latitude, pos1.longitude, pos2.latitude,
pos2.longitude);
}
/** distance in meters **/
private float distBetween(double lat1, double lng1, double lat2, double lng2) {
double earthRadius = 3958.75;
double dLat = Math.toRadians(lat2 - lat1);
double dLng = Math.toRadians(lng2 - lng1);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos(Math.toRadians(lat1))
* Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2)
* Math.sin(dLng / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double dist = earthRadius * c;
int meterConversion = 1609;
return (float) (dist * meterConversion);
}
Again, use the well known LatLngBounds to determine how much you need to zoom after using one of the filtering algorithms above.
Based on some ideas from cYrixmorten, I have simplified the problem because I know the map can accomodate at least 4000km of surface. So here is the function to build the list of ignored webcams (then I simply ignore that webcam for the camera bounds computation but still add the marker so that it is on the map if the user moves).
private List<Webcam> buildIgnoredWebcamsList(List<Webcam> webcams) {
if (webcams == null || webcams.size() < 2) return Lists.newArrayList();
int webcamCount = webcams.size();
// Number of conflicts (distance > 4000 km) for the camera at index #
float averageConflictCount = 0;
int[] conflictCount = new int[webcamCount];
Arrays.fill(conflictCount, 0);
// Find number of conflicts between camera pairs
float[] distance = new float[1];
for (int i = 0; i < webcamCount - 1; ++i) {
Webcam a = webcams.get(i);
                // We don't have to start from 0, compare a and b only once
for (int j = i + 1; j < webcamCount; ++j) {
Webcam b = webcams.get(j);
Location.distanceBetween(a.getLatitude(), a.getLongitude(), b.getLatitude(), b.getLongitude(), distance);
// We have a conflict between a and b if they are more than 4000km away
if (distance[0] > 4000 * 1000) {
conflictCount[i] += 1;
conflictCount[j] += 1;
averageConflictCount += 2;
}
}
}
averageConflictCount /= webcamCount;
// Exclude all webcams with a number of conflicts greater than the average
List<Webcam> ignoredCamerasForBounds = Lists.newArrayList();
for (int i = 0; i < webcamCount; ++i) {
if (conflictCount[i] > averageConflictCount) {
ignoredCamerasForBounds.add(webcams.get(i));
}
}
return ignoredCamerasForBounds;
}
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int ancho = size.x;
int alto =size.y;
List<LatLng> copiedPoints = new ArrayList<LatLng>();
copiedPoints.add(origin);
copiedPoints.add(dest);
centerIncidentRouteOnMap(copiedPoints, ancho, alto);
....
public void centerIncidentRouteOnMap(List<LatLng> copiedPoints, int ancho, int alto) {
double minLat = Integer.MAX_VALUE;
double maxLat = Integer.MIN_VALUE;
double minLon = Integer.MAX_VALUE;
double maxLon = Integer.MIN_VALUE;
for (LatLng point : copiedPoints) {
maxLat = Math.max(point.latitude, maxLat);
minLat = Math.min(point.latitude, minLat);
maxLon = Math.max(point.longitude, maxLon);
minLon = Math.min(point.longitude, minLon);
}
final LatLngBounds bounds = new LatLngBounds.Builder().include(new LatLng(maxLat, maxLon)).include(new LatLng(minLat, minLon)).build();
map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds,ancho, alto, 50));
}

Android Maps API v2: Zoom in on current location and marker

I have a map included in an application. I need the map to zoom in on the a marker and the users location but keep the marker centered. The zoom in works perfectly, but of course doesn't center the marker on the map.
#Override
public void onLocationChanged(Location location) {
if (mListener != null) {
mListener.onLocationChanged(location);
LatLngBounds bounds = new LatLngBounds.Builder()
.include(new LatLng(location.getLatitude(),location.getLongitude()))
.include((new LatLng(52.3563, 4.8790)))
.build();
mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 35));
}
}
What is the best way to keep both within the bounds but keep the marker centered? I can't seem to find a solution in the official documentation.
There are two functions I would call, one for centering the marker and an other one to zoom on the map until your current location is still visible.
My solution for zooming to a point with a 15 zoom level:
myMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLngToCenter, 1));
zooming until all markers are still visible (change code to fit the marker and your position instead):
public void fitZoomAndPositionToMapByMarkers() {
int minLat = Integer.MAX_VALUE;
int maxLat = Integer.MIN_VALUE;
int minLon = Integer.MAX_VALUE;
int maxLon = Integer.MIN_VALUE;
List<MyMapMarker> markersShownOnMap = getMarkersToShow();
for (MyMapMarker item : markersShownOnMap) {
int lat = (int) (item.getLatitude() * 1E6);
int lon = (int) (item.getLongitude() * 1E6);
maxLat = Math.max(lat, maxLat);
minLat = Math.min(lat, minLat);
maxLon = Math.max(lon, maxLon);
minLon = Math.min(lon, minLon);
}
double latitudeToGo = (maxLat + minLat) / 1E6 / 2;
double longitudeToGo = (maxLon + minLon) / 1E6 / 2;
LatLng toCenter = new LatLng(latitudeToGo, longitudeToGo);
centerCameraToProperPosition(toCenter);
LatLng southWestLatLon = new LatLng(minLat / 1E6, minLon / 1E6);
LatLng northEastLatLon = new LatLng(maxLat / 1E6, maxLon / 1E6);
zoomInUntilAllMarkersAreStillVisible(southWestLatLon, northEastLatLon);
}
Hope this helps!
private void zoomInUntilAllMarkersAreStillVisible(final LatLng southWestLatLon, final LatLng northEastLatLon) {
myMap.setOnCameraChangeListener(new OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition arg0) {
myMap.moveCamera(CameraUpdateFactory.newLatLngBounds(new LatLngBounds(southWestLatLon, northEastLatLon), 50));
myMap.setOnCameraChangeListener(null);
}
});
}
Hello i have found a nice and easy way using LatLngbounds:
googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(new LatLngBounds(latlngSW, latlngNE, 50));
this function will use the most SW point and the most NE point to create a rectangle to center the view with 50 padding, but u need to check first which one of the points should be given as first or second parameter, here is my code:
public void centerMap(LatLng latLng){
if(fenceMarker != null){
LatLngBounds center;
LatLng fence = fenceMarker.getPosition();
if(fence.latitude < latLng.latitude || fence.longitude < latLng.longitude)
center = new LatLngBounds(fenceMarker.getPosition(), latLng);
else{
center = new LatLngBounds(latLng, fenceMarker.getPosition());
}
googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(center, 50));
}
}
The 2 points are my position latLng and the marker I have for the GeoFence, fenceMarker.
I call this function centerMap(latLng) from the onLocationChanged():
#Override
public void onLocationChanged(Location location) {
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
centerMap(latLng);
}
Setting Boundaries -
An overload of the method, newLatLngBounds(boundary, width, height, padding) allows you to specify a width and height in pixels for a rectangle, with the intention that these correspond to the dimensions of the map. The rectangle is positioned such that its center is the same as that of the map's view (so that if the dimensions specified are the same as those of the map's view, then the rectangle coincides with the map's view). The returned CameraUpdate will move the camera such that the specified LatLngBounds are centered on screen within the given rectangle at the greatest possible zoom level, taking into account the padding required.
Reference- https://developers.google.com/maps/documentation/android/views#target_location

How to find zoom level based on circle draw on map

I am drawing the circle on map with specifying the radius and it'll draw the circle successfully. But when I change the size of circle using seekbar I need to feet the circle in screen and zoom the map of that level, I have not idea about this, need your guideline thank you.
We can also get the zoom level for map from the drawn circle
Circle circle = googleMap.addCircle(circleOptions);
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
circleOptions.getCenter(), getZoomLevel(circle)
// Methode for zoomlevel
public int getZoomLevel(Circle circle) {
int zoomLevel = 11;
if (circle != null) {
double radius = circle.getRadius() + circle.getRadius() / 2;
double scale = radius / 500;
zoomLevel = (int) (16 - Math.log(scale) / Math.log(2));
}
return zoomLevel;
}
build.gradle:
dependencies {
compile 'com.google.maps.android:android-maps-utils:0.4+'
}
public static LatLngBounds getLatLngBoundsFromCircle(Circle circle){
if(circle != null){
return new LatLngBounds.Builder()
.include(SphericalUtil.computeOffset(circle.getCenter(), circle.getRadius() * Math.sqrt(2), 45))
.include(SphericalUtil.computeOffset(circle.getCenter(), circle.getRadius() * Math.sqrt(2), 225))
.build();
}
return null;
}
map.animateCamera( CameraUpdateFactory
.newLatLngBounds(MapUtils.getLatLngBoundsFromCircle(mapCircle),20) );
After long time I found the solution from somewhere.
here is the method which was giving me the min lat/lng and max lat/lng.
Based on this I have getting the latspan and longspan.
public void boundingCoordinates(double distance, double radius) {
if (radius < 0d || distance < 0d)
throw new IllegalArgumentException();
// angular distance in radians on a great circle
double radDist = distance / radius;
double radLat = Math.toRadians(gp.getLatitudeE6()/1e6); // here is your single point latitude gp.getLatitude
double radLon = Math.toRadians(gp.getLongitudeE6()/1e6); // here is your single point longitude gp.getlongitude
double minLat = radLat - radDist;
double maxLat = radLat + radDist;
double minLon, maxLon;
if (minLat > MIN_LAT && maxLat < MAX_LAT) {
double deltaLon = Math.asin(Math.sin(radDist) /Math.cos(radLat));
minLon = radLon - deltaLon;
if (minLon < MIN_LON)
minLon += 2d * Math.PI;
maxLon = radLon + deltaLon;
if (maxLon > MAX_LON)
maxLon -= 2d * Math.PI;
} else {
// a pole is within the distance
minLat = Math.max(minLat, MIN_LAT);
maxLat = Math.min(maxLat, MAX_LAT);
minLon = MIN_LON;
maxLon = MAX_LON;
}
minLat = Math.toDegrees(minLat);
minLon = Math.toDegrees(minLon);
maxLat = Math.toDegrees(maxLat);
maxLon = Math.toDegrees(maxLon);
minGeo = new GeoPoint((int)(minLat*1e6),(int)(minLon*1e6));
maxGeo = new GeoPoint((int)(maxLat*1e6),(int)(maxLon*1e6));
}
now you pass the distance in any unit as per that you have to pass the radius of earth for example if you pass 2 km then the radius of earth is in km say 6370.997.
you can try it, its cool thing
In my code I am adding a transparent circle around a marker which has dynamic radius and zooming the map camera so it fit to screen.
it is working 100% fine in my project.
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
mMap.setOnMarkerClickListener(this);
MapsInitializer.initialize(Objects.requireNonNull(getContext()));
// Add a marker on Property location
LatLng propertyLatlng = new LatLng(getLatitude(), getLongitude());
// draw transparent blue circle around marker
try {
CircleOptions circleOptions = new CircleOptions()
.center(propertyLatlng)
.radius(Double.parseDouble(radius) / 0.00062137)
.strokeColor(BLUE_TRANSPARENT)
.strokeWidth(0)
.fillColor(BLUE_TRANSPARENT);
Circle circle = mMap.addCircle(circleOptions);
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
circleOptions.getCenter(), getZoomLevel(circle)));
} catch (Exception e) {
AppLogger.logError(TAG, e.getMessage());
}
}
/**
* #param circle : circle
* #return : return zoom level according to circle radius
*/
public float getZoomLevel(Circle circle) {
int zoomLevel = 11;
if (circle != null) {
double radius = circle.getRadius() + circle.getRadius() / 2;
double scale = radius / 500;
zoomLevel = (int) (16 - Math.log(scale) / Math.log(2));
}
return zoomLevel+.4f;
}
double getZoomLevel() {
double zoomLevel = 0.0;
double newRadius = radiusOfCircle + radiusOfCircle / 2;
double scale = newRadius / 500;
zoomLevel = (6 - log(scale) / log(2));
return zoomLevel;
}
This worked for me
Thanks to #Anand Tiwari

Categories

Resources