How to center the camera so that marker is at the bottom of screen? (Google map api V2 Android)

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 ^ ^

Answers


I might edit this answer later to provide some code, but what I think could work is this:

  1. Get LatLng (LatLng M) of the clicked marker.
  2. 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).
  3. Compute the location of a point (New Point) that's above Point M by half of the map's height.
  4. 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);

Yes, use Projection class. More specifically:

Get Projection of the map:

 Projection projection = map.getProjection();

Get location of your marker:

 LatLng markerLocation = marker.getPosition();

Pass location to the Projection.toScreenLocation() method:

 Point screenPosition = projection.toScreenLocation(markerLocation);

Like this you can move your marker relative to center or around the screen

Point mappoint = googleMap.getProjection().toScreenLocation(new LatLng(latitude, longitude));
        mappoint.set(mappoint.x, mappoint.y-30);
        googleMap.animateCamera(CameraUpdateFactory.newLatLng(googleMap.getProjection().fromScreenLocation(mappoint)));

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);

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;
            }
        });

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 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.


Need Your Help

Which is faster? ++, += or x + 1?

c# .net performance operators

I am using C# (This question is also valid for similar languages like C++) and I am trying to figure out the fastest and most efficient way to increment. It isn't just one or two increments, in my ...