Question

I'm trying to grab a screenshot of a Google Maps V2 map using the API mentioned here, however this seems to always return a white image with the Google logo at the bottom. If it wasn't for the logo I would have been sure this isn't working at all but the logo means that something is happening and a map just isn't showing. This is roughly what I'm doing:

mv.setVisibility(View.VISIBLE);
mv.getMap().snapshot(new GoogleMap.SnapshotReadyCallback() {
    public void onSnapshotReady(Bitmap snapshot) {
        synchronized(finished) {
            finished[0] = snapshot;
            finished.notify();
        }
    }
});

I tried multiple different approaches including drawing the image to a different image and various other attempts.

The only major difference is that I am using a MapView and not a MapFragment due to some technical issues I can't switch to using fragments here.

Was it helpful?

Solution

To take a snapshot you wait for the map to load then take a snapshot.

details: implement SnapshotReadyCallback and OnMapLoadedCallback.

import com.google.android.gms.maps.GoogleMap.SnapshotReadyCallback;
import com.google.android.gms.maps.GoogleMap.OnMapLoadedCallback;

public class KmlReader extends ActionBarActivity implements
     SnapshotReadyCallback,
    OnMapLoadedCallback {

...

 if (mMap == null) {
// Try to obtain the map from the SupportMapFragment.
// mMap = mMapFragment.getMap();
// // Check if we were successful in obtaining the map.

// Try to obtain the map from the SupportMapFragment.
mMap = ((SupportMapFragment) getSupportFragmentManager()
        .findFragmentById(R.id.map)).getMap();
// use the settings maptype

// Check if we were successful in obtaining the map.
if (mMap != null) {

mMap.setOnMapLoadedCallback(this);

...

@Override
public void onMapLoaded() {
   if (mMap != null) {
        mMap.snapshot(this);
   }
}

You have a real bitmap when this fires.

@Override
public void onSnapshotReady(Bitmap bm) {
    if (CheckStorage.isExternalStorageWritable()) {
        int newHeight = 96;
        int newWidth = 96;

        int width = bm.getWidth();

        int height = bm.getHeight();

        float scaleWidth = ((float) newWidth) / width;

        float scaleHeight = ((float) newHeight) / height;

        // create a matrix for the manipulation

        Matrix matrix = new Matrix();

        // resize the bit map

        matrix.postScale(scaleWidth, scaleHeight);

        // recreate the new Bitmap

        Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height,
                matrix, false);

OTHER TIPS

That works for me. I hope it helps you:

SnapshotReadyCallback callback = new SnapshotReadyCallback() {
                Bitmap bitmap;

                @Override
                public void onSnapshotReady(Bitmap snapshot) {
                    // TODO Auto-generated method stub
                    bitmap = snapshot;
                    try {
                        String mPath = Environment.getExternalStorageDirectory().toString();
                        FileOutputStream out = new FileOutputStream(mPath + "/ "+ nom + ".png");
                        bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
                        Toast.makeText(getActivity(), "Capture OK", Toast.LENGTH_LONG).show();
                    } catch (Exception e) {
                        e.printStackTrace();
                        Toast.makeText(getActivity(), "Capture NOT OK", Toast.LENGTH_LONG).show();
                    }
                }
            };
            map.snapshot(callback);
            return true;

I needed to take a snapshot on a button press. So in the button handler I put the following code:

final SnapshotReadyCallback snapReadyCallback = new SnapshotReadyCallback() {
        Bitmap bitmap;
        @Override
        public void onSnapshotReady(Bitmap snapshot) {
            bitmap = snapshot;
            try {
                //do something with your snapshot
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

GoogleMap.OnMapLoadedCallback mapLoadedCallback = new GoogleMap.OnMapLoadedCallback() {
        @Override
        public void onMapLoaded() {
            gmap.snapshot(snapReadyCallback);
        }
};
gmap.setOnMapLoadedCallback(mapLoadedCallback);

What happens here is that I call the Map.setOnMapLoadedCallback() which will wait until the map has loaded (onMapLoaded()) and the calls the Map.snapshot(). This will guarantee that the map is allways loaded and you will not get the white image.

I found that the map.snapshot() method only works properly under special conditions. If called too early, it will crash, if called to late and to soon after setting camera it will return a snapshot of incorrect map image. I expect snapshot to return a snapshot of the map defined by the last given camera position and polylines. However it is difficult to get the GoogleMap-class to wait will it has fetched the correct tiles. It is easy if user clicks button to do snapshot because user knows when map is ready, its more difficult if it happens programmatically.

If you have the CameraPosition (LatLng, zoom) then it works well if MapFragment is created with GoogleMapOption:

GoogleMapOptions googleMapOptions = new GoogleMapOptions();
googleMapOptions.liteMode(true).mapType(GoogleMap.MAP_TYPE_NORMAL); // if this is what you want
googleMapOptions.camera(new CameraPosition(new LatLng(56, 10), 11, 0, 0));
MapFragment.newInstance(googleMapOptions);

If you don't have the CameraPosition yet, it is more difficult. You might not have the cameraposition yet if it is computed so that given markers are contained in camera, because that computation requires GoogleMap to be initialised with width/height. (chicken-and-egg problem, or do-it-yourself).

Subclass MapFragment and overwrite onCreate

public class FixedMapFragment extends MapFragment {

    public void setOncreateListener(Listener listener) {
        this.listener = listener;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = super.onCreateView(inflater, container, savedInstanceState);
        if (listener != null) {
            listener.onCreate(v, mMapManager);
        }
        return v;
    }
}

Then create your fragment, and set your onCreateListener. The onCreateListener should update cameraPosition and set polylines etc.

When onLoad on GoogleMap is invoked we should schedule yet another onLoad which does the snapshot (or we get wrong tiles again).

private void onLoad() {
    // Set camera position and polylines here
    mMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
        @Override
        public void onMapLoaded() {
            snapshot(mSnapshotCallback);
        }
    });
}

Disadvantage: Sometimes map will flash an 'incorrect' world map on screen. To avoid this we can hide the map view until snapshat is taken using

mapView.setVisibility(VIEW.INVISIBLE);

This has been tested on google-maps 8.3.0 for android.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top