如何在现有道路上绘制两点之间的路线?

31

我想在我的Android应用程序中展示两个地点之间的驾车路线,只希望在道路部分上面绘制路线。

在stackoverflow网站上有几个答案都使用了同样的方法。使用Google方向API获取起点到终点的方向,并通过返回的点绘制折线。以下是一些使用此方法的答案。

https://dev59.com/M2Yq5IYBdhLWcg3wfgrd#17007360

https://stackoverflow.com/a/40563930/1015678

但是,以上方法存在问题,当道路不直时,绘制出的路线通常不在道路上,因为方向API仅返回您需要从一条路转换到另一条路(在路口处)的点。它不会提供同一道路段曲线处的详细点信息。因此,当我在道路有很多弯道的区域使用以上方法时,绘制的路线几乎总是不在道路部分上面。

我找到了这个答案,它使用JavaScript API做到了我所需的功能。在此解决方案中,绘制的路线沿着道路很好地跟随,类似于Google Maps Android应用程序。有人知道是否可以在Android应用程序中实现这一点吗?

Google Maps Android应用程序可以很好地从一个点绘制路线到另一个点,并保持路线在道路上。有人知道Google Maps是如何做到这一点的吗?它使用了任何其他未公开的API吗?


看一下这个答案。然后将其与Directions API结合使用。 - Andrii Omelchenko
@AndriiOmelchenko 我尝试过这个方法,但是由Directions API返回的步骤点之间的距离可能相当大,而且只有在给定的点彼此接近时才能进行道路捕捉。 - Lahiru Chandima
尝试请求方向,不是针对所有路径,而是逐步使用几个最短距离。 - Andrii Omelchenko
@AndriiOmelchenko,如果我请求整个路线中单个步骤的起点和终点之间的方向,谷歌不会将该步骤分成更小的步骤。因此,我无法通过这种方式增加返回的整个路线中的点数。 - Lahiru Chandima
或/和在方向 API 中使用 途经点 - Andrii Omelchenko
显示剩余5条评论
5个回答

72

实际上,您可以使用Directions API Web服务提供的结果在Google Maps Android API中绘制精确路线。如果您阅读Directions API的文档,您会发现响应包含有关路线段和步骤的信息。每个步骤都有一个字段polyline,在文档中描述为

polyline包含一个单一的points对象,该对象保存步骤的编码折线表示形式。此折线是步骤的近似(平滑)路径。

因此,解决您的问题的主要思路是获取来自Directions API的响应,遍历路线段和步骤,对于每个步骤获取编码的折线并将其解码为坐标列表。完成后,您将拥有组成路线的所有坐标列表,而不仅仅是每个步骤的起点和终点。

为简单起见,我建议使用Google Maps Web服务的Java客户端库:

https://github.com/googlemaps/google-maps-services-java

使用此库,您可以避免实现自己的异步任务和折线解码功能。阅读文档以了解如何在项目中添加客户端库。

在Gradle中,应该类似于:

compile 'com.google.maps:google-maps-services:(insert latest version)'
compile 'org.slf4j:slf4j-nop:1.7.25'

我创建了一个简单的示例,以演示它如何工作。请查看我在代码中的评论。

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {

    private GoogleMap mMap;
    private String TAG = "so47492459";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        LatLng barcelona = new LatLng(41.385064,2.173403);
        mMap.addMarker(new MarkerOptions().position(barcelona).title("Marker in Barcelona"));

        LatLng madrid = new LatLng(40.416775,-3.70379);
        mMap.addMarker(new MarkerOptions().position(madrid).title("Marker in Madrid"));

        LatLng zaragoza = new LatLng(41.648823,-0.889085);

        //Define list to get all latlng for the route
        List<LatLng> path = new ArrayList();


        //Execute Directions API request
        GeoApiContext context = new GeoApiContext.Builder()
                .apiKey("YOUR_API_KEY")
                .build();
        DirectionsApiRequest req = DirectionsApi.getDirections(context, "41.385064,2.173403", "40.416775,-3.70379");
        try {
            DirectionsResult res = req.await();

            //Loop through legs and steps to get encoded polylines of each step
            if (res.routes != null && res.routes.length > 0) {
                DirectionsRoute route = res.routes[0];

                if (route.legs !=null) {
                    for(int i=0; i<route.legs.length; i++) {
                        DirectionsLeg leg = route.legs[i];
                        if (leg.steps != null) {
                            for (int j=0; j<leg.steps.length;j++){
                                DirectionsStep step = leg.steps[j];
                                if (step.steps != null && step.steps.length >0) {
                                    for (int k=0; k<step.steps.length;k++){
                                        DirectionsStep step1 = step.steps[k];
                                        EncodedPolyline points1 = step1.polyline;
                                        if (points1 != null) {
                                            //Decode polyline and add points to list of route coordinates
                                            List<com.google.maps.model.LatLng> coords1 = points1.decodePath();
                                            for (com.google.maps.model.LatLng coord1 : coords1) {
                                                path.add(new LatLng(coord1.lat, coord1.lng));
                                            }
                                        }
                                    }
                                } else {
                                    EncodedPolyline points = step.polyline;
                                    if (points != null) {
                                        //Decode polyline and add points to list of route coordinates
                                        List<com.google.maps.model.LatLng> coords = points.decodePath();
                                        for (com.google.maps.model.LatLng coord : coords) {
                                            path.add(new LatLng(coord.lat, coord.lng));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch(Exception ex) {
            Log.e(TAG, ex.getLocalizedMessage());
        }

        //Draw the polyline
        if (path.size() > 0) {
            PolylineOptions opts = new PolylineOptions().addAll(path).color(Color.BLUE).width(5);
            mMap.addPolyline(opts);
        }

        mMap.getUiSettings().setZoomControlsEnabled(true);

        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(zaragoza, 6));
    }
}
请注意,对于 Web 服务,您需要创建一个单独的 API 密钥,带有 Android 应用程序限制的 API 密钥无法与 Web 服务一起使用。
我的示例结果显示在截图中 enter image description here 您还可以从以下链接下载完整的示例项目: https://github.com/xomena-so/so47492459 不要忘记将 API 密钥替换为您自己的密钥。
希望这可以帮助您!

1
我复制/粘贴了这段代码,但在最新版本的google.maps.services(0.9.0)上崩溃了,出现以下异常:NoClassDefFoundError:无法解析的内容为:Ljava/time/LocalDateTime。我切换到google.maps.services:0.2.11,现在没问题了。 - Nabzi
1
根据文档 java.time.LocalDateTime,它是在Java 8中引入的。我猜测版本0.9.0需要Java 8或更高版本。 - xomena
@Duncan Luk 正确。最佳解决方案是创建一个中间服务器,在该服务器上使用Java客户端库执行Directions请求,并将结果传递回Android应用程序。 - xomena
@xomena,如果一个标记是移动的,我该如何更新它的位置?就像优步一样? - Manoj Perumarath
感谢您详细的回答。 我遇到了一个异常,捕获时出现了“无法找到本地变量'coord'”的错误,请问如何解决? - Ouneeb Ur Rehman
显示剩余7条评论

6

请在Google控制台启用方向API。 在GetPathFromLocation.java类中替换API_KEY

import android.graphics.Color;
import android.os.AsyncTask;
import android.util.Log;

import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.PolylineOptions;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class GetPathFromLocation extends AsyncTask<String, Void, PolylineOptions> {

    private String TAG = "GetPathFromLocation";
    private String API_KEY = "Place_Your_API_Key";
    private LatLng source, destination;
    private DirectionPointListener resultCallback;

    public GetPathFromLocation(LatLng source, LatLng destination, DirectionPointListener resultCallback) {
        this.source = source;
        this.destination = destination;
        this.resultCallback = resultCallback;
    }

    public String getUrl(LatLng origin, LatLng dest) {

        String str_origin = "origin=" + origin.latitude + "," + origin.longitude;
        String str_dest = "destination=" + dest.latitude + "," + dest.longitude;
        String sensor = "sensor=false";
        String parameters = str_origin + "&" + str_dest + "&" + sensor;
        String output = "json";
        String url = "https://maps.googleapis.com/maps/api/directions/" + output + "?" + parameters + "&key=" + API_KEY;

        return url;
    }

    @Override
    protected PolylineOptions doInBackground(String... url) {

        String data;

        try {
            InputStream inputStream = null;
            HttpURLConnection connection = null;
            try {
                URL directionUrl = new URL(getUrl(source, destination));
                connection = (HttpURLConnection) directionUrl.openConnection();
                connection.connect();
                inputStream = connection.getInputStream();

                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                StringBuffer stringBuffer = new StringBuffer();

                String line = "";
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuffer.append(line);
                }

                data = stringBuffer.toString();
                bufferedReader.close();

            } catch (Exception e) {
                Log.e(TAG, "Exception : " + e.toString());
                return null;
            } finally {
                inputStream.close();
                connection.disconnect();
            }
            Log.e(TAG, "Background Task data : " + data);


            JSONObject jsonObject;
            List<List<HashMap<String, String>>> routes = null;

            try {
                jsonObject = new JSONObject(data);
                // Starts parsing data
                DirectionHelper helper = new DirectionHelper();
                routes = helper.parse(jsonObject);
                Log.e(TAG, "Executing Routes : "/*, routes.toString()*/);


                ArrayList<LatLng> points;
                PolylineOptions lineOptions = null;

                // Traversing through all the routes
                for (int i = 0; i < routes.size(); i++) {
                    points = new ArrayList<>();
                    lineOptions = new PolylineOptions();

                    // Fetching i-th route
                    List<HashMap<String, String>> path = routes.get(i);

                    // Fetching all the points in i-th route
                    for (int j = 0; j < path.size(); j++) {
                        HashMap<String, String> point = path.get(j);

                        double lat = Double.parseDouble(point.get("lat"));
                        double lng = Double.parseDouble(point.get("lng"));
                        LatLng position = new LatLng(lat, lng);

                        points.add(position);
                    }

                    // Adding all the points in the route to LineOptions
                    lineOptions.addAll(points);
                    lineOptions.width(10);
                    lineOptions.color(Color.BLUE);

                    Log.e(TAG, "PolylineOptions Decoded");
                }

                // Drawing polyline in the Google Map for the i-th route
                if (lineOptions != null) {
                    return lineOptions;
                } else {
                    return null;
                }

            } catch (Exception e) {
                Log.e(TAG, "Exception in Executing Routes : " + e.toString());
                return null;
            }

        } catch (Exception e) {
            Log.e(TAG, "Background Task Exception : " + e.toString());
            return null;
        }
    }

    @Override
    protected void onPostExecute(PolylineOptions polylineOptions) {
        super.onPostExecute(polylineOptions);
        if (resultCallback != null && polylineOptions != null)
            resultCallback.onPath(polylineOptions);
    }
}

DirectionHelper.java

import com.google.android.gms.maps.model.LatLng;

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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class DirectionHelper {

    public List<List<HashMap<String, String>>> parse(JSONObject jObject) {

        List<List<HashMap<String, String>>> routes = new ArrayList<>();
        JSONArray jRoutes;
        JSONArray jLegs;
        JSONArray jSteps;

        try {

            jRoutes = jObject.getJSONArray("routes");

            /** Traversing all routes */
            for (int i = 0; i < jRoutes.length(); i++) {
                jLegs = ((JSONObject) jRoutes.get(i)).getJSONArray("legs");
                List path = new ArrayList<>();

                /** Traversing all legs */
                for (int j = 0; j < jLegs.length(); j++) {
                    jSteps = ((JSONObject) jLegs.get(j)).getJSONArray("steps");

                    /** Traversing all steps */
                    for (int k = 0; k < jSteps.length(); k++) {
                        String polyline = "";
                        polyline = (String) ((JSONObject) ((JSONObject) jSteps.get(k)).get("polyline")).get("points");
                        List<LatLng> list = decodePoly(polyline);

                        /** Traversing all points */
                        for (int l = 0; l < list.size(); l++) {
                            HashMap<String, String> hm = new HashMap<>();
                            hm.put("lat", Double.toString((list.get(l)).latitude));
                            hm.put("lng", Double.toString((list.get(l)).longitude));
                            path.add(hm);
                        }
                    }
                    routes.add(path);
                }
            }

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


        return routes;
    }

    //Method to decode polyline points
    private List<LatLng> decodePoly(String encoded) {

        List<LatLng> poly = new ArrayList<>();
        int index = 0, len = encoded.length();
        int lat = 0, lng = 0;

        while (index < len) {
            int b, shift = 0, result = 0;
            do {
                b = encoded.charAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
            lat += dlat;

            shift = 0;
            result = 0;
            do {
                b = encoded.charAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
            lng += dlng;

            LatLng p = new LatLng((((double) lat / 1E5)),
                    (((double) lng / 1E5)));
            poly.add(p);
        }

        return poly;
    }
}

DirectionPointListener.java

import com.google.android.gms.maps.model.PolylineOptions;

public interface DirectionPointListener {
    public void onPath(PolylineOptions polyLine);
}

在Activity或Fragment中使用
LatLng source = new LatLng(xx.xxxx, yy.yyyy);
LatLng destination = new LatLng(xx.xxxx, yy.yyyy);

new GetPathFromLocation(source, destination, new DirectionPointListener() {
            @Override
            public void onPath(PolylineOptions polyLine) {
                yourMap.addPolyline(polyLine);
            }
        }).execute();

你好,我在使用安卓API密钥时没有得到任何结果。我已将所有限制设置为无。但是谷歌服务器仍然向我发送空数组。 我收到了以下响应: E/GetPathFromLocation: 后台任务数据:{"geocoded_waypoints" : [ {}, {} ], "routes" : [], "status" : "ZERO_RESULTS"} - Yunus
注意:使用via:前缀来避免中途停留会导致路线方向非常严格地解释该途经点。这可能导致路线上的严重绕路或者在响应状态码中产生ZERO_RESULTS,如果Directions API无法创建通过该点的路线。(https://developers.google.com/maps/documentation/directions/intro) 如果您已经在URL中使用了via:前缀,请将其删除。 - Ketan Ramani
我正在一项技术挑战的工作中,这篇文章解决了我的问题 :) 谢谢。 - Asad Ali Choudhry
@AsadAliChoudhry 欢迎。 - Ketan Ramani
检查高级版 https://dev59.com/d6Dia4cB1Zd3GeqPIbxV#59622783 - Ketan Ramani

3

对于我来说,我使用OSM获取作为GeoJSON的方向指引,然后使用Google Maps工具在Google地图上绘制路线。
首先

// build.gradle
dependencies {
implementation 'com.google.maps.android:android-maps-utils:0.5'
}

// in maps Activity
// mMap is Google map
// geoJsonData is the returend json object from directions api
// which in my case is OSM
GeoJsonLayer layer = new GeoJsonLayer(mMap, geoJsonData);

// now you can add the layer to the map
layer.addLayerToMap();
// congrats you draw the road between two points now :)

欲了解更多信息,请查看Google Maps Android GeoJson Utility
祝你编码愉快!


2
您可以使用这个,它很简单,查看使用示例
Routing routing = new Routing.Builder()
    .travelMode(AbstractRouting.TravelMode.DRIVING)
    .withListener(this)
    .alternativeRoutes(true)
    .waypoints(start, end)
    .build();
routing.execute();


@Override
public void onRoutingSuccess(List<Route> route, int shortestRouteIndex) {
    progressDialog.dismiss();
    CameraUpdate center = CameraUpdateFactory.newLatLng(start);
    CameraUpdate zoom = CameraUpdateFactory.zoomTo(16);

    map.moveCamera(center);

    if(polylines.size()>0) {
        for (Polyline poly : polylines) {
            poly.remove();
        }
    }

    polylines = new ArrayList<>();
    // Add route(s) to the map.
    for (int i = 0; i <route.size(); i++) {

        //In case of more than 5 alternative routes
        int colorIndex = i % COLORS.length;

        PolylineOptions polyOptions = new PolylineOptions();
        polyOptions.color(getResources().getColor(COLORS[colorIndex]));
        polyOptions.width(10 + i * 3);
        polyOptions.addAll(route.get(i).getPoints());
        Polyline polyline = map.addPolyline(polyOptions);
        polylines.add(polyline);

        Toast.makeText(getApplicationContext(),"Route "+ (i+1) +": distance - "+ route.get(i).getDistanceValue()+": duration - "+ route.get(i).getDurationValue(),Toast.LENGTH_SHORT).show();
    }

    // Start marker
    MarkerOptions options = new MarkerOptions();
    options.position(start);
    options.icon(BitmapDescriptorFactory.fromResource(R.drawable.start_blue));
    map.addMarker(options);

    // End marker
    options = new MarkerOptions();
    options.position(end);
    options.icon(BitmapDescriptorFactory.fromResource(R.drawable.end_green));
    map.addMarker(options);

}

不要忘记使用构建器从示例中添加key,如果您收到有关无密钥访问的警告(您应该拥有计费帐户才能使用它)


2
这个库已经被弃用/不再维护。 - Sandeep Rajoria

1

Kotlin方式

implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'com.google.maps.android:android-maps-utils:2.4.0'
implementation 'com.google.maps:google-maps-services:2.1.0'
implementation 'org.slf4j:slf4j-nop:2.0.0'

实现完成时:

private var mMap: GoogleMap? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val mapFragment = supportFragmentManager.findFragmentById(R.id.frg_map) as SupportMapFragment?
    mapFragment?.let { it ->
        it.getMapAsync { googleMap ->
            mMap = googleMap

            val firstLocation = LatLng( 40.984443, 28.7324437)
            val secondLocation = LatLng(40.9822821, 28.7210424)

            mMap?.addMarker(MarkerOptions().position(firstLocation)
                .icon(bitmapFromVector(this, R.drawable.ic_marker_first)))

            mMap?.addMarker(MarkerOptions().position(secondLocation)
                .icon(bitmapFromVector(this, R.drawable.ic_marker_second)))


            val paths: MutableList<LatLng> = ArrayList()

            val geoApiContext = GeoApiContext.Builder()
                .apiKey(getString(R.string.google_maps_api_key))
                .build()

            val req = DirectionsApi.getDirections(geoApiContext,
                "${secondLocation.latitude},${secondLocation.longitude}",
                "${firstLocation.latitude},${firstLocation.longitude}")
            try {
                val res = req.await()
                if (res.routes.isNullOrEmpty().not()) {
                    val route = res.routes[0]
                    if (route.legs.isNullOrEmpty().not()) {
                        for (leg in route.legs) {
                            if (leg.steps.isNullOrEmpty().not()) {
                                for (step in leg.steps) {
                                    if (step.steps.isNullOrEmpty().not()) {
                                        for (step1 in step.steps) {
                                            step1.polyline?.let { points1 ->
                                                val coordinates = points1.decodePath()
                                                for (coordinate in coordinates) {
                                                    paths.add(LatLng(coordinate.lat, coordinate.lng))
                                                }
                                            }

                                        }
                                    } else {
                                        step.polyline?.let { points ->
                                            val coordinates = points.decodePath()
                                            for (coordinate in coordinates) {
                                                paths.add(LatLng(coordinate.lat, coordinate.lng))
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (ex: Exception) {
                Log.e("DirectionsApi", "DirectionsApi exception localizedMessage: ${ex.localizedMessage}")
            }

            if (paths.isNotEmpty()) {
                val opts = PolylineOptions().addAll(paths).color(Color.BLUE).width(5f)
                mMap?.addPolyline(opts)
            }

            mMap?.uiSettings?.isZoomControlsEnabled = true

        }
    }
    
    
    
}


private fun bitmapFromVector(context: Context, vectorResId: Int): BitmapDescriptor {
    val vectorDrawable = ContextCompat.getDrawable(context, vectorResId)
    vectorDrawable!!.setBounds(0, 0, vectorDrawable.intrinsicWidth, vectorDrawable.intrinsicHeight)
    val bitmap = Bitmap.createBitmap(vectorDrawable.intrinsicWidth, vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    vectorDrawable.draw(canvas)
    return BitmapDescriptorFactory.fromBitmap(bitmap)
}

你好,是否可以在“getDirections”函数中设置步行或驾车模式? - sheko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接