Android Google Maps v2 Camera Animation - android

So im not sure if this is a bug or not yet... might be or I may have missed something.
Anyway so here is the link to Google Maps V2 Camera Controls. https://developers.google.com/maps/documentation/android/views#moving_the_camera
The issue :
Animate to a location already animated to does not call onFinish();
How to replicate:
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(mLocation.getLatLng(), zoomLevel), 200, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
//DO some stuff here!
Log.d("animation", "onFinishCalled");
}
#Override
public void onCancel() {
Log.d("animation", "onCancel");
}
});
This issue may well come about when a user double taps something which called the same animation even if there is a long time between, onFinish will only be called for a successful animation. When the camera is already positioned the onFinish method will not be called!
I could go around doing checks before I do any camera animation but I don't like that as its wasteful.
Any help would be appreciated.
Thanks.

I have the same problem when i want to move camera to the same position, it seems like a bug.
Even if old and new position are not the same and the difference is so small , ex: old position.latitude = 94.54284009112, new position.latitude = 94.54284003451, it dosen't work.
my solution is to truncate values to get only old_position.latitude = new_position.latitude = 94.54, then i do a test.
There is another problem with moving camera and scroll the map in the same time, for that i disable scroll gesture before moving and enable it on the onFinish() and the onCancel().
public void animateCameraTo(final double lat, final double lng)
{
_googleMap = getMap();
CameraPosition camPosition = _googleMap.getCameraPosition();
if (!((Math.floor(camPosition.target.latitude * 100) / 100) == (Math.floor(lat * 100) / 100) && (Math.floor(camPosition.target.longitude * 100) / 100) == (Math.floor(lng * 100) / 100)))
{
_googleMap.getUiSettings().setScrollGesturesEnabled(false);
_googleMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(lat, lng)), new CancelableCallback()
{
#Override
public void onFinish()
{
_googleMap.getUiSettings().setScrollGesturesEnabled(true);
}
#Override
public void onCancel()
{
_googleMap.getUiSettings().setAllGesturesEnabled(true);
}
});
}
}
Hope this helps you ;)

Your should check the pixel distance, not the geo distance:
LatLngBounds myBounds = YOUR_BOUNDS;
LatLngBounds visibleBounds = map.getProjection().getVisibleRegion().latLngBounds;
Point myCenter = map.getProjection().toScreenLocation(myBounds.getCenter());
Point visibleCenter = map.getProjection().toScreenLocation(visibleBounds.getCenter());
int dist = (int) Math.sqrt(Math.pow(myCenter.x - visibleCenter.x, 2) + Math.pow(myCenter.y - visibleCenter.y, 2));
if (dist > YOUR_THRESHOLD) {
map.animateCamera(CameraUpdateFactory.newLatLngBounds(myBounds, YOUR_PADDING), new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
// do something
}
#Override
public void onCancel() {
// do something
}
});
} else {
// do something
}

The only solution I have found is doing the wasteful pixel value checks, setting up a bogus (but close) CameraUpdate object and making a nested animateCamera() call using the bogus CameraUpdate first and then calling another cameraUpdate() inside the first onFinish() with the correct CameraUpdate. I installed the Google MAP v2 update today but it didn't help. This is a problem on device rotation for me. I am displaying bounding rectangles and the centroid doesn't change on rotation so the animateCamera() doesn't work and when the device is rotated the selected rectangle may be partially off the screen.
Did you find another solution yet?
Thanks

Here's another workaround:
You do know the animation duration of your map animations. So you can post a delayed runnable with a delay of the animation duration + some offset and there you can check if the finished/canceld listener was called... if not you can fetch it there and call the apropriate listener

Related

Android google maps v2 execute asynctask when user stops moving the camera

I am making an app which is using google maps api v2. My problem is that when I register camera change listener, it executes each time the camera moves not when the moving stops. I want to achieve the latter. How can I modify my code to register only when moving stops ?
Here is my code:
mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition position) {
LatLngBounds bounds = mMap.getProjection().getVisibleRegion().latLngBounds;
ne = bounds.northeast;
sw = bounds.southwest;
ne1 = ne.latitude;
ne2 = ne.longitude;
sw1 = sw.latitude;
sw2 = sw.longitude;
new DownloadJSON().execute();
}
});
I implemented custom timer to execute after certain time only when conditions are met. Here is my code:
mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition position) {
LatLngBounds bounds = mMap.getProjection().getVisibleRegion().latLngBounds;
ne = bounds.northeast;
sw = bounds.southwest;
if(t!=null){
t.purge();
t.cancel();
}
t = new Timer();
t.schedule(new TimerTask() {
public void run() {
if(ne1 != ne.latitude && ne2 != ne.longitude && sw1 != sw.latitude && sw2 != sw.longitude){
ne1 = ne.latitude;
ne2 = ne.longitude;
sw1 = sw.latitude;
sw2 = sw.longitude;
Log.d("Tag","Refreshing data");
new DownloadJSON().execute();
t.cancel();
}
else{
ne1 = ne.latitude;
ne2 = ne.longitude;
sw1 = sw.latitude;
sw2 = sw.longitude;}
t.cancel(); // also just top the timer thread, otherwise, you may receive a crash report
}
}, 1000);
//new DownloadJSON().execute();
}
});
I think you can not do this "oncamerachange" triggers an event at the end of the movement, but may trigger, or not, others during the movement.
Google docs :
" Called after the camera position has changed . During an animation , listener May not be Notified of intermediate camera positions . It is always called for the end position in the animation . "
And there are no listeners for start or end camera movement.
One way to do something similar, is to save the last point ( latlang ) where you loaded the json for the last time and every time there is a " oncamerachange " event, compare the current point to the previous point and decide if you want to reload the json again or not.
Edited to post workaround from google gmaps-api-issues.
Maybe this is what are you loocking for. From the discusion on google issues.
https://code.google.com/p/gmaps-api-issues/issues/attachmentText?id=4636&aid=46360026000&name=sample_inner_listener.java&token=ABZ6GAdaQesCaWQ_IU8t9J222IBYzPceUA%3A1431366784956
new GoogleMap.OnCameraChangeListener() {
private static int CAMERA_MOVE_REACT_THRESHOLD_MS = 500;
private long lastCallMs = Long.MIN_VALUE;
#Override
public void onCameraChange(CameraPosition cameraPosition) {
final long snap = System.currentTimeMillis();
if (lastCallMs + CAMERA_MOVE_REACT_THRESHOLD_MS > snap) {
lastCallMs = snap;
return;
}
;; // here the actual call whatever you want to do on camera change
lastCallMs = snap;
}
};
Comment from the author:
hi, I've attached a sample of anon listener wrapper implementation, you can play with CAMERA_MOVE_REACT_THRESHOLD_MS to evaluate best value for you, if its not enough, you could also add kinda autosensing though, which would adapt that threshold value to the onCameraChange() call frequency

Skobbler: centerMapOnCurrentPosition() centers me in Aftrica even though blue dot is correct

I am using the skobbler sdk for maps.
I use this code to center map view on current position, but even though the blue dot is at my correct current position in Los Angeles (I verify by manually going there), the map centers me at gps(0,0).
public void onCurrentPositionUpdate(SKPosition currentPosition) {
this.currentPosition = currentPosition;
mapView.reportNewGPSPosition(this.currentPosition);
if(firstPositionUpdate){
firstPositionUpdate = false;
mapView.centerMapOnCurrentPosition();
}
}
and heres my code for initializing the map:
private void initializeMapView() {
currentPositionProvider = new SKCurrentPositionProvider(getActivity());
currentPositionProvider.setCurrentPositionListener(this);
if (DemoUtils.hasGpsModule(getActivity()) && ((LocationManager)getActivity().getSystemService(Context.LOCATION_SERVICE)).isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
currentPositionProvider.requestLocationUpdates(true, true, true);
}
SKMapViewHolder mapViewGroup = (SKMapViewHolder) getView().findViewById(R.id.map_surface_holder);
mapView = mapViewGroup.getMapSurfaceView();
mapView.setMapSurfaceListener(this);
mapView.getMapSettings().setFollowerMode(SKMapSettings.SKMapFollowerMode.NONE);
mapView.getMapSettings().setMapRotationEnabled(true);
mapView.getMapSettings().setMapZoomingEnabled(true);
mapView.getMapSettings().setMapPanningEnabled(true);
mapView.getMapSettings().setZoomWithAnchorEnabled(true);
mapView.getMapSettings().setInertiaRotatingEnabled(true);
mapView.getMapSettings().setInertiaZoomingEnabled(true);
mapView.getMapSettings().setInertiaPanningEnabled(true);
SKVersioningManager.getInstance().setMapUpdateListener(this);
mapView.centerMapOnPosition(new SKCoordinate( -118.123,34.123));
//launchRouteCalculation();
}
It seems that reportNewGPSPosition(this.currentPosition); does not work instantly.
I noticed that if I simply delay the call to centerMapOnCurrentPosition(), the map is centered correctly. There are two work arounds:
-set location manually:
mapView.centerMapOnPosition(new SKCoordinate( currentPosition.getLongitude(), currentPosition.getLatitude()));
-or create a delayed runnable to call to perform the centering at a future time:
android.os.Handler handler = new android.os.Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
mapView.centerMapOnCurrentPosition();
mapView.setZoom(10);
}
},100);
note: This issue only becomes evident because of the if statement I put in to only center the map on the first update of location. Another work around would be to put a use a counter and center map on the second time the location is updated.
Also if implementing this with the mentioned if statement, don't forget to consider accuracy; it would be bad to center when the location is not accurate, and not center on a future location update which IS accurate.

Android maps v2 becomes low quality when map area is automatically moved

I am simulating a car moving on a pre-recorded path on Android maps v2. When I zoom on the path by hand, it works great, but when I move the camera over the path with mMap.animateCamera(), it doesn't load the visible map area, I only get a very pixelated, low quality map. If I touch the screen and move the map or zoom a little, then it loads again this part.
How can I achieve, that it always loads clearly the visible part?
EDIT:
I added an example image: this is what I see when I don't touch the map. After I touch it, it becomes clear (similar to the bottom left part).
EDIT2:
I have an idea, that this is because Google want's to prevent the map to be cached by quickly moving the camera over an area. Is it possible, that this is the cause of this issue? (The map is showing Budapest, Hungary, which part of the map you can not download for offline use...) But here I only want to show the animation and place markers, I only need the visible area to be cached - are there any way to workaround this behaviour?
EDIT3:
The animation code:
new Thread(new Runnable() {
#Override
public void run() {
// ... Calculating and sending new location, in an infinite loop with sleeps for timing
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
mMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(location.getLatitude(), location.getLongitude())));
}
});
}
}).start();
Finally found a solution. I was able to recreate your problem using the code you provided. I replaced your code with the following and it worked for me.
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
// ... Calculating and sending new location, in an infinite loop with sleeps for timing
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
mMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(location.getLatitude(), location.getLongitude())));
}
});
}
}, 0, 2000);
Just remove your code for sleeping and replace the last argument in scheduleAtFixedRate (the 2000) with whatever value you were using for sleeping.
I had the same issue. Since it did not happen when not animating the camera, it had to be something related to that.
Apperently the camera has to be able to finish its operation, before it will update the background/roads etc.
Since my app updates the position every second and the map needs about 2 seconds to complete I end up with pixelated roads and no surroundings at all.
The solution is to use one of overloads of animateCamera:
public final void animateCamera (CameraUpdate update, int durationMs,
GoogleMap.CancelableCallback callback)
Moves the map according to the update with an animation over a
specified duration, and calls an optional callback on completion. See
CameraUpdateFactory for a set of updates.
I used for my case a duration of 900msec, so the animation is done before it receives a new location.
To get the callback to work you need to implement GoogleMap.CancelableCallback to your class. This requires you to add two overrides:
#Override
public void onFinish() {
}
#Override
public void onCancel() {
}
They are not required to get the problem solved, altough you are free to add extra logic there.
The call to update the camera can look like this:
cameraPosition = new CameraPosition.Builder()
.target(current)
.zoom(zoomLevel)
.bearing(bearing)
.tilt(tilt)
.build();
CameraUpdate update = CameraUpdateFactory.newCameraPosition(cameraPosition);
map.animateCamera(update, 900, this);

Zooming and animating to point mapview android

So in my current application, I want to zoom to a lat/long point, but also use the animateTo (or equivalent) to make sure the screen is properly centered. Currently I'm just doing something like this:
_mapController.animateTo(new GeoPoint(googleLat, googleLng));
// Smooth zoom handler
int zoomLevel = _mapView.getZoomLevel();
int targetZoomLevel = AppConstants.MAP_ZOOM_LEVEL_SWITCH;
long delay = 0;
while (zoomLevel++ < targetZoomLevel) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
_mapController.zoomIn();
}
}, delay);
delay += 350; // Change this to whatever is good on the device
}
This kind of works, but what happens is that my thread starts, BEFORE the 'animate to' finishes, so it zooms in and then 'jumps' to display the correct center geoPoint. Is there a way to smoothly 'drill down' to a certain zoom level, as well as move to a geoPoint at the same time? Similar to how the official Googl eMaps application zooms to an address after you've searched for it is what I'm looking to do.
I would do something like:
_mapController.animateTo(new GeoPoint(googleLat,googleLong), new Runnable() {
public void run()
{
_mapController.zoomIn ();
}
});
This will achieve a pan-then-zoom effect. You can try your same logic inside the runnable to perform multi-step zoomIn's.

knowing when map has stopped scrolling (like "moveend" in javascript API)

I need to detect when a MapView has been scrolled or zoomed, like the "moveend" event in the javascript API. I'd like to wait until the view has stopped moving, so I can then detect if I need to query my server for items withing the viewing rectangle, and if so send out a request. (actually I send a request for a slightly larger area than the viewing rectangle)
Obviously, I'd rather not send out a request for data if the view is still moving. But even worse is that I don't know that I need to send another request, leaving areas of the map missing markers.
Currently I am subclassing MapView and handling the onTouchEvent as follows:
public boolean onTouchEvent(android.view.MotionEvent ev) {
super.onTouchEvent (ev);
if (ev.getAction() == MotionEvent.ACTION_UP) {
GeoPoint center = getMapCenter();
int latSpan = getLatitudeSpan(), lngSpan = getLongitudeSpan();
/* (check if it has moved enough to need a new set of data) */
}
return true;
}
Problem is, I don't know if the view has stopped, since scrolling tends to have inertia and can keep going past the "ACTION_UP" event.
Is there some event I can tap into that will alert me when a mapview is done moving (or zooming)? If not, has anyone written logic to detect this? In theory I could make a guess by looking at all the actions, and set something to come along bit later and check it...but...that seems messy and a PITA. But if someone has already written it.... :)
This is the method I am using at the moment, I have used this and tested it, works well.
Just make sure you make your draw() method efficient. (Avoid GC in it).
//In map activity
class MyMapActivity extends MapActivity {
protected void onCreate(Bundle savedState){
setContent(R.layout.activity_map);
super.onCreate(savedSate);
OnMapMoveListener mapListener = new OnMapMoveListener(){
public void mapMovingFinishedEvent(){
Log.d("MapActivity", "Hey look! I stopped scrolling!");
}
}
// Create overlay
OnMoveOverlay mOnMoveOverlay = new OnMoveOverlay(mapListener);
// Add overlay to view.
MapView mapView = (MapView)findViewById(R.id.map_view);
// Make sure you add as the last overlay so its on the top.
// Otherwise other overlays could steal the touchEvent;
mapView.getOverlays().add(mOnMoveOverlay);
}
}
This is your OnMoveOverlay class
//OnMoveOverlay
class OnMoveOverlay extends Overlay
{
private static GeoPoint lastLatLon = new GeoPoint(0, 0);
private static GeoPoint currLatLon;
// Event listener to listen for map finished moving events
private OnMapMoveListener eventListener = null;
protected boolean isMapMoving = false;
public OnMoveOverlay(OnMapMoveListener eventLis){
//Set event listener
eventListener = eventLis;
}
#Override
public boolean onTouchEvent(android.view.MotionEvent ev)
{
super.onTouchEvent(ev);
if (ev.getAction() == MotionEvent.ACTION_UP)
{
// Added to example to make more complete
isMapMoving = true;
}
//Fix: changed to false as it would handle the touch event and not pass back.
return false;
}
#Override
public void draw(Canvas canvas, MapView mapView, boolean shadow)
{
if (!shadow)
{
if (isMapMoving)
{
currLatLon = mapView.getProjection().fromPixels(0, 0);
if (currLatLon.equals(lastLatLon))
{
isMapMoving = false;
eventListener.mapMovingFinishedEvent();
}
else
{
lastLatLon = currLatLon;
}
}
}
}
public interface OnMapMoveListener{
public void mapMovingFinishedEvent();
}
}
Just implement your own listener eventListener.mapMovingFinishedEvent(); and fire the map moving bool by another method like above and your sorted.
The idea is when the map is moving the pixel projection to the coords will be changing, once they are the same, you have finished moving.
I have updated this with newer more complete code, there was an issue with it double drawing.
We don't do anything on the shadow pass as we would just double calculate per draw pass which is a waste.
Feel Free to ask any questions :)
Thanks,
Chris
I had the same problem and "solved" it in a similar way, but I think less complicated:
As overriding computeScroll() didn't work for me, I overrode onTouchEvent, too. Then I used a Handler, that invokes a method call after 50ms, if the map center changed, the same happens again, if the map center didn't change, the listener is called. The method I invoke in onTouchEvent looks like this:
private void refreshMapPosition() {
GeoPoint currentMapCenter = getMapCenter();
if (oldMapCenter==null || !oldMapCenter.equals(currentMapCenter)) {
oldMapCenter = currentMapCenter;
handler.postDelayed(new Runnable() {
#Override
public void run() {
refreshMapPosition();
}
}, 50);
}
else {
if (onScrollEndListener!=null)
onScrollEndListener.onScrollEnd(currentMapCenter);
}
}
But I'm waiting for a real solution for this, too ...
I don't really have a satisfactory solution to this problem, but I can tell what I did to partially solve it.
I subclassed MapView and overrode the computeScroll() method, which gets the current centre-point of the map and compares it with the last-known centre-point (stored as a volatile field in the subclass). If the centre-point has changed, it fires an event to the listener of the map (I defined a custom listener interface for this).
The listener is an activity that instantiates a subclass of AsyncTask and executes it. This task pauses for 100ms in its doInBackGround() method, before performing the server data fetch.
When the listener activity receives a second map-move event (which it will do because of the stepping effect of the map movement), it checks the status of the just-executed AsyncTask. If that task is still running, it will cancel() it. It then creates a new task, and executes that.
The overall effect is that when the listeners get the flurry of map-moved events a few milliseconds apart, the only one that actually triggers the task to perform the server-fetch is the last one in the sequence. The downside is that it introduces a slight delay between the map movement happening, and the server fetch occurring.
I'm not happy with it, it's ugly, but it mitigates the problem. I would love to see a better solution to this.
I solved it using a thread and it seems to work quite good. It not only detects center changes but also zoom changes. Well, the detection is done after zooming and scrolling ends. If you need to detect zooming changes when you move up the first finger then you can modify my code a bit to detect different pointers. But I didn't need it, so didn't include it and left some homework for you :D
public class CustomMapView extends MapView {
private GeoPoint pressGP;
private GeoPoint lastGP;
private int pressZoom;
private int lastZoom;
public boolean onTouchEvent( MotionEvent event ) {
switch( event.getAction() ) {
case MotionEvent.ACTION_DOWN:
pressGP = getMapCenter();
pressZoom = getZoomLevel();
break;
case MotionEvent.ACTION_UP:
lastGP = getMapCenter();
pressZoom = getZoomLevel();
if( !pressGP.equals( lastGP ) ) {
Thread thread = new Thread() {
public void run() {
while( true ) {
try {
Thread.sleep( 100 );
} catch (InterruptedException e) {}
GeoPoint gp = getMapCenter();
int zl = getZoomLevel();
if( gp.equals( lastGP ) && zl == lastZoom)
break;
lastGP = gp;
lastZoom = zl;
}
onMapStop( lastGP );
}
};
thread.start();
}
break;
}
return super.onTouchEvent( event );
}
public void onMapStop( GeoPoint point , int zoom ){
// PUT YOUR CODE HERE
}
}
With the latest version of google maps API (V2) there is a listener to do this, i.e. GoogleMap.OnCameraChangeListener.
mGoogleMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener()
{
#Override
public void onCameraChange(CameraPosition cameraPosition)
{
Toast.makeText(mActivity, "Longitude : "+cameraPosition.target.longitude
+", Latitude : "+cameraPosition.target.latitude, Toast.LENGTH_SHORT).show();
}
});

Categories

Resources