Rotate Marker and Move Animation on Map like Uber Android

JavaAndroidXml

Java Problem Overview


I am working on a project similar to UBER, Lyft or OLA ie. Map on the home with available moving Cars. I'm looking for some kind of Library which can make Cars move and take turn smoothly just like UBER. For now I was able to move car smoothly from one lat-long to another with the below code. But tricky part is Taking turn and make sure the car face to front when moving to direction.

Smooth Moving Car Code:

    final LatLng SomePos = new LatLng(12.7796354, 77.4159606);

    try {
        if (googleMap == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                googleMap = ((MapFragment) getFragmentManager().findFragmentById(R.id.map)).getMap();
            }
        }
        googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
        googleMap.setMyLocationEnabled(true);
        googleMap.setTrafficEnabled(false);
        googleMap.setIndoorEnabled(false);
        googleMap.setBuildingsEnabled(true);
        googleMap.getUiSettings().setZoomControlsEnabled(true);
        googleMap.moveCamera(CameraUpdateFactory.newLatLng(SomePos));
        googleMap.moveCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition.Builder()
                .target(googleMap.getCameraPosition().target)
                .zoom(17)
                .bearing(30)
                .tilt(45)
                .build()));

        myMarker = googleMap.addMarker(new MarkerOptions()
                .position(SomePos)
                .icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_launcher))
                .title("Hello world"));


        googleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
            @Override
            public boolean onMarkerClick(Marker arg0) {

                final LatLng startPosition = myMarker.getPosition();
                final LatLng finalPosition = new LatLng(12.7801569, 77.4148528);
                final Handler handler = new Handler();
                final long start = SystemClock.uptimeMillis();
                final Interpolator interpolator = new AccelerateDecelerateInterpolator();
                final float durationInMs = 3000;
                final boolean hideMarker = false;

                handler.post(new Runnable() {
                    long elapsed;
                    float t;
                    float v;

                    @Override
                    public void run() {
                        // Calculate progress using interpolator
                        elapsed = SystemClock.uptimeMillis() - start;
                        t = elapsed / durationInMs;

                        LatLng currentPosition = new LatLng(
                                startPosition.latitude * (1 - t) + finalPosition.latitude * t,
                                startPosition.longitude * (1 - t) + finalPosition.longitude * t);

                        myMarker.setPosition(currentPosition);

                        // Repeat till progress is complete.
                        if (t < 1) {
                            // Post again 16ms later.
                            handler.postDelayed(this, 16);
                        } else {
                            if (hideMarker) {
                                myMarker.setVisible(false);
                            } else {
                                myMarker.setVisible(true);
                            }
                        }
                    }
                });

                return true;

            }

        });

    } catch (Exception e) {
        e.printStackTrace();
    }

Java Solutions


Solution 1 - Java

I recently came across the same use-case. Here is my solution on it.

First, I would like to thank @VipiN for sharing "The Smooth Moving Car Code". It works smoothly.

The second part is to place car-marker in the right direction and rotate it according to turns. To achieve this I calculated the bearing or heading angle between two successive points(i.e. location updates you receive from device/server). This link will help you understand the maths behind it.

The following code will give you bearing between two locations:

private double bearingBetweenLocations(LatLng latLng1,LatLng latLng2) {

    double PI = 3.14159;
    double lat1 = latLng1.latitude * PI / 180;
    double long1 = latLng1.longitude * PI / 180;
    double lat2 = latLng2.latitude * PI / 180;
    double long2 = latLng2.longitude * PI / 180;

    double dLon = (long2 - long1);

    double y = Math.sin(dLon) * Math.cos(lat2);
    double x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
            * Math.cos(lat2) * Math.cos(dLon);

    double brng = Math.atan2(y, x);

    brng = Math.toDegrees(brng);
    brng = (brng + 360) % 360;
    
    return brng;
}

Finally, we need to rotate the car-marker by the angle that we get from above method.

private void rotateMarker(final Marker marker, final float toRotation) {
    if(!isMarkerRotating) {
        final Handler handler = new Handler();
        final long start = SystemClock.uptimeMillis();
        final float startRotation = marker.getRotation();
        final long duration = 1000;

        final Interpolator interpolator = new LinearInterpolator();

        handler.post(new Runnable() {
            @Override
            public void run() {
                isMarkerRotating = true;

                long elapsed = SystemClock.uptimeMillis() - start;
                float t = interpolator.getInterpolation((float) elapsed / duration);

                float rot = t * toRotation + (1 - t) * startRotation;

                marker.setRotation(-rot > 180 ? rot / 2 : rot);
                if (t < 1.0) {
                    // Post again 16ms later.
                    handler.postDelayed(this, 16);
                } else {
                    isMarkerRotating = false;
                }
            }
        });
    }
}

Cheers!

Solution 2 - Java

Here is my code to move marker like uber. i have shown two ways to move marker .

Important Note: To move car on proper road [like ola,uber] you need to use road api provided by google

1.By static latitude and longitude

2.By Real time Latitude and longitude

package com.gangsofcoder.googlemapdemo;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.PersistableBundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.animation.LinearInterpolator;
import android.widget.Button;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.RequestParams;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;

import cz.msebera.android.httpclient.Header;

public class MoveCar extends AppCompatActivity {
    private GoogleMap googleMap;
    SupportMapFragment mapFragment;
    Marker marker;
    private boolean isMarkerRotating = false;
    ArrayList<LatLng> listOfPoints = new ArrayList<>();
    int currentPt = 0;
    LatLng finalPosition;
    Marker mMarker;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    setUpMapIfNeeded();
    //new location details
    listOfPoints.add(new LatLng(30.701623, 76.684220));
    listOfPoints.add(new LatLng(30.702486, 76.685487));
    listOfPoints.add(new LatLng(30.703135, 76.684891));
    listOfPoints.add(new LatLng(30.703256, 76.685000));
    listOfPoints.add(new LatLng(30.703883, 76.685941));
    listOfPoints.add(new LatLng(30.703413, 76.685190));
}

private void setUpMapIfNeeded() {
    if (mapFragment == null) {
        mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);

        if (mapFragment != null) {
            mapFragment.getMapAsync(new OnMapReadyCallback() {
                @Override
                public void onMapReady(GoogleMap googleMap) {
                    loadMap(googleMap);
                }
            });
        }
    }
}

private void loadMap(GoogleMap map) {
    googleMap = map;

    mMarker = googleMap.addMarker(new MarkerOptions().position(new LatLng(30.701623, 76.684220)).icon(BitmapDescriptorFactory.fromResource(R.drawable.icon_car)));      
  

    final Handler handler = new Handler();
    //Code to move car along static latitude and longitude
	
  /*  handler.postDelayed(new Runnable() {
        @Override
        public void run() {

            if (currentPt < listOfPoints.size()) {
                //post again
                Log.d("tess", "inside run ");
                Location targetLocation = new Location(LocationManager.GPS_PROVIDER);
                targetLocation.setLatitude(listOfPoints.get(currentPt).latitude);
                targetLocation.setLongitude(listOfPoints.get(currentPt).longitude);
                animateMarkerNew(targetLocation, mMarker);
                handler.postDelayed(this, 3000);
                currentPt++;
            } else {
                Log.d("tess", "call back removed");
                //removed callbacks
                handler.removeCallbacks(this);
            }
        }
    }, 3000);*/

	//Here move marker along real time updates
    final RequestParams params = new RequestParams();
    params.put("source_lattitude", "lat");
    params.put("source_longitude", "long");
    params.put("date", "date");
    
	//new handler
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {

            LoopjHttpClient.post(getString(R.string.default_upload_website), params, new AsyncHttpResponseHandler() {
                @Override
                public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
                  
                    try {
                        JSONObject jsonObject = new JSONObject(new String(responseBody));
                        String status = jsonObject.getString("status");
                        String text = jsonObject.getString("text");
                        //reading json array
                        JSONArray jsonArray = jsonObject.getJSONArray("result");
                        String source = jsonArray.getJSONObject(0).getString("source");

                        String[] latLong = source.split(",");
                        Location location = new Location(LocationManager.GPS_PROVIDER);
                        location.setLatitude(Double.parseDouble(latLong[0]));
                        location.setLongitude(Double.parseDouble(latLong[1]));
						//calling method to animate marker	
                        animateMarkerNew(location, mMarker);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
                    Log.d("onFailure", "onFailure");
                }
            });

            handler.postDelayed(this, 3000);
        }
    }, 3000);

}

private void animateMarkerNew(final Location destination, final Marker marker) {

    if (marker != null) {
          
                final LatLng startPosition = marker.getPosition();
                final LatLng endPosition = new LatLng(destination.getLatitude(), destination.getLongitude());

                final float startRotation = marker.getRotation();
                final LatLngInterpolatorNew latLngInterpolator = new LatLngInterpolatorNew.LinearFixed();

                ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
                valueAnimator.setDuration(3000); // duration 3 second
                valueAnimator.setInterpolator(new LinearInterpolator());
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        try {
                            float v = animation.getAnimatedFraction();
                            LatLng newPosition = latLngInterpolator.interpolate(v, startPosition, endPosition);
                            marker.setPosition(newPosition);
                            googleMap.moveCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition.Builder()
                                    .target(newPosition)
                                    .zoom(15.5f)
                                    .build()));

                            marker.setRotation(getBearing(startPosition, new LatLng(destination.getLatitude(), destination.getLongitude())));
                        } catch (Exception ex) {
                            //I don't care atm..
                        }
                    }
                });
                valueAnimator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        
                        // if (mMarker != null) {
                            // mMarker.remove();
                        // }
                        // mMarker = googleMap.addMarker(new MarkerOptions().position(endPosition).icon(BitmapDescriptorFactory.fromResource(R.drawable.icon_car)));

                    }
                });
                valueAnimator.start();
    }
}

private interface LatLngInterpolatorNew {
    LatLng interpolate(float fraction, LatLng a, LatLng b);

    class LinearFixed implements LatLngInterpolatorNew {
        @Override
        public LatLng interpolate(float fraction, LatLng a, LatLng b) {
            double lat = (b.latitude - a.latitude) * fraction + a.latitude;
            double lngDelta = b.longitude - a.longitude;
            // Take the shortest path across the 180th meridian.
            if (Math.abs(lngDelta) > 180) {
                lngDelta -= Math.signum(lngDelta) * 360;
            }
            double lng = lngDelta * fraction + a.longitude;
            return new LatLng(lat, lng);
        }
    }
}


//Method for finding bearing between two points
private float getBearing(LatLng begin, LatLng end) {
    double lat = Math.abs(begin.latitude - end.latitude);
    double lng = Math.abs(begin.longitude - end.longitude);

    if (begin.latitude < end.latitude && begin.longitude < end.longitude)
        return (float) (Math.toDegrees(Math.atan(lng / lat)));
    else if (begin.latitude >= end.latitude && begin.longitude < end.longitude)
        return (float) ((90 - Math.toDegrees(Math.atan(lng / lat))) + 90);
    else if (begin.latitude >= end.latitude && begin.longitude >= end.longitude)
        return (float) (Math.toDegrees(Math.atan(lng / lat)) + 180);
    else if (begin.latitude < end.latitude && begin.longitude >= end.longitude)
        return (float) ((90 - Math.toDegrees(Math.atan(lng / lat))) + 270);
    return -1;
}
}

Solution 3 - Java

Since its been bit confusing for SO users to look for working code in two different posts. Here is the working code for Rotate and Move Marker which seamlessly worked for me.

in MainActivity.java

public void rotateMarker(final Marker marker, final float toRotation, final float st) {
    final Handler handler = new Handler();
    final long start = SystemClock.uptimeMillis();
    final float startRotation = st;
    final long duration = 1555;

    final Interpolator interpolator = new LinearInterpolator();

    handler.post(new Runnable() {
        @Override
        public void run() {
            long elapsed = SystemClock.uptimeMillis() - start;
            float t = interpolator.getInterpolation((float) elapsed / duration);

            float rot = t * toRotation + (1 - t) * startRotation;

            marker.setRotation(-rot > 180 ? rot / 2 : rot);
            if (t < 1.0) {
                // Post again 16ms later.
                handler.postDelayed(this, 16);
            }
        }
    });
}


public void animateMarker(final LatLng toPosition,final boolean hideMarke) {
    final Handler handler = new Handler();
    final long start = SystemClock.uptimeMillis();
    Projection proj = googleMap.getProjection();
    Point startPoint = proj.toScreenLocation(m.getPosition());
    final LatLng startLatLng = proj.fromScreenLocation(startPoint);
    final long duration = 5000;

    final Interpolator interpolator = new LinearInterpolator();

    handler.post(new Runnable() {
        @Override
        public void run() {
            long elapsed = SystemClock.uptimeMillis() - start;
            float t = interpolator.getInterpolation((float) elapsed
                    / duration);
            double lng = t * toPosition.longitude + (1 - t)
                    * startLatLng.longitude;
            double lat = t * toPosition.latitude + (1 - t)
                    * startLatLng.latitude;
            m.setPosition(new LatLng(lat, lng));

            if (t < 1.0) {
                // Post again 16ms later.
                handler.postDelayed(this, 16);
            } else {
                if (hideMarke) {
                    m.setVisible(false);
                } else {
                    m.setVisible(true);
                }
            }
        }
    });
}

Solution 4 - Java

Finally wrote the code that works exactly in the similar manner that OLA CABS does...

Here it is -

  1. Put your google maps fragment in a Relative layout and put the marker(as image view ) in the center -

  2. In your fragment's code once you have setup all the google maps basic working write the following code for the onMapReady(GoogleMap googleMap) function-

My Code is as follows -

//Step 1 -

 <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">



<fragment
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MapsActivity_for_request_pages" />

    <ImageView
        android:layout_width="30sp"
        android:layout_height="30sp"
        android:layout_centerInParent="true"
        android:id="@+id/central_marker"
        android:src="@drawable/marker_pic"/>

</RelativeLayout>

//Step 2 -

@Override
public void onMapReady(GoogleMap googleMap) {
 

     central_marker = (ImageView)v.findViewById(R.id.central_marker);
     int init_loc = 0,final_loc = -300;
     mMap = googleMap;
         
 final  CountDownTimer timer = new CountDownTimer(300,300) {
       @Override
       public void onTick(long millisUntilFinished) {

       }

       @Override
       public void onFinish() {
           init_loc = 0;
           ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(central_marker, "translationY", final_loc, init_loc);
           objectAnimatorY.setDuration(200);
           objectAnimatorY.start();
       }
   };

    
    mMap.setOnCameraMoveStartedListener(new GoogleMap.OnCameraMoveStartedListener() {
        @Override
        public void onCameraMoveStarted(int i) {

            System.out.println("Camera started moving worked");
            timer.cancel();
            ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(central_marker, "translationY", init_loc, final_loc);
            objectAnimatorY.setDuration(200);
            objectAnimatorY.start();
            init_loc = -300;

        }
    });



    mMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
        @Override
        public void onCameraIdle() {
            System.out.println("Camera idle worked");
            if(initial_flag!=0)
            {
                System.out.println("Camera Setting timer now");
                timer.cancel();
                timer.start();
            }
            initial_flag++;
            System.out.println("Camera Value of initial_flag ="+initial_flag);
        }
    });

}

Solution 5 - Java

Firstly,I would like to thank you both @Vipin Negi & @Prasad for such a good & awesomely working code.Since there are many pending queries above, I want to make all the above stuff a little simpler. Guys, just follow the following steps to achieve the marker rotation.

1. Define below two methods in your MainActivity.java file

private double bearingBetweenLocations(LatLng latLng1, LatLng latLng2) {

    double PI = 3.14159;
    double lat1 = latLng1.latitude * PI / 180;
    double long1 = latLng1.longitude * PI / 180;
    double lat2 = latLng2.latitude * PI / 180;
    double long2 = latLng2.longitude * PI / 180;

    double dLon = (long2 - long1);

    double y = Math.sin(dLon) * Math.cos(lat2);
    double x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
            * Math.cos(lat2) * Math.cos(dLon);

    double brng = Math.atan2(y, x);

    brng = Math.toDegrees(brng);
    brng = (brng + 360) % 360;

    return brng;
}

&

private void rotateMarker(final Marker marker, final float toRotation) {
    final Handler handler = new Handler();
    final long start = SystemClock.uptimeMillis();
    final float startRotation = marker.getRotation();
    final long duration = 1000;

    final Interpolator interpolator = new LinearInterpolator();

    handler.post(new Runnable() {
        @Override
        public void run() {

            long elapsed = SystemClock.uptimeMillis() - start;
            float t = interpolator.getInterpolation((float) elapsed / duration);

            float rot = t * toRotation + (1 - t) * startRotation;

            marker.setRotation(-rot > 180 ? rot / 2 : rot);
            if (t < 1.0) {
                // Post again 16ms later.
                handler.postDelayed(this, 16);
            }
        }
    });

}

2. After this, add the following lines to the code where you want your marker to rotate.

double bearing = bearingBetweenLocations(m.getPosition(), updatedLatLng);
rotateMarker(m, (float) bearing);

Note that "m" is your marker object that you want to rotate. And, you're done!!!

If you want marker animation related help,you can use this code.

Solution 6 - Java

Try google default rotation or see the detail explanation Markers Rotation

 private void updateCamera(LatLng currentLatLng, Location location) {
    //googleMap.clear();
    if (marker == null) {
        MarkerOptions options = new MarkerOptions();
        options.position(currentLatLng);
        options.icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_driver_car_pin_v));
        options.flat(true);
        options.anchor(0.5f, 0.5f);
        marker = googleMap.addMarker(options);
    } else {
        marker.setPosition(currentLatLng);
        marker.setRotation(location.getBearing());
    }
    CameraPosition cameraPosition = new CameraPosition.Builder(googleMap.getCameraPosition())
            .target(currentLatLng).zoom(18).build();
    googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVipiN NegiView Question on Stackoverflow
Solution 1 - JavaPrasadView Answer on Stackoverflow
Solution 2 - JavaSuraj BahadurView Answer on Stackoverflow
Solution 3 - JavaVipiN NegiView Answer on Stackoverflow
Solution 4 - JavaShubham AroraView Answer on Stackoverflow
Solution 5 - JavaAnshuman ParasharView Answer on Stackoverflow
Solution 6 - JavaMujahid KhanView Answer on Stackoverflow