android - ¿Cómo dibujo una ruta, a lo largo de una carretera existente, entre dos puntos?
google-maps google-maps-android-api-2 (3)
Quiero mostrar la ruta de conducción entre dos ubicaciones en mi aplicación de Android. Quiero dibujar la ruta solo en la parte superior de los segmentos de la carretera.
Hay varias respuestas sobre el desbordamiento de la pila, y todas ellas estaban usando el mismo método. Obtenga las indicaciones desde el punto de inicio hasta el destino utilizando la API de indicaciones de Google y dibuje una polilínea a través de los puntos devueltos. Las siguientes son algunas de las respuestas que utilizan este método.
https://stackoverflow.com/a/17007360/1015678
https://stackoverflow.com/a/40563930/1015678
Pero, el problema con el método anterior es que, cuando las carreteras no son rectas, la memorización del amanecer no siempre está en la parte superior de las carreteras, porque las direcciones API solo devuelven puntos en los que debe girar de una carretera a otra (en los cruces). No da detalles de puntos en las curvas del mismo segmento de carretera. Entonces, cuando uso el método anterior en un área donde las carreteras tienen muchas curvas, la ruta trazada casi siempre no está en la parte superior de los segmentos de la carretera.
Encontré this respuesta, que hace lo que tengo que hacer, usando la API de JavaScript. En esta solución, la ruta dibujada sigue muy bien los caminos, de manera similar a la aplicación de Android google maps. ¿Alguien sabe si esto se puede lograr en una aplicación de Android?
La aplicación de Android Google Maps puede dibujar una ruta de un punto a otro, manteniendo la ruta en las carreteras. ¿Alguien sabe cómo Google Maps está haciendo esto? ¿Utiliza alguna otra API que no esté expuesta públicamente?
De hecho, puede dibujar una ruta precisa en la API de Android de Google Maps utilizando los resultados proporcionados por el servicio web de API de Direcciones.
Si lee la documentación de la
API de indicaciones
, verá que la respuesta contiene información sobre pasos y pasos de la ruta.
Cada paso tiene una
polyline
campo que se describe en la documentación como
La polilínea contiene un objeto de puntos únicos que contiene una representación polilínea codificada del paso. Esta polilínea es una ruta aproximada (suavizada) del paso.
Por lo tanto, la idea principal para resolver su problema es obtener una respuesta de Instrucciones API, recorrer las etapas y pasos de la ruta, para cada paso obtener una polilínea codificada y decodificarla en la lista de coordenadas. Una vez hecho esto, tendrá una lista de todas las coordenadas que componen la ruta, no solo el punto inicial y final de cada paso.
Para simplificar, recomiendo usar la biblioteca de cliente Java para los servicios web de Google Maps:
https://github.com/googlemaps/google-maps-services-java
Con esta biblioteca puede evitar implementar sus propias tareas asíncronas y la función de decodificación para polilíneas. Lea la documentación para descubrir cómo agregar la biblioteca del cliente en su proyecto.
En Gradle debería ser algo similar a
compile ''com.google.maps:google-maps-services:(insert latest version)''
compile ''org.slf4j:slf4j-nop:1.7.25''
He creado un ejemplo simple para demostrar cómo funciona. Mira mis comentarios en el código
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));
}
}
Tenga en cuenta que para el servicio web debe crear una clave API separada, la clave API con restricción de la aplicación de Android no funcionará con el servicio web.
El resultado de mi ejemplo se muestra en la captura de pantalla
También puede descargar un proyecto de muestra completo desde
https://github.com/xomena-so/so47492459
No olvides reemplazar la clave API con la tuya.
¡Espero que esto ayude!
Habilite Direction API desde Google Console.
Reemplace
API_KEY
en la clase
GetPathFromLocation.java
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);
}
Uso en actividad o fragmento
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();
Puede usar esta library , y es simple, verifique el ejemplo de uso :
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);
}
Y no olvide agregar la
key
usando el generador del ejemplo, si recibe advertencias sobre el acceso sin clave (
debe tener una cuenta de facturación
para usarla con la clave)