How to move marker along polyline using google map
You can use for your task your approach based on custom marker animation: animate separately car movement and car turns throughout all direction points. For this You need 2 kinds of animation:
1) animation for car movement;
2) animation for car turn;
which calls each other on its end (car movement animation on end calls car turn animation and vice versa: car turn animation on its end calls car movement animation and so for all points of car path).
For example on fig.:
1) animation for car movement from P0
to P1
;
2) animation for car turn on P1
;
3) animation for car movement from P1
to P2
and so on.
Car movement animation can be implemented by method like this:
private void animateCarMove(final Marker marker, final LatLng beginLatLng, final LatLng endLatLng, final long duration) {
final Handler handler = new Handler();
final long startTime = SystemClock.uptimeMillis();
final Interpolator interpolator = new LinearInterpolator();
// set car bearing for current part of path
float angleDeg = (float)(180 * getAngle(beginLatLng, endLatLng) / Math.PI);
Matrix matrix = new Matrix();
matrix.postRotate(angleDeg);
marker.setIcon(BitmapDescriptorFactory.fromBitmap(Bitmap.createBitmap(mMarkerIcon, 0, 0, mMarkerIcon.getWidth(), mMarkerIcon.getHeight(), matrix, true)));
handler.post(new Runnable() {
@Override
public void run() {
// calculate phase of animation
long elapsed = SystemClock.uptimeMillis() - startTime;
float t = interpolator.getInterpolation((float) elapsed / duration);
// calculate new position for marker
double lat = (endLatLng.latitude - beginLatLng.latitude) * t + beginLatLng.latitude;
double lngDelta = endLatLng.longitude - beginLatLng.longitude;
if (Math.abs(lngDelta) > 180) {
lngDelta -= Math.signum(lngDelta) * 360;
}
double lng = lngDelta * t + beginLatLng.longitude;
marker.setPosition(new LatLng(lat, lng));
// if not end of line segment of path
if (t < 1.0) {
// call next marker position
handler.postDelayed(this, 16);
} else {
// call turn animation
nextTurnAnimation();
}
}
});
}
where
mMarkerIcon
is:
Bitmap mMarkerIcon;
...
mMarkerIcon = BitmapFactory.decodeResource(getResources(), R.drawable.the_car); // for your car icon in file the_car.png in drawable folder
and car icon should be North oriented:
for correct rotation apply
nextTurnAnimation()
- method called on end of car movement animation to start car turn animation:
private void nextTurnAnimation() {
mIndexCurrentPoint++;
if (mIndexCurrentPoint < mPathPolygonPoints.size() - 1) {
LatLng prevLatLng = mPathPolygonPoints.get(mIndexCurrentPoint - 1);
LatLng currLatLng = mPathPolygonPoints.get(mIndexCurrentPoint);
LatLng nextLatLng = mPathPolygonPoints.get(mIndexCurrentPoint + 1);
float beginAngle = (float)(180 * getAngle(prevLatLng, currLatLng) / Math.PI);
float endAngle = (float)(180 * getAngle(currLatLng, nextLatLng) / Math.PI);
animateCarTurn(mCarMarker, beginAngle, endAngle, TURN_ANIMATION_DURATION);
}
}
In its turn car turn animation method can be like this:
private void animateCarTurn(final Marker marker, final float startAngle, final float endAngle, final long duration) {
final Handler handler = new Handler();
final long startTime = SystemClock.uptimeMillis();
final Interpolator interpolator = new LinearInterpolator();
final float dAndgle = endAngle - startAngle;
Matrix matrix = new Matrix();
matrix.postRotate(startAngle);
Bitmap rotatedBitmap = Bitmap.createBitmap(mMarkerIcon, 0, 0, mMarkerIcon.getWidth(), mMarkerIcon.getHeight(), matrix, true);
marker.setIcon(BitmapDescriptorFactory.fromBitmap(rotatedBitmap));
handler.post(new Runnable() {
@Override
public void run() {
long elapsed = SystemClock.uptimeMillis() - startTime;
float t = interpolator.getInterpolation((float) elapsed / duration);
Matrix m = new Matrix();
m.postRotate(startAngle + dAndgle * t);
marker.setIcon(BitmapDescriptorFactory.fromBitmap(Bitmap.createBitmap(mMarkerIcon, 0, 0, mMarkerIcon.getWidth(), mMarkerIcon.getHeight(), m, true)));
if (t < 1.0) {
handler.postDelayed(this, 16);
} else {
nextMoveAnimation();
}
}
});
}
where nextMoveAnimation()
is:
private void nextMoveAnimation() {
if (mIndexCurrentPoint < mPathPolygonPoints.size() - 1) {
animateCarMove(mCarMarker, mPathPolygonPoints.get(mIndexCurrentPoint), mPathPolygonPoints.get(mIndexCurrentPoint+1), MOVE_ANIMATION_DURATION);
}
}
The mPathPolygonPoints
(geopoints of car trip) is:
private List<LatLng> mPathPolygonPoints;
And the mIndexCurrentPoint
variable is index of current point on path (it should be 0 at start of animation and incremented on each turn of path in nextTurnAnimation()
method).
TURN_ANIMATION_DURATION
- duration (in ms) animation for car turn on path geopoint;
MOVE_ANIMATION_DURATION
- duration (in ms) animation for car movement along line segment of path;
To get bearing You can use method like that:
private double getAngle(LatLng beginLatLng, LatLng endLatLng) {
double f1 = Math.PI * beginLatLng.latitude / 180;
double f2 = Math.PI * endLatLng.latitude / 180;
double dl = Math.PI * (endLatLng.longitude - beginLatLng.longitude) / 180;
return Math.atan2(Math.sin(dl) * Math.cos(f2) , Math.cos(f1) * Math.sin(f2) - Math.sin(f1) * Math.cos(f2) * Math.cos(dl));;
}
Finally You can start all animations by call animateCarMove()
once:
animateCarMove(mCarMarker, mPathPolygonPoints.get(0), mPathPolygonPoints.get(1), MOVE_ANIMATION_DURATION);
other steps of animation will be called automatically for each point of car path.
And You should take into account some "special cases" like:
1) changing sign of of turn angle (e.g. bearing changes from -120 to 150 degrees);
2) possibilities for interrupt of animation by user;
3) calculate animation duration on path segment length (e.g. 1 sec for 1 km of segment length instead of fixed MOVE_ANIMATION_DURATION
)
4) probably tune value 16
in handler.postDelayed(this, 16);
line for better performance;
5) and so on.
The problem is the way you are creating your Location
objects. You are using the Location (String provider)
constructor that Construct a new Location with a named provider (documentation):
By default time, latitude and longitude are 0, and the location has no bearing, altitude, speed, accuracy or extras.
In your case you are not creating a Location
with your desired coordinates but a Location
whose provider's name is String.valueOf(directionPoint.get(i))
but the Location
objects are created with latitude and longitude = 0.
The correct way to create the Location
objects is as follows:
Location location = new Location(LocationManager.GPS_PROVIDER);
location.setLatitude(directionPoint.get(i).latitude);
location.setLongitude(directionPoint.get(i).longitude);
Location newlocation = new Location(LocationManager.GPS_PROVIDER);
newlocation.setLatitude(directionPoint.get(i+1).latitude);
newlocation.setLongitude(directionPoint.get(i+1).longitude);
Anyway take into account that you will get an ArrayIndexOutOfBoundsException
because you are not taking into account that i+1
will be ==directionPoint.size()
in the end.
I think what you're looking for is Marker Animations.
You can animate markers so that they exhibit dynamic movement in a variety of different circumstances. To specify the way a marker is animated, use the marker's animation property, of type google.maps.Animation. The following Animation values are supported:
-DROP indicates that the marker should drop from the top of the map to its final location when first placed on the map. Animation will cease once the marker comes to rest and animation will revert to null. This type of animation is usually specified during creation of the Marker.
-BOUNCE indicates that the marker should bounce in place. A bouncing marker will continue bouncing until its animation property is explicitly set to null.
Here's a snippet from the guide:
var marker;
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 13,
center: {lat: 59.325, lng: 18.070}
});
marker = new google.maps.Marker({
map: map,
draggable: true,
animation: google.maps.Animation.DROP,
position: {lat: 59.327, lng: 18.067}
});
marker.addListener('click', toggleBounce);
}
function toggleBounce() {
if (marker.getAnimation() !== null) {
marker.setAnimation(null);
} else {
marker.setAnimation(google.maps.Animation.BOUNCE);
}
}