I only want to display a google map of a specific area on the mobile screen. The map should not move beyond that area when there is user interaction such as zoom or moving the map.
For example:
These are latitude and longitude bound:
LatLng one = new LatLng(42.0140555,-88.2131937);
LatLng two = new LatLng(40.993729,-87.6622417);
I have found the solution this way. Do this on onMapReady() function.
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
//get latlong for corners for a specified area of map
LatLng one = new LatLng(42.0140555,-88.2131937);
LatLng two = new LatLng(40.993729,-87.6622417);
LatLngBounds.Builder builder = new LatLngBounds.Builder();
//add them to builder
builder.include(one);
builder.include(two);
LatLngBounds bounds = builder.build();
//get width and height to current display screen
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
// 20% padding
int padding = (int) (width * 0.20);
//set latlong bounds
mMap.setLatLngBoundsForCameraTarget(bounds);
//move camera to fill the bound to screen
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, width, height, padding));
//set zoom to level to current so that you won't be able to zoom out viz. move outside bounds
mMap.setMinZoomPreference(mMap.getCameraPosition().zoom);
}
Hi Im having a little issue with displaying points on a map.
I use an Arraylist to store multiple lat/lng values and then do a for loop to add the point and auto zoom. Everything works fine when there are 2 or more markers. The problem is when only 1 marker is added it zooms in too close.
Anyone know how to resolve this?
public static void processMap()
{
for (int i = 0; i < lat.size(); i++)
{
MarkerOptions markerOptions = new MarkerOptions();
LatLng latLng = new LatLng(lat.get(i), lng.get(i));
markerOptions.position(latLng);
markerOptions.title("title");
markerOptions.snippet("description");
mMap.addMarker(markerOptions);
bounds.include(new LatLng(lat.get(i), lng.get(i)));
}
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 150));
}
Updated code
public static void processMap()
{
int num = 0;
double lat2 = 0;
double lng2 = 0;
for (int l = 0; l < lat.size(); l++)
{
lat2 = lat.get(l);
lng2 = lng.get(l);
LatLng latLng = new LatLng(lat2, lng2);
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.position(latLng);
markerOptions.title(title.get(l));
markerOptions.snippet(description.get(l));
mMap.addMarker(markerOptions);
bounds.include(new LatLng(lat2, lng2));
num++;
}
if (num == 1)
{
// if only 1 marker
LatLng latLng2 = new LatLng(lat2, lng2);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng2, 16));
}
else
{
// more than 1 marker
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 150));
}
}
Hi sorry I didn't reply earlier I was attempting to fix the problem. I managed to fix it, but is this the correct way of going about it?
The edit you made should fix your problem. Anyway you don't need to set new LatLng if you only have 1 marker. Passing the bound should do. I would make this little edit:
if (num == 1)
{
// if only 1 marker
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(bounds.build(), 16));
}
else
{
// more than 1 marker
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 150));
}
Hope it helps. Good luck!
Your code does not really tell why you are using .newLatLngBounds(), but this will center the map on your given bounding box and respecting the given 150px padding around the box.
The zoom is then adapted according to the elements inside the bounding box to show all of them. When there is only one marker inside the box, it will zoom to it (and probably too close). The docs say that the map will be centered "at the greatest possible zoom level".
So to prevent this, I would choose a good zoom level for the first marker, add it, move the camera with moveCamera(...) and after that continue with your code when there are other markers left.
When a marker is clicked, the default behavior for the camera is to center it on screen, but because I usually have long text description in the info window, it's more convenient to actually change the camera position so that the marker is on the bottom of screen(making the info window in the center of screen). I think I should be able to do that by overriding onMarkerClick function like below (the default behavior is cancelled when this function return true)
#Override
public boolean onMarkerClick(final Marker marker) {
// Google sample code comment : We return false to indicate that we have not
// consumed the event and that we wish
// for the default behavior to occur (which is for the camera to move
// such that the
// marker is centered and for the marker's info window to open, if it
// has one).
marker.showInfoWindow();
CameraUpdate center=
CameraUpdateFactory.newLatLng(new LatLng(XXXX,
XXXX));
mMap.moveCamera(center);//my question is how to get this center
// return false;
return true;
}
Edit:
Problem solved using accepted answer's steps, codes below:
#Override
public boolean onMarkerClick(final Marker marker) {
//get the map container height
LinearLayout mapContainer = (LinearLayout) findViewById(R.id.map_container);
container_height = mapContainer.getHeight();
Projection projection = mMap.getProjection();
LatLng markerLatLng = new LatLng(marker.getPosition().latitude,
marker.getPosition().longitude);
Point markerScreenPosition = projection.toScreenLocation(markerLatLng);
Point pointHalfScreenAbove = new Point(markerScreenPosition.x,
markerScreenPosition.y - (container_height / 2));
LatLng aboveMarkerLatLng = projection
.fromScreenLocation(pointHalfScreenAbove);
marker.showInfoWindow();
CameraUpdate center = CameraUpdateFactory.newLatLng(aboveMarkerLatLng);
mMap.moveCamera(center);
return true;
}
Thanks for helping ^ ^
I might edit this answer later to provide some code, but what I think could work is this:
Get LatLng (LatLng M) of the clicked marker.
Convert LatLng M to a Point (Point M) using the Projection.toScreenLocation(LatLng) method. This gives you the location of the marker on the device's display (in pixels).
Compute the location of a point (New Point) that's above Point M by half of the map's height.
Convert the New Point back to LatLng and center the map on it.
Look here for my answer on how to get the map's height.
// googleMap is a GoogleMap object
// view is a View object containing the inflated map
// marker is a Marker object
Projection projection = googleMap.getProjection();
LatLng markerPosition = marker.getPosition();
Point markerPoint = projection.toScreenLocation(markerPosition);
Point targetPoint = new Point(markerPoint.x, markerPoint.y - view.getHeight() / 2);
LatLng targetPosition = projection.fromScreenLocation(targetPoint);
googleMap.animateCamera(CameraUpdateFactory.newLatLng(targetPosition), 1000, null);
I prefer Larry McKenzie's answer which it doesn't depend on screen projection (i.e. mProjection.toScreenLocation()), my guess is the projection resolution will go poor when the map zoom level is low, it made me sometimes couldn't get an accurate position. So, calculation based on google map spec will definitely solve the problem.
Below is an example code of moving the marker to 30% of the screen size from bottom.
zoom_lvl = mMap.getCameraPosition().zoom;
double dpPerdegree = 256.0*Math.pow(2, zoom_lvl)/170.0;
double screen_height = (double) mapContainer.getHeight();
double screen_height_30p = 30.0*screen_height/100.0;
double degree_30p = screen_height_30p/dpPerdegree;
LatLng centerlatlng = new LatLng( latlng.latitude + degree_30p, latlng.longitude );
mMap.animateCamera( CameraUpdateFactory.newLatLngZoom( centerlatlng, 15 ), 1000, null);
If you don't care about the map zooming in and just want the marker to be at the bottom see below, I think it's a simpler solution
double center = mMap.getCameraPosition().target.latitude;
double southMap = mMap.getProjection().getVisibleRegion().latLngBounds.southwest.latitude;
double diff = (center - southMap);
double newLat = marker.getPosition().latitude + diff;
CameraUpdate centerCam = CameraUpdateFactory.newLatLng(new LatLng(newLat, marker.getPosition().longitude));
mMap.animateCamera(centerCam);
I had the same issue, I tried the following perfectly working solution
mMap.setOnMarkerClickListener(new OnMarkerClickListener()
{
#Override
public boolean onMarkerClick(Marker marker)
{
int yMatrix = 200, xMatrix =40;
DisplayMetrics metrics1 = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics1);
switch(metrics1.densityDpi)
{
case DisplayMetrics.DENSITY_LOW:
yMatrix = 80;
xMatrix = 20;
break;
case DisplayMetrics.DENSITY_MEDIUM:
yMatrix = 100;
xMatrix = 25;
break;
case DisplayMetrics.DENSITY_HIGH:
yMatrix = 150;
xMatrix = 30;
break;
case DisplayMetrics.DENSITY_XHIGH:
yMatrix = 200;
xMatrix = 40;
break;
case DisplayMetrics.DENSITY_XXHIGH:
yMatrix = 200;
xMatrix = 50;
break;
}
Projection projection = mMap.getProjection();
LatLng latLng = marker.getPosition();
Point point = projection.toScreenLocation(latLng);
Point point2 = new Point(point.x+xMatrix,point.y-yMatrix);
LatLng point3 = projection.fromScreenLocation(point2);
CameraUpdate zoom1 = CameraUpdateFactory.newLatLng(point3);
mMap.animateCamera(zoom1);
marker.showInfoWindow();
return true;
}
});
I also faced this problem and fixed it in a hacky way. Let's declare a double field first. You need to adjust the value of it based on your requirement but I recommend you keep it between 0.001~0.009 otherwise you can miss your marker after the zoom animation.
double offset = 0.009
/*You can change it based on your requirement.
For left-right alignment please kindly keep it between 0.001~0.005 */
For bottom-centered:
LatLng camera = new LatLng(marker.getPosition().latitude+offset , marker.getPosition().longitude);
//Here "marker" is your target market on which you want to focus
For top-centered:
LatLng camera = new LatLng(marker.getPosition().latitude-offset , marker.getPosition().longitude);
For left-centered:
LatLng camera = new LatLng(marker.getPosition().latitude, marker.getPosition().longitude+offset);
For right-centered:
LatLng camera = new LatLng(marker.getPosition().latitude-offset , marker.getPosition().longitude-offset);
Then finally call the -
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(camera, yourZoom));
I did a little research and according to the documentation the map is square and at zero zoom level the width and height is 256dp and +/- 85 degrees N/S. The map width increases with zoom level so that width and height = 256 * 2N dp. Where N is the zoom level. So in theory you can determine the new location by getting the map height and dividing it by 170 total degrees to get dp per degree. Then get the screen height (or mapview height) in dp divided it by two and convert half view size to degrees of latitude. Then set your new Camera point that many degrees of latitude south. I can add code if you need it but I'm on a phone at the moment.
I have been trying out all the solutions proposed here, and came with a combined implementation of them. Considering, map projection, tilt, zoom and info window height.
It doesn't really place the marker at the bottom of the "camera view", but I think it accommodates the info window and the marker centre pretty well in most cases.
#Override
public boolean onMarkerClick(Marker marker) {
mIsMarkerClick = true;
mHandler.removeCallbacks(mUpdateTimeTask);
mLoadTask.cancel(true);
getActivity().setProgressBarIndeterminateVisibility(false);
marker.showInfoWindow();
Projection projection = getMap().getProjection();
Point marketCenter = projection.toScreenLocation(marker.getPosition());
float tiltFactor = (90 - getMap().getCameraPosition().tilt) / 90;
marketCenter.y -= mInfoWindowAdapter.getInfoWindowHeight() / 2 * tiltFactor;
LatLng fixLatLng = projection.fromScreenLocation(marketCenter);
mMap.animateCamera(CameraUpdateFactory.newLatLng(fixLatLng), null);
return true;
}
And then, your custom adapter would have to keep an instance of the info window inflated view, to be able to fetch its height.
public int getInfoWindowHeight(){
if (mLastInfoWindoView != null){
return mLastInfoWindoView.getMeasuredHeight();
}
return 0;
}
Anyone who's still looking to center the camera according to location coordinates
CameraPosition cameraPosition = new CameraPosition.Builder().target(new LatLng(Lat, Lon))
.zoom(15)
.bearing(0)
.tilt(45)
.build();
map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
Credits
After some experiences i've implemented the solution that fine for me.
DisplayMetrics metrics = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(metrics);
Point targetPoint = new Point(metrics.widthPixels / 2, metrics.heightPixels - metrics.heightPixels / 9);
LatLng targetLatlng = map.getProjection().fromScreenLocation(targetPoint);
double fromCenterToTarget = SphericalUtil.computeDistanceBetween(map.getCameraPosition().target, targetLatlng);
LatLng center = SphericalUtil.computeOffset(new LatLng(location.latitude, location.longitude), fromCenterToTarget/1.2, location.bearing);
CameraUpdate camera = CameraUpdateFactory.newLatLng(center);
map.animateCamera(camera, 1000, null);
Here. First, we pick the physical point on the screen where the marker should be moved. Then, convert it to LatLng. Next step - calculate distance from current marker position (in center) to target. Finally, we move the center of map straight from the marker to calculated distance.
I needed something similar, but with also zoom, tilt and bearing in the equation.
My problem is more complex, but the solution is a sort of generalization so it could be applied also to the problem in the question.
In my case, I update programmatically the position of a marker; the camera can be rotated, zoomed and tilted, but I want the marker always visible at a specific percentage of the View height from the bottom. (similar to the car marker position in the Maps navigation)
The solution:
I first pick the map location on the center of the screen and the location of a point that would be visible at a percentage of the View from the bottom (using map projection); I get the distance between these two points in meters, then I calculate a position, starting from the marker position, moving for the calculated distance towards the bearing direction; this new position is my new Camera target.
The code (Kotlin):
val movePointBearing =
if (PERCENTAGE_FROM_BOTTOM > 50) {
(newBearing + 180) % 360
} else newBearing
val newCameraTarget = movePoint(
markerPosition,
distanceFromMapCenter(PERCENTAGE_FROM_BOTTOM),
markerBearing)
with the movePoint method copied from here:
https://stackoverflow.com/a/43225262/2478422
and the distanceFromMapCenter method defined as:
fun distanceFromMapCenter(screenPercentage: Int): Float {
val screenHeight = mapFragment.requireView().height
val screenWith = mapFragment.requireView().width
val projection = mMap.projection
val center = mMap.cameraPosition.target
val offsetPointY = screenHeight - (screenHeight * screenPercentage / 100)
val offsetPointLocation = projection.fromScreenLocation(Point(screenWith / 2, offsetPointY))
return distanceInMeters(center, offsetPointLocation)
}
then just define a distanceInMeters method (for example using android Location class)
I hope the idea is clear without any further explanations.
One obvious limitation: it applies the logic using the current zoom and tilt, so it would not work if the new camera position requires also a different zoom_level and tilt.
I have a task for move my app to Google Maps Android APIs V2. Now, I need to get Latitude/Longitude span. I used MapView.getLatitudeSpan() and MapView.getLongitudeSpan() in previous version APIs. Now I can't find something like this in V2.
Does anybody have the same problem?
You can use the following code to get the lat/lng span:
VisibleRegion vr = mMap.getProjection().getVisibleRegion();
double left = vr.latLngBounds.southwest.longitude;
double top = vr.latLngBounds.northeast.latitude;
double right = vr.latLngBounds.northeast.longitude;
double bottom = vr.latLngBounds.southwest.latitude;
I hope this helps.
First obtain a Projection using GoogleMap.getProjection(). Then you can call Projection.getVisibleRegion() to obtain a VisibleRegion which has a LatLngBounds.
The reason why a LatitudeSpan and Longitude span no longer makes sense is because the map can now be rotated and tilted and so viewport is no longer a latitude/longitude aligned rectangle on the map.
This way works for me:
CameraPosition camPos2 = mapa.getCameraPosition();
LatLng pos = camPos2.target;
Toast.makeText(MainActivity.this,"Lat: " + pos.latitude + " - Lng: " +pos.longitude, Toast.LENGTH_LONG).show();
Oops, i misunderstood the question, i mean i did not saw "span" word.
According the API the correct would be:
First get the bounds:
LatLngBounds bounds = gMap.getProjection().getVisibleRegion().latLngBounds;
And then ask if any point is in bounds:
LatLng point = new LatLng (latitude, longitude);
if(bounds.contains(point)){
//do something
}
Here is the answer
LatLngBounds bounds = googleMap.getProjection().getVisibleRegion().latLngBounds;
if (bounds.contains(ROMA)) {
marker = googleMap.addMarker(
new MarkerOptions()
.position(ROMA)
.title("Hello")
.snippet("Nice Place")
.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher))
);
System.out.println("Marker added");
}
Add the marker only when it falls in the visible region
I have two geo points that vary intermittently, and want the MapView to resize and translate to make sure both points are always visible. I can easily re-centre the map on the point mid-way between the two, but how do I set the zoom level to ensure my two points are visible?
Check out his answer in a different post:
Google Map V2 how to set ZoomToSpan?
It solves it without much hassle. Note that you can only use that function AFTER the map has been loaded at least once. If not use function with specified map size on screen
LatLngBounds bounds = new LatLngBounds.Builder()
.include(new LatLng(CURRENTSTOP.latitude, CURRENTSTOP.longitude))
.include(new LatLng(MYPOSITION.latitude, MYPOSITION.longitude)).build();
Point displaySize = new Point();
getWindowManager().getDefaultDisplay().getSize(displaySize);
map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, displaySize.x, 250, 30));
Works like a charm and very dynamic!
Try this and see:
double latitudeSpan = Math.round(Math.abs(firstLat -
secLat));
double longitudeSpan = Math.round(Math.abs(firstLong -
secLong));
double currentLatitudeSpan = (double)mapView.getLatitudeSpan();
double currentLongitudeSpan = (double)mapView.getLongitudeSpan();
double ratio = currentLongitudeSpan/currentLatitudeSpan;
if(longitudeSpan < (double)(latitudeSpan+2E7) * ratio){
longitudeSpan = ((double)(latitudeSpan+2E7) * ratio);
}
mapController.zoomToSpan((int)(latitudeSpan*2), (int)(longitudeSpan*2));
mapView.invalidate();
in the new Maps API (v2) you can do it this way:
LatLng southwest = new LatLng(Math.min(laglng1.latitude, laglng2.latitude), Math.min(laglng1.longitude, laglng2.longitude));
LatLng northeast = new LatLng(Math.max(laglng1.latitude, laglng2.latitude), Math.max(laglng1.longitude, laglng2.longitude));
googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(new LatLngBounds(southwest, northeast),500,500, 0));