I have a google maps app that I need to be able to upload different floorplans to. So far I am adding the floorplan image to the map using a ground overlay object like this:
LatLngBounds bounds = new LatLngBounds(new LatLng(floorplan.get_swlat(), floorplan.get_swlng()),
new LatLng(floorplan.get_nelat(), floorplan.get_nelng()));
GroundOverlayOptions overlayOptions = new GroundOverlayOptions()
.image(BitmapDescriptorFactory.fromBitmap(floorplan.get_floorplanimage())).positionFromBounds(bounds);
mMap.addGroundOverlay(overlayOptions);
This adds the floorplan to the map, but the problem is that it is only north-south facing, so it won't work for buildings that don't perfectly sit like that. So I was wondering if there was a way to go in and manually place it (and use fingers to rotate and scale the image) where I need it to be. Anyone know where to even begin?
Took some time but I finally figured something out. Instead of adding the overlay using the two corners I use the center lat/lon and the width and height. I add the overlay as follows.
mOverlayOptions = new GroundOverlayOptions()
.image(BitmapDescriptorFactory.fromBitmap(floorplan.get_floorplanimage()))
.position(new LatLng(floorplan.get_clat(), floorplan.get_clng()), (float) floorplan.get_width(), (float) floorplan.get_height())
.bearing((float) floorplan.get_bearing())
.transparency(mOpacity);
mOverlay = mMap.addGroundOverlay(mOverlayOptions);
So you have to give the floorplan initial values to begin.Then I added two markers, one located at the center of the overlay (same lat/lon) and another one located along the center of the right edge of the overlay (mSizeMarker). Make sure they're draggable. I had to do some math to figure out where to place the one on the right edge.
mCenterMarker = mMap.addMarker(new MarkerOptions().position(new LatLng(floorplan.get_clat(), floorplan.get_clng())));
Double new_longitude = floorplan.get_clng() + (floorplan.get_width() / 2 / (6371000)) * (180 / 3.14159265) / Math.cos(floorplan.get_clat() * 3.14159265/180);
mSizeMarker = mMap.addMarker(new MarkerOptions().position(new LatLng(floorplan.get_clat(), new_longitude)));
mCenterMarker.setDraggable(true);
mSizeMarker.setDraggable(true);
All that's left is to add an OnMarkerDragListener:
mMap.setOnMarkerDragListener(new GoogleMap.OnMarkerDragListener() {
#Override
public void onMarkerDragStart(Marker marker) {
}
}
#Override
public void onMarkerDrag(Marker marker) {
if (marker.equals(mCenterMarker)) {
mOverlay.remove();
mOverlayOptions.position(marker.getPosition(), mOverlay.getWidth(), mOverlay.getHeight());
mOverlay = mMap.addGroundOverlay(mOverlayOptions);
mSizeMarker.setPosition(new LatLng(marker.getPosition().latitude, marker.getPosition().longitude + (mOverlay.getWidth() / 2 / (6371000)) * (180 / 3.14159265) / Math.cos(marker.getPosition().latitude * 3.14159265/180)));
}
if (marker.equals(mSizeMarker)) {
marker.setPosition(new LatLng(mCenterMarker.getPosition().latitude, marker.getPosition().longitude));
double newWidth = ((marker.getPosition().longitude - mCenterMarker.getPosition().longitude) / ((180 / 3.14159265) / Math.cos(mCenterMarker.getPosition().latitude * 3.14159265/180))) * 6371000 * 2;
mOverlay.remove();
mOverlayOptions.position(mCenterMarker.getPosition(), (float) newWidth);
mOverlay = mMap.addGroundOverlay(mOverlayOptions);
}
}
#Override
public void onMarkerDragEnd(Marker marker) {
if (marker.equals(mSizeMarker)) {
marker.setPosition(new LatLng(mCenterMarker.getPosition().latitude, marker.getPosition().longitude));
}
}
});
Use the middle marker to drag where you want the floorplan to be located, and the one on the right to change the width (the height will automatically update correspondingly).
Then when the image is positioned correctly, just have a save button somewhere that updates where the floorplan is stored with the position, width, and height of mOverlay. It would be possible to add another marker to manually adjust the bearing, but that's more math than I need to do right now. I'll just keep adjusting it when I set it originally. Hope this helps anyone else who comes along this problem, I know this solution is working great for me so far.
Related
I have added current location via google map routing with
Routing routing = new Routing.Builder()
.travelMode(Routing.TravelMode.DRIVING)
.key(getResources().getString(R.string.google_maps_api))
.withListener(this)
.waypoints(new LatLng(mLastKnownLocation.getLatitude(), mLastKnownLocation.getLongitude()), site_location)
.alternativeRoutes(false)
.build();
routing.execute();
#Override
public void onRoutingSuccess(ArrayList<Route> route, int shortestRouteIndex) {
if (polylines.size() > 0) {
for (Polyline poly : polylines) {
poly.remove();
}
}
polylines = new ArrayList<>();
//add route(s) to the map.
for (int i = 0; i < route.size(); i++) {
//In case of more than 5 alternative routes
int colorIndex = i % COLORS.length;
PolylineOptions polyOptions = new PolylineOptions();
polyOptions.color(getResources().getColor(COLORS[colorIndex]));
polyOptions.width(10 + i * 13);
polyOptions.addAll(route.get(i).getPoints());
Polyline polyline = googleMap.addPolyline(polyOptions);
polylines.add(polyline);
int distance = route.get(i).getDistanceValue();
if (distance < 1000){
totalKm.setText( distance+" Metres");
}else {
totalKm.setText( (distance/1000) +" km");
}
}
LatLngBounds.Builder builder = new LatLngBounds.Builder();
builder.include(new LatLng(mLastKnownLocation.getLatitude(), mLastKnownLocation.getLongitude()));
builder.include(site_marker.getPosition());
LatLngBounds bounds = builder.build();
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, 100);
googleMap.animateCamera(cu);
}
THis displays a driving directions like
But am trying to display the default google map driving icon with zoomed view like
How do i proceed to add such a map while still retaining the polylines to show driving view.
Jinesh Francis totally right in his answer: you should either run the default map Google Maps application through intent or modify the standard MapView (or MapFragment).
TLDR;
If you chose the second way - easiest approach is to use standard classes of Android Google Maps API to create view like in your example (other way is to create MapView-based custom view).
At first read carefully p 3.2.4 Restrictions Against Misusing the Services (d) of Google Maps Platform Terms of Service:
(d) No Re-Creating Google Products or Features. Customer will not use
the Services to create a product or service with features that are
substantially similar to or that re-create the features of another
Google product or service. Customer’s product or service must contain
substantial, independent value and features beyond the Google products
or services. For example, Customer will not: (i) re-distribute the
Google Maps Core Services or pass them off as if they were Customer’s
services; (ii) create a substitute of the Google Maps Core Services,
Google Maps, or Google Maps mobile apps, or their features; (iii) use
the Google Maps Core Services in a listings or directory service or to
create or augment an advertising product; (iv) combine data from the
Directions API, Geolocation API, and Maps SDK for Android to create
real-time navigation functionality substantially similar to the
functionality provided by the Google Maps for Android mobile app.
and if you not violate Terms of Service you can do what you want with that steps/tasks:
1) get user current location;
2) get a route path segment nearest to user current location (because user location rarely exactly on road);
3) get a azimuth (bearing) of this segment;
4) show map with route path and user current position marker with appropriate tilt and rotation according path segment bearing.
Task 1 can be solved like in this answer of Axxiss:
private final LocationListener mLocationListener = new LocationListener() {
#Override
public void onLocationChanged(final Location location) {
//your code here
}
};
Task 2 can be solved via PolyUtil.isLocationOnPath() like in that answer:
private LatLng getMarkerProjectionOnSegment(LatLng carPos, List<LatLng> segment, Projection projection) {
LatLng markerProjection = null;
Point carPosOnScreen = projection.toScreenLocation(carPos);
Point p1 = projection.toScreenLocation(segment.get(0));
Point p2 = projection.toScreenLocation(segment.get(1));
Point carPosOnSegment = new Point();
float denominator = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
// p1 and p2 are the same
if (Math.abs(denominator) <= 1E-10) {
markerProjection = segment.get(0);
} else {
float t = (carPosOnScreen.x * (p2.x - p1.x) - (p2.x - p1.x) * p1.x
+ carPosOnScreen.y * (p2.y - p1.y) - (p2.y - p1.y) * p1.y) / denominator;
carPosOnSegment.x = (int) (p1.x + (p2.x - p1.x) * t);
carPosOnSegment.y = (int) (p1.y + (p2.y - p1.y) * t);
markerProjection = projection.fromScreenLocation(carPosOnSegment);
}
return markerProjection;
}
Task 3 can be solved with code like that:
private float getBearing(LatLng begin, LatLng end) {
double dLon = (end.longitude - begin.longitude);
double x = Math.sin(Math.toRadians(dLon)) * Math.cos(Math.toRadians(end.latitude));
double y = Math.cos(Math.toRadians(begin.latitude))*Math.sin(Math.toRadians(end.latitude))
- Math.sin(Math.toRadians(begin.latitude))*Math.cos(Math.toRadians(end.latitude)) * Math.cos(Math.toRadians(dLon));
double bearing = Math.toDegrees((Math.atan2(x, y)));
return (float) bearing;
}
where begin and end is begin and end of current route path segment.
Task 4 can be solved with code like that:
as marker you can use vector drawable of north oriented arrow like that:
ic_up_arrow_circle.xml (also you can adjust transparency and colors):
<vector android:height="24dp" android:viewportHeight="93.934"
android:viewportWidth="93.934"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#8fFF0000"
android:pathData="m0,46.9666c0,25.939 21.028,46.967 46.967,46.967c25.939,-0 46.967,-21.028 46.967,-46.967c0,-25.939 -21.027,-46.967 -46.967,-46.967c-25.939,-0 -46.967,21.028 -46.967,46.967zM78.262,67.4396l-31.295,-16.845l-31.295,16.845l31.295,-51.614l31.295,51.614z"
/>
<path
android:fillColor="#FFFFFF"
android:pathData="M78.262,67.4396l-31.295,-16.845l-31.295,16.845l31.295,-51.614l31.295,51.614z"
/>
</vector>
and you can place it on map with code like that:
public Marker addDirectionMarker(LatLng latLng, float angle) {
Drawable circleDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_up_arrow_in_circle);
BitmapDescriptor markerIcon = getMarkerIconFromDrawable(circleDrawable, 150, 150);
return mGoogleMap.addMarker(new MarkerOptions()
.position(latLng)
.anchor(0.5f, 0.5f)
.rotation(angle)
.flat(true)
.icon(markerIcon)
);
}
where 150 is marker size in pixels. NB! You need flat marker for its rotation and tilt with map and 0.5f for move marker anchor exactly on its center point.
then you can show all of this on map:
...
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(userPosition)
.tilt(tilt)
.zoom(zoom)
.bearing(bearing)
.build();
mGoogleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
...
But if you do only that marker of user current position appeared in the center of screen (because GoogleMap.moveCamera() sets the center exactly at .target()). So, to avoid it you need to shift down the map slightly - in that case user location marker should be appeared at the bottom of screen. For map center shift you need get current map center screen coordinates, then change y coordinate and get new screen center. Something like that:
...
LatLng mapCenter = mGoogleMap.getCameraPosition().target;
Projection projection = mGoogleMap.getProjection();
Point centerPoint = projection.toScreenLocation(mapCenter);
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int displayHeight = displayMetrics.heightPixels;
centerPoint.y = centerPoint.y - (int) (displayHeight / 4.5); // move center down for approx 22%
LatLng newCenterPoint = projection.fromScreenLocation(centerPoint);
mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(newCenterPoint, zoom));
...
And with all of this stuff, for your route (with zoom = 15 and tilt = 50) you should get something like that:
As you can see, the route path is not exactly on road, so you need to get route path points more precisely than Directions API response. You can get that e.g. via Google Maps Roads API part Snap to Road which
returns the best-fit road geometry for a given set of GPS coordinates.
This service takes up to 100 GPS points collected along a route, and
returns a similar set of data with the points snapped to the most
likely roads the vehicle was traveling along.
like in that answer. If your route path has more than points you need to split in into 100-points portions and process them separately (also Snap to Road API has 2500 request per day per user (IP) and 10 requests per sec. restriction).
And as Jaswant Singh answered you:
need to set a custom marker (with icon same as that blue arrow) on
your current location and move it to the new location every time there
is onLocationChanged() callback is called (Also animate the camera to
that new location).
Also, you need to select zoom and tilt properties according, for example, current user speed: when user drives faster tilt -> 0. And so on. It's not a simple task.
In addition to Jinesh’s answer,
If you still want to add that marker for development, you need to set a custom marker (with icon same as that blue arrow) on your current location and move it to the new location every time there is onLocationChanged() callback is called (Also animate the camera to that new location).
And tilt the map a little to get the exact look of the google maps navigation view, though you won’t get to use all the functionalities but it’s worth to give it a try.
I've looked at everything I can find on this, and haven't found a solution. I'm trying to do something very simple:
protected void moveCamera(final LatLng position, final float zoom) {
getMap(new OnMapReadyCallback() {
#Override
public void onMapReady(final GoogleMap googleMap) {
logger.debug("Moving map to " + position);
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(position)
.zoom(zoom)
.build();
googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
}
});
}
Nothing happens. No errors, no camera movement. I've also tried moveCamera instead. I've tried different types of calls to CameraUpdateFactory, I've tried moving and zooming separately, I've tried posting with a Handler with a delay. Sometimes this works perfectly, but if I leave the fragment with the map and come back, then it doesn't work. The map resets to latitude 0,0 and standard map type (it was set to hybrid) and I can't get it to do anything. I know the method is getting called by logging statements. Is there some kind of lifecycle thing I need to do to make sure the map can be restored?
I'd really prefer not to switch to Mapbox or something because that would be a lot of work to fix a little bug, but I need this to work correctly.
Try this code for this camera issue.
You can change zoom level, radius.
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
height = Math.round(0.45f*height);
int padding = (int) (width * 0.10);
LatLngBounds.Builder builder = new LatLngBounds.Builder();
LatLngBounds bounds = builder.include(new LatLng(INSERT_LAT,
INSERT_LNG)).build();
CameraUpdate cameraUpdate =
CameraUpdateFactory.newLatLngBounds(bounds,width,height,padding);
googleMap.moveCamera(cameraUpdate);
googleMap.animateCamera(CameraUpdateFactory.zoomTo(14), 1000, null);
I've used the following and it works fine:
GoogleMap mGoogleMap;
LatLngBounds bounds = builder.build();
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
height = Math.round(0.45f*height);
int padding = (int) (width * 0.10);
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds,width,height,padding);
// Add this where you want to move map camera
mGoogleMap.moveCamera(CameraUpdateFactory.newLatLng("<pass_LatLng_here"));
mGoogleMap.animateCamera(CameraUpdateFactory.zoomTo(14)); //set the zoom level here
For me, the move camera inside the onPlaceSelected callback in Places Autocomplete api doesn't work.
I moved the moveCamera into a runOnUiThread and it's now working fine.
Background
Suppose I have a Google maps view, and another view on top of it, that covers a part of it, hiding some content of the map.
The problem
I need to make the "camera" of the map, to focus and have a marker on a coordinate , yet let it all be in the middle of the visible part of the map.
Something like this:
The original code was focusing on (about) the center of the entire screen, making the marker almost invisible (as the bottom view covers it).
Thing is, I can't find the proper way to set the correct value to the Y coordinate of the map itself (meaning latitude).
What I've tried
I tried, given the height of the bottom view, and the coordinate that I've put the marker on, to calculate the delta (yet of course not change the marker itself) :
final float neededZoom = 6.5f;
int bottomViewHeight = bottomView.getHeight();
LatLng posToFocusOn = ...;
final Point point = mMap.getProjection().toScreenLocation(posToFocusOn);
final float curZoom = mMap.getCameraPosition().zoom;
point.y += bottomViewHeight * curZoom / neededZoom;
posToFocusOn = mMap.getProjection().fromScreenLocation(point);
final CameraUpdate cameraPosition = CameraUpdateFactory.newCameraPosition(new Builder().target(posToFocusOn).zoom(neededZoom).build());
Sadly, this focuses way above the marker.
The question
What's wrong with what I wrote? What can I do to fix it?
ok, I've found a workaround, which I think works on all devices (tested on 3, each with a different screen resolution and size) :
I've measured how many pixels (and then converted to DP) a change of one degree has on the marker itself.
From this, I measured the height of each view, and calculated the delta needed to move the camera.
In my case, it's this way (supposing the zoom is 6.5f) :
//measured as 223 pixels on Nexus 5, which has xxhdpi, so divide by 3
final float oneDegreeInPixels = convertDpToPixels( 223.0f / 3.0f);
final float mapViewCenter = mapViewHeight / 2.0f;
final float bottomViewHeight = ...;
final float posToFocusInPixelsFromTop = (mapViewHeight - bottomViewHeight) / 2.0f ;// can optionally add the height of the view on the top area
final float deltaLatDegreesToMove = (mapViewCenter - posToFocusInPixelsFromTop) / oneDegreeInPixels;
LatLng posToFocusOn = new LatLng(latitude - deltaLatDegreesToMove, longitude);
final CameraUpdate cameraPosition = CameraUpdateFactory.newCameraPosition(new Builder().target(posToFocusOn).zoom(neededZoom).build());
And it worked.
I wonder if it can be adjusted to support any value of zoom.
Your code is almost right, but it goes above the marker because you are taking into account bottomViewHeight when computing point.y instead of bottomViewHeight/2 (When your view's size is 200px, you only need to displace the map 100px to recenter it):
point.y += (bottomViewHeight / 2) * curZoom / neededZoom;
Update:
This is a more general approach taht takes into account the map bounds and calculates a new map bounds according to the height of your bottomView. This is zoom independent.
public void recenter() {
LatLngBounds mapBounds = mMap.getProjection().getVisibleRegion().latLngBounds;
Point nothEastPoint = mMap.getProjection().toScreenLocation(mapBounds.northeast);
Point souhWestPoint = mMap.getProjection().toScreenLocation(mapBounds.southwest);
Point newNorthEast = new Point(nothEastPoint.x, nothEastPoint.y + bottomView.getHeight() / 2);
Point newSouhWestPoint = new Point(souhWestPoint.x, souhWestPoint.y + bottomView.getHeight() / 2);
LatLngBounds newBounds = LatLngBounds.builder()
.include(mMap.getProjection().fromScreenLocation(newNorthEast))
.include(mMap.getProjection().fromScreenLocation(newSouhWestPoint))
.build();
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(newBounds, 0));
}
Note that each time you call recenter() the map will move.
I have a screen with google map, on top of screen there is one AutotextView for search place, and make a custom info window to show more details, problem is when user click on any mark custom window popup as their default behaviour (center on screen), so it mixed up autotextview, Can I change info-window position on screen, i.e bottom or anything like that.
If you want to adjust Marker location perfect center on google map screen with popup then please use below code in that i have get popup height and based on that update y axis.i hope it will help you.
public boolean onMarkerClick(Marker marker) {
//Please use fix height popup
float container_height = getResources().getDimension(R.dimen.DIP_300);
Projection projection = mGoogleMap.getProjection();
Point markerScreenPosition = projection.toScreenLocation(marker.getPosition());
Point pointHalfScreenAbove = new Point(markerScreenPosition.x,(int) (markerScreenPosition.y - (container_height / 2)));
LatLng aboveMarkerLatLng = projection.fromScreenLocation(pointHalfScreenAbove);
marker.showInfoWindow();
CameraUpdate center = CameraUpdateFactory.newLatLng(aboveMarkerLatLng);
mGoogleMap.moveCamera(center);
mGoogleMap.animateCamera(center);
marker.showInfoWindow();
return true;
}
Currently you cannot change the info window to be below marker. This is already requested and discussed on gmaps-api-issues.
I can only suggest you to animate marker after being clicked to a different position. Add OnMarkerClickListener and return true (handled) there. After that you can call:
marker.showInfoWindow();
map.animateCamera(...);
to create an effect similar to default but with a different position.
Google Map Info Window Position always on top middle of marker now. If marker is rotated.
double angle = MARKER_ROTATION_ANGLE;
double x = Math.sin(-angle * Math.PI / 180) * 0.5 + 0.5;
double y = -(Math.cos(-angle * Math.PI / 180) * 0.5 - 0.5);
marker.setInfoWindowAnchor((float)x, (float)y);
Explanation:
instead of MARKER_ROTATION_ANGLE you have to give the exact rotation of your marker. Then it will be work fine. :)
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.