简体   繁体   English

如何使用沿折线的实时位置更新标记?

[英]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 route between two locations.我有一个基于地图操作的活动,在那里我沿着一条道路绘制一条折线,可以说是两个位置之间的路线。 Basically the app tracks the users current location (Traveling by car).基本上,该应用程序会跟踪用户当前的位置(开车旅行)。 So till part everything is working, as in, the route is properly shown, device Location API is giving location updates (kindof exact), and also i was able to change the location updates smoothly,所以直到部分一切正常,如路线正确显示,设备位置 API 提供位置更新(有点准确),而且我能够顺利更改位置更新,

So the issue is, the locations updates are sometimes zig zag, it might not touch the road sometimes, the location updates will be going all over the place.所以问题是,位置更新有时是曲折的,有时可能不会触及道路,位置更新会到处都是。

I have looked into ROAD api also, but am not getting the correct help, even from some previously asked questions.我也研究过 ROAD api,但没有得到正确的帮助,即使是从之前提出的一些问题中也是如此。

Will it be possible to make the marker move only along the road?是否可以使标记仅沿道路移动?

Any kind of help will be appreciated.任何形式的帮助将不胜感激。

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()找到最近的段:

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 ):和标记到该段的投影,您可以通过将测地线球面坐标转换为正交屏幕坐标计算投影正交坐标并将其转换回球面( WGS84 LatLng -> Screen x,y -> WGS84 LatLng )来找到:

Point carPosOnScreen = projection.toScreenLocation(carPos);
Point p1 = projection.toScreenLocation(segment.get(0));
Point p2 = projection.toScreenLocation(segment.get(1));
Point carPosOnSegment = new Point();

float denominator = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
// p1 and p2 are the same
if (Math.abs(denominator) <= 1E-10) {
    markerProjection = segment.get(0);
} else {
    float t = (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:带有完整的源代码:

public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {

    private GoogleMap mGoogleMap;
    private MapFragment mapFragment;

    private Button mButton;

    @Override
    protected void onCreate(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(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mGoogleMap = googleMap;
        mGoogleMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
            @Override
            public void onMapLoaded() {
                List<LatLng> sourcePoints = new ArrayList<>();
                PolylineOptions polyLineOptions;
                LatLng carPos;

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

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

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

                for (int i = 0; i < sourcePoints.size() - 1; i++) {
                    LatLng segmentP1 = sourcePoints.get(i);
                    LatLng segmentP2 = sourcePoints.get(i+1);
                    List<LatLng> segment = new ArrayList<>(2);
                    segment.add(segmentP1);
                    segment.add(segmentP2);

                    if (PolyUtil.isLocationOnPath(carPos, segment, true, 30)) {
                        polyLineOptions = new PolylineOptions();
                        polyLineOptions.addAll(segment);
                        polyLineOptions.width(10);
                        polyLineOptions.color(Color.RED);
                        mGoogleMap.addPolyline(polyLineOptions);
                        LatLng snappedToSegment = 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) {
        LatLng markerProjection = null;

        Point carPosOnScreen = projection.toScreenLocation(carPos);
        Point p1 = projection.toScreenLocation(segment.get(0));
        Point p2 = projection.toScreenLocation(segment.get(1));
        Point carPosOnSegment = new Point();

        float denominator = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
        // p1 and p2 are the same
        if (Math.abs(denominator) <= 1E-10) {
            markerProjection = segment.get(0);
        } else {
            float t = (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;
    }

    public void addMarker(LatLng latLng) {
        mGoogleMap.addMarker(new MarkerOptions()
                .position(latLng)
        );
    }
}

you'll got something like that:你会得到这样的东西:

标记捕捉到路径

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 (eg on different lanes of same road) like that:但更好的方法是从路径起点计算汽车距离并通过SphericalUtil.interpolate()找到它在路径上的位置,因为如果多个路径段彼此靠近(例如在同一条道路的不同车道上),如下所示:

错误的最近段

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.因此,计算汽车与路线起点的距离,并使用SphericalUtil.interpolate()确定路径上的确切点。

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:我知道这个问题很旧,但以防万一有人需要它,添加到 Andrii Omelchenko 的答案中,这是您可以使用SphericalUtil.interpolate()精确找到该段上的点的一种方法:

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 short
        if(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.interpolate
        float 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);
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM