Skip to content Skip to sidebar Skip to footer

How To Update Marker Using Live Location Along A Polyline?

My question titles seems to be an existing one, but here is my complete scenario. I have an activity for Map based operations, where am drawing a polyline along a road, lets say a

Solution 1:

You can snap marker to the path by projection of marker on nearest path segment. Nearest segment you can find via PolyUtil.isLocationOnPath():

PolyUtil.isLocationOnPath(carPos, segment, true, 30)

and projections of marker to that segment you can find via converting geodesic spherical coordinates into orthogonal screen coordinates calculating projection orthogonal coordinates and converting it back to spherical (WGS84 LatLng -> Screen x,y -> WGS84 LatLng):

PointcarPosOnScreen= projection.toScreenLocation(carPos);
Pointp1= projection.toScreenLocation(segment.get(0));
Pointp2= projection.toScreenLocation(segment.get(1));
PointcarPosOnSegment=newPoint();

floatdenominator= (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
// p1 and p2 are the sameif (Math.abs(denominator) <= 1E-10) {
    markerProjection = segment.get(0);
} else {
    floatt= (carPosOnScreen.x * (p2.x - p1.x) - (p2.x - p1.x) * p1.x
            + carPosOnScreen.y * (p2.y - p1.y) - (p2.y - p1.y) * p1.y) / denominator;
    carPosOnSegment.x = (int) (p1.x + (p2.x - p1.x) * t);
    carPosOnSegment.y = (int) (p1.y + (p2.y - p1.y) * t);
    markerProjection = projection.fromScreenLocation(carPosOnSegment);
}

With full source code:

publicclassMainActivityextendsAppCompatActivityimplementsOnMapReadyCallback {

    private GoogleMap mGoogleMap;
    private MapFragment mapFragment;

    private Button mButton;

    @OverrideprotectedvoidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mapFragment = (MapFragment) getFragmentManager()
                .findFragmentById(R.id.map_fragment);
        mapFragment.getMapAsync(this);

        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(newView.OnClickListener() {
            @OverridepublicvoidonClick(View v) {

            }
        });
    }

    @OverridepublicvoidonMapReady(GoogleMap googleMap) {
        mGoogleMap = googleMap;
        mGoogleMap.setOnMapLoadedCallback(newGoogleMap.OnMapLoadedCallback() {
            @OverridepublicvoidonMapLoaded() {
                List<LatLng> sourcePoints = newArrayList<>();
                PolylineOptions polyLineOptions;
                LatLng carPos;

                sourcePoints.add(newLatLng(-35.27801,149.12958));
                sourcePoints.add(newLatLng(-35.28032,149.12907));
                sourcePoints.add(newLatLng(-35.28099,149.12929));
                sourcePoints.add(newLatLng(-35.28144,149.12984));
                sourcePoints.add(newLatLng(-35.28194,149.13003));
                sourcePoints.add(newLatLng(-35.28282,149.12956));
                sourcePoints.add(newLatLng(-35.28302,149.12881));
                sourcePoints.add(newLatLng(-35.28473,149.12836));

                polyLineOptions = newPolylineOptions();
                polyLineOptions.addAll(sourcePoints);
                polyLineOptions.width(10);
                polyLineOptions.color(Color.BLUE);
                mGoogleMap.addPolyline(polyLineOptions);

                carPos = newLatLng(-35.281120, 149.129721);
                addMarker(carPos);
                mGoogleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sourcePoints.get(0), 15));

                for (inti=0; i < sourcePoints.size() - 1; i++) {
                    LatLngsegmentP1= sourcePoints.get(i);
                    LatLngsegmentP2= sourcePoints.get(i+1);
                    List<LatLng> segment = newArrayList<>(2);
                    segment.add(segmentP1);
                    segment.add(segmentP2);

                    if (PolyUtil.isLocationOnPath(carPos, segment, true, 30)) {
                        polyLineOptions = newPolylineOptions();
                        polyLineOptions.addAll(segment);
                        polyLineOptions.width(10);
                        polyLineOptions.color(Color.RED);
                        mGoogleMap.addPolyline(polyLineOptions);
                        LatLngsnappedToSegment= getMarkerProjectionOnSegment(carPos, segment, mGoogleMap.getProjection());
                        addMarker(snappedToSegment);
                        break;
                    }
                }
            }
        });
        mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(sourcePoints.get(0), 15));
    }

    private LatLng getMarkerProjectionOnSegment(LatLng carPos, List<LatLng> segment, Projection projection) {
        LatLngmarkerProjection=null;

        PointcarPosOnScreen= projection.toScreenLocation(carPos);
        Pointp1= projection.toScreenLocation(segment.get(0));
        Pointp2= projection.toScreenLocation(segment.get(1));
        PointcarPosOnSegment=newPoint();

        floatdenominator= (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
        // p1 and p2 are the sameif (Math.abs(denominator) <= 1E-10) {
            markerProjection = segment.get(0);
        } else {
            floatt= (carPosOnScreen.x * (p2.x - p1.x) - (p2.x - p1.x) * p1.x
                    + carPosOnScreen.y * (p2.y - p1.y) - (p2.y - p1.y) * p1.y) / denominator;
            carPosOnSegment.x = (int) (p1.x + (p2.x - p1.x) * t);
            carPosOnSegment.y = (int) (p1.y + (p2.y - p1.y) * t);
            markerProjection = projection.fromScreenLocation(carPosOnSegment);
        }    
        return markerProjection;
    }

    publicvoidaddMarker(LatLng latLng) {
        mGoogleMap.addMarker(newMarkerOptions()
                .position(latLng)
        );
    }
}

you'll got something like that:

Marker snapped to path

But better way is to calculate car distance from start of the path and find it position on path via SphericalUtil.interpolate() because if several path segments is close one to another (e.g. on different lanes of same road) like that:

Wrong nearest segment

to current car position may be closest "wrong" segment. So, calculate distance of the car from the start of the route and use SphericalUtil.interpolate() for determine point exactly on path.

Solution 2:

I know this question is old, but just in case anyone needs it, adding to Andrii Omelchenko's answer, this is one way you could use SphericalUtil.interpolate() to find the point exactly on the segment:

private LatLng getMarkerProjectionOnSegment(LatLng carPos, List<LatLng> segment, Projection projection) {
        Point a = projection.toScreenLocation(segment.get(0));
        Point b = projection.toScreenLocation(segment.get(1));
        Point p = projection.toScreenLocation(carPos);

        if(a.equals(b.x, b.y)) return segment.get(0); // Projected points are the same, segment is very shortif(p.equals(a.x, a.y) || p.equals(b.x, b.y)) return carPos;

        /*
        If you're interested in the math (d represents point on segment you are trying to find):
        
        angle between 2 vectors = inverse cos of (dotproduct of 2 vectors / product of the magnitudes of each vector)
        angle = arccos(ab.ap/|ab|*|ap|)
        ad magnitude = magnitude of vector ap multiplied by cos of (angle).
        ad = ap*cos(angle) --> basic trig adj = hyp * cos(opp)
        below implementation is just a simplification of these equations
         */float dotproduct = ((b.x-a.x)*(p.x-a.x)) + ((b.y-a.y)*(p.y-a.y));
        float absquared = (float) (Math.pow(a.x-b.x, 2) + Math.pow(a.y-b.y, 2)); // Segment magnitude squared// Get the fraction for SphericalUtil.interpolatefloat fraction = dotproduct / absquared;

        if(fraction > 1) return segment.get(1);
        if(fraction < 0) return segment.get(0);
        return SphericalUtil.interpolate(segment.get(0), segment.get(1), fraction);
    }

Post a Comment for "How To Update Marker Using Live Location Along A Polyline?"