Search code examples
javaandroidgoogle-mapsmarkergoogle-polyline

How to make a Marker traverse a Polyline in Java?


I'm working on an app, similar to uber, didi, etc. I have a problem when making the animations of the vehicles (going from point A to point B), I found this code on Internet and it works quite well:

public void animateMarker(final LatLng startPosition, final LatLng toPosition,final boolean hideMarke) {
        final Handler handler = new Handler();
        final Marker m = map.addMarker(new MarkerOptions()
                .position(startPosition)
                .icon(BitmapDescriptorFactory.fromResource(R.drawable.bus2))
                .title("Camión"));
        final long start = SystemClock.uptimeMillis();
        Projection proj = map.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);
                    }
                }
            }
        });
        markers_animations.add(m);
    }

I have this method that is in charge of passing it the positions of a list with all the coordinates that I require for the Polylines:

private void controlAnimaciones(List<LatLng> ruta) {
        for (int i=0; i<ruta.size()-1; i++) {
            if (i<ruta.size()) {
                animateMarker3(ruta.get(i), ruta.get(i+1), true);
            }
        }
    }

It does what I expected it to do, if it moves the marker from point A to point B, but, just by iterating the list, I don't know how to explain it, there are many markers that move only from one element of the list to the next and after that they stop. What I want to do is to achieve that a single marker can move through all the points of the list, I have been trying in some ways with the code I got from the internet, to try to understand it, but I have not had much success. How could I do it?


Solution

  • Currently in your posted code, animateMarker creates a marker for each "segment" of the polyline - it starts and stops the movement of the marker along the one segment, and it does this asynchronously. This would have the effect of a marker created (simultaneously) at the start of every segment and each one animated in parallel (nearly). Not what you want.

    So you have two things to change:

    1. Create the marker once at the start of the first segment.
    2. Continue the animation of the second and later segments after the first (or previous leg completes.)

    Easy way to do the above is to modify the animateMarker to accept the list of points rather than a single point. The list of points is your polyline (ruta).

    I made some comments where the method was modified from your original.

    public void animateMarker(List<LatLng> pts,final boolean hideMarker) {
    
       // Simple check to make sure there are enough points in the list.
       if (pts.size() <= 1) {
            // need at least two points.
            return;
        }
    
        final Handler handler = new Handler();
    
        // Use first point in list as start.
        final Marker m = mMap.addMarker(new MarkerOptions()
                .position(pts.get(0))
                .title("Camión"));
        Projection proj = mMap.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() {
            // start at first segment
            private int segment = 0;
            // initial start time
            long start = SystemClock.uptimeMillis();
            @Override
            public void run() {
                long elapsed = SystemClock.uptimeMillis() - start;
                float t = interpolator.getInterpolation((float) elapsed
                        / duration);
                // Use next point in list as destination
                double lng = t * pts.get(segment+1).longitude + (1 - t)
                        * pts.get(segment).longitude;
                double lat = t * pts.get(segment+1).latitude + (1 - t)
                        * pts.get(segment).latitude;
                m.setPosition(new LatLng(lat, lng));
    
                if (t < 1.0) {
                    // Post again 16ms later.
                    handler.postDelayed(this, 16);
                } 
    
                // check if to move to next segment (or done)
                else if (segment < (pts.size()-2)) {
                    // move to next segment
                    segment++;
                    start = SystemClock.uptimeMillis();
                    handler.postDelayed(this,16);
                } else {
                    if (hideMarker) {
                        m.setVisible(false);
                    } else {
                        m.setVisible(true);
                    }
                }
            }
        });
        markers_animations.add(m);
    }
    

    And to call the animation with your list just modify your

    private void controlAnimaciones(List<LatLng> ruta) {
        animateMarker(ruta, true);
    }
    

    And this is the result.

    enter image description here

    (Note that the "velocity" of the marker movement is dependent on the length of the segment. Since the duration is fixed per segment, longer segments would make the marker appear to move faster. You could change this to a constant velocity for any segment by changing duration as a function of the distance between the two points.)


    Note that the animaterMarker supports multiple animations naturally without modification. So in this example the map onMapClickListener invokes controlAnimaciones on every map click which was done every few seconds for demonstration purposes.

    enter image description here

    Hope this helps!