I'm working with mapbox android, i'm trying to add multiple waypoints between origin and destination.but after adding one waypoint when it's adding another one this gives the exception " Too many coordinate the s; maximum number of coordinates is 3."
I just want to add multiple waypoint between two point and draw route over those line in mapbox android.
[pastbin link] : https://paste.ubuntu.com/p/PKMQzFyzVb/
My Route Draw Function -->
{
private void getRouteWithWaypoint(Point origin, Point destination, List<Point> wayPoints) {
assert Mapbox.getAccessToken() != null;
NavigationRoute.Builder builder = NavigationRoute.builder(getActivity())
.accessToken(Mapbox.getAccessToken())
.origin(origin)
.destination(destination);
if (wayPoints != null) {
for (Point point : wayPoints) {
builder.addWaypoint(point);
}
}
builder.build().getRoute(new Callback<DirectionsResponse>() {
#Override
public void onResponse(#NonNull Call<DirectionsResponse> call, Response<DirectionsResponse> response) {
Log.e(TAG, "Response code: " + response.code());
if (response.body() == null) {
Log.e(TAG, "No routes found, make sure you set the right user and access token.");
return;
} else if (response.body().routes().size() < 1) {
Log.e(TAG, "No routes found");
return;
}
currentRoute = response.body().routes().get(0);
if (navigationMapRoute != null) {
navigationMapRoute.removeRoute();
} else {
navigationMapRoute = new NavigationMapRoute(null, mapView, map, R.style.NavigationMapRoute);
}
navigationMapRoute.addRoute(currentRoute);
}
#SuppressLint("TimberArgCount")
#Override
public void onFailure(Call<DirectionsResponse> call, Throwable t) {
Timber.e(t, "Error: %s");
}
});
}}
Draw root on Mapbox map, copied the below code from Mapbox documentation.
private void getRoute(Point origin, Point destination) {
NavigationRoute.builder(this)
.accessToken(Mapbox.getAccessToken())
.origin(origin)
.destination(destination)
.build()
.getRoute(new Callback<DirectionsResponse>() {
#Override
public void onResponse(Call<DirectionsResponse> call, Response<DirectionsResponse> response) {
// You can get the generic HTTP info about the response
Log.d(TAG, "Response code: " + response.code());
if (response.body() == null) {
Log.e(TAG, "No routes found, make sure you set the right user and access token.");
return;
} else if (response.body().routes().size() < 1) {
Log.e(TAG, "No routes found");
return;
}
currentRoute = response.body().routes().get(0);
// Draw the route on the map
if (navigationMapRoute != null) {
navigationMapRoute.removeRoute();
} else {
navigationMapRoute = new NavigationMapRoute(null, mapView, mapboxMap, R.style.NavigationMapRoute);
}
navigationMapRoute.addRoute(currentRoute);
}
#Override
public void onFailure(Call<DirectionsResponse> call, Throwable throwable) {
Log.e(TAG, "Error: " + throwable.getMessage());
}
});
}
for more detail follow the link
https://www.mapbox.com/help/android-navigation-sdk/#calculate-and-draw-route
The default profile for requesting routes is DirectionsCriteria.ProfileCriteria.PROFILE_DRIVING_TRAFFIC
This profile allows only 1 waypoint between the origin and the destination. If you want to use more than 1 waypoint, just use PROFILE_DRIVING instead (this allows up to 25 waypoints I think).
Like this:
NavigationRoute.Builder builder = NavigationRoute.builder(getActivity())
.accessToken(Mapbox.getAccessToken())
.origin(origin)
.destination(destination)
.profile(DirectionsCriteria.ProfileCriteria.PROFILE_DRIVING);
if (wayPoints != null) {
for (Point point : wayPoints) {
builder.addWaypoint(point);
}
}
Related
in my Android inside an activity I have an asynchronous call to a google maps API inside a method see the code below :
public void reverseGeocode(String lat, String lng) {
String geocodeApiUrl = getUrl(lat, lng);
GoogleService googleService = GoogleServiceBuilder.buildService(GoogleService.class);
Call<Address> geocodeRequest = googleService.geocodeAddress(geocodeApiUrl);
geocodeRequest.enqueue(new Callback<Address>() {
#Override
public void onResponse(Call<Address> call, Response<Address> response) {
if (response.errorBody() == null) {
if (response.body().getResults().length > 0) {
if (response.body().getResults()[0].getFormatted_address() != null) {
dropOffAddress = response.body().getResults()[0].getFormatted_address();
}
}
} else {
Toast.makeText(AdressPicker.this, "An error has occured", Toast.LENGTH_LONG).show();
}
}
#Override
public void onFailure(Call<Address> call, Throwable t) {
Toast.makeText(AdressPicker.this, "An error has occured", Toast.LENGTH_LONG).show();
}
});
}
this method is called inside the Google map marker 's onMarkerDragEnd callback function , see code below :
#Override
public void onMarkerDragEnd(Marker marker) {
double lat = marker.getPosition().latitude;
double lng = marker.getPosition().longitude;
reverseGeocode(String.valueOf(lat), String.valueOf(lng));
marker.setSnippet(dropOffAddress);
marker.showInfoWindow();
editor.putString("dropOffAddress", dropOffAddress);
editor.putString("lat", String.valueOf(lat));
editor.putString("lng", String.valueOf(lng));
editor.apply();
}
The problem here is the first time when I move the marker the reverseGeocode method is called but it is not awaited and the onMarkerDragEnd continue its execution and puts null inside the value of the dropOffAddress variable which is intended to be displayed inside the snippet of the marker.
My question here is HOW to WAIT for the reverseGeocode method'is response inside onMarkerDragEnd before continuing executing ?
Thank you.
the only thing you. need is marker, just pass it as a parameter in your retrofit api call something like this.
public void reverseGeocode(String lat, String lng, Marker marker) {
String geocodeApiUrl = getUrl(lat, lng);
GoogleService googleService = GoogleServiceBuilder.buildService(GoogleService.class);
Call<Address> geocodeRequest = googleService.geocodeAddress(geocodeApiUrl);
geocodeRequest.enqueue(new Callback<Address>() {
#Override
public void onResponse(Call<Address> call, Response<Address> response) {
if (response.errorBody() == null) {
if (response.body().getResults().length > 0) {
if (response.body().getResults()[0].getFormatted_address() != null) {
dropOffAddress = response.body().getResults()[0].getFormatted_address();
applyGeoCode(marker)
}
}
} else {
Toast.makeText(AdressPicker.this, "An error has occured", Toast.LENGTH_LONG).show();
}
}
#Override
public void onFailure(Call<Address> call, Throwable t) {
Toast.makeText(AdressPicker.this, "An error has occured", Toast.LENGTH_LONG).show();
}
});
}
now from here call a method passing the marker
public void applyGeoCode(Marker marker){
double lat = marker.getPosition().latitude;
double lng = marker.getPosition().longitude;
marker.setSnippet(dropOffAddress);
marker.showInfoWindow();
editor.putString("dropOffAddress", dropOffAddress);
editor.putString("lat", String.valueOf(lat));
editor.putString("lng", String.valueOf(lng));
editor.apply();
}
and call like this
#Override
public void onMarkerDragEnd(Marker marker) {
reverseGeocode(String.valueOf(lat), String.valueOf(lng), marker);
}
You can use CountDownLatch from the java.utils.Concurrent class.
Here is an example:
public class DoSomething {
private Address dropoffAddr;
private CountDownLatch dropoffAddrLatch = new CountDownLatch(1);
public void getAddressCallback(Address addr) {
dropoffAddr = addr;
dropoffAddrLatch.countDown();
}
#Override
public void onMarkerDragEnd(Marker marker) {
//your stuff
reverseGeocode(...); //you should call getAddressCallback once you have everything
dropOffAddrLatch.await(); //this will wait until getAddressCallBack has returned,
// you can call dropoffAddrLatch.countDown() wherever you want.
//finish your stuff
}
}
After updating version of forms to 2.4.0.282, I started getting this weird behavior in MapView. I have created a custom renderer for map in android where I am setting marker images as per my requirement. Custom markers in fact appear, but on top of it, it's default icon still gets overlaid.
Note that I am using Xamarin.Maps version 2.4.0.282, tried to downgrade to prior versions as well but I am of no help.
I even tried it out by commenting line,
Forms.SetFlags("FastRenderers_Experimental");
but even this didn't help.
Below is the renderer I created,
public class CustomMapRenderer : MapRenderer, IOnMapReadyCallback
{
GoogleMap map;
public static double PreviousDistance = 0;
List<CustomPin> customPins;
CustomMap formsMap = null;
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
map.InfoWindowClick -= OnInfoWindowClick;
map.MarkerClick -= OnMarkerClick;
}
if (e.NewElement != null)
{
formsMap = (CustomMap)e.NewElement;
customPins = formsMap.CustomPins;
((MapView)Control).GetMapAsync(this);
}
}
protected override void OnMapReady(GoogleMap googleMap)
{
map = googleMap;
map.InfoWindowClick += OnInfoWindowClick;
map.MarkerClick += OnMarkerClick;
map.UiSettings.ZoomControlsEnabled = false;
formsMap.MoveToRegion(MapSpan.FromCenterAndRadius(formsMap.Location, Distance.FromMiles(1.0)));
if(customPins != null && customPins.Count > 0)
{
setMapPins("CustomPins");
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == "CustomPins" || (e.PropertyName.Equals("VisibleRegion")))
{
setMapPins(e.PropertyName);
}
}
private void setMapPins(string PropertyName)
{
customPins = formsMap.CustomPins;
map.Clear();
if (customPins != null && customPins.Count > 0)
{
if (PropertyName == "CustomPins")
{
//Set map zoom
var defaultZoom = 14;
try
{
PreviousDistance = DistanceCalculation.MoveToRegionData.MoveToRegion(formsMap, customPins, defaultZoom, PreviousDistance);
}
catch (Exception ex)
{
PreviousDistance = 0;
Console.WriteLine(ex.Message);
}
}
foreach (var pin in customPins)
{
var pinImage = Resources.GetIdentifier(pin.PinImage.ToLower(), "drawable", Context.PackageName);
var markerImg = BitmapDescriptorFactory.FromResource(pinImage);
map.AddMarker(new MarkerOptions().SetTitle(pin.Pin.Label).SetSnippet(pin.Id).SetPosition(new LatLng(pin.Pin.Position.Latitude, pin.Pin.Position.Longitude)).SetIcon(markerImg));
}
}
else
{
Console.WriteLine("In else!!");
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if (changed)
{
}
}
void OnInfoWindowClick(object sender, GoogleMap.InfoWindowClickEventArgs e)
{
var customPin = GetCustomPin(e.Marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
if (!string.IsNullOrWhiteSpace(customPin.Url))
{
var url = global::Android.Net.Uri.Parse(customPin.Url);
var intent = new Intent(Intent.ActionView, url);
intent.AddFlags(ActivityFlags.NewTask);
global::Android.App.Application.Context.StartActivity(intent);
}
}
CustomPin GetCustomPin(Marker annotation)
{
var position = new Position(annotation.Position.Latitude, annotation.Position.Longitude);
foreach (var pin in customPins)
{
if (pin.Pin.Position == position)
{
return pin;
}
}
return null;
}
void OnMarkerClick(object sender, GoogleMap.MarkerClickEventArgs ea)
{
var marker = (Marker) ea.Marker;
formsMap.IsPinClicked = false;
var customPin = GetCustomPin(marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
formsMap.SelectedPinId = Convert.ToInt32(marker.Snippet);
formsMap.IsPinClicked = true;
}
}
This is what it looks like right now..
I have created a custom renderer for map in android where I am setting marker images as per my requirement. Custom markers in fact appear, but on top of it, it's default icon still gets overlaid.
I've made a demo and reproduced the problem, after some tests I found that the overriding of OnMapReady function causes the problem. Even with totally empty OnMapReady the problem will occurs. My guess is a call to the OnMapReady in Custom Map Render might lead to a rerender of the Map including pins.
Solution:
Comment OnMapReady out.
Move the logic in OnMapReady to OnElementChanged.
Let your local variable map=NativeMap and make sure setMapPins get called in OnElementPropertyChanged.
Add NativeMap.Clear(); in OnElementPropertyChanged and try again. Hope it will help!!!
Have a look below code for customizing pin on Android Platform.
[assembly:ExportRenderer (typeof(CustomMap), typeof(CustomMapRenderer))]
namespace CustomRenderer.Droid
{
public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter
{
List<CustomPin> customPins;
bool isDrawn;
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
NativeMap.InfoWindowClick -= OnInfoWindowClick;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
customPins = formsMap.CustomPins;
((MapView)Control).GetMapAsync(this);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName.Equals("VisibleRegion") && !isDrawn)
{
NativeMap.Clear();
NativeMap.InfoWindowClick += OnInfoWindowClick;
NativeMap.SetInfoWindowAdapter(this);
foreach (var pin in customPins)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Pin.Position.Latitude, pin.Pin.Position.Longitude));
marker.SetTitle(pin.Pin.Label);
marker.SetSnippet(pin.Pin.Address);
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));
NativeMap.AddMarker(marker);
}
isDrawn = true;
}
}
}
}
For more detail Click here
I want to draw a complete route on Mapbox from origin to destination using .geojson file.
Please find below code.
private void drawMapMatched(Position[] coordinates) {
try {
MapboxMapMatching client = new MapboxMapMatching.Builder()
.setAccessToken(Mapbox.getAccessToken())
.setSteps(true)
.setProfile(MapMatchingCriteria.PROFILE_DRIVING)
.setCoordinates(coordinates)
.build();
client.enqueueCall(new Callback<MapMatchingResponse>() {
#Override
public void onResponse(Call<MapMatchingResponse> call, Response<MapMatchingResponse> response) {
List<LatLng> mapMatchedPoints = new ArrayList<>();
if (response.code() == 200) {
String geometry = response.body().getMatchings().get(0).getGeometry();
List<Position> positions = PolylineUtils.decode(geometry, Constants.PRECISION_6);
if (positions == null) {
return;
}
for (int i = 0; i < positions.size(); i++) {
mapMatchedPoints.add(new LatLng(
positions.get(i).getLatitude(),
positions.get(i).getLongitude()));
}
if (mapMatchedRoute != null) {
mapboxMap.removeAnnotation(mapMatchedRoute);
}
mapMatchedRoute = mapboxMap.addPolyline(new PolylineOptions()
.color(Color.GREEN)
.alpha(0.65f)
.width(4));
for (int i = 0; i < mapMatchedPoints.size(); i++) {
mapMatchedRoute.addPoint(mapMatchedPoints.get(i));
}
Position origin = Position.fromCoordinates(mapMatchedPoints.get(0).getLongitude(), mapMatchedPoints.get(0).getLatitude());
Position destination = Position.fromCoordinates(mapMatchedPoints.get(mapMatchedPoints.size() - 1).getLongitude(), mapMatchedPoints.get(mapMatchedPoints.size() - 1).getLatitude());
getRoute(origin, destination);
} else {
Log.e(TAG, "Too many coordinates, profile not found, invalid input, or no match.");
}
}
#Override
public void onFailure(Call<MapMatchingResponse> call, Throwable throwable) {
Log.e(TAG, "MapboxMapMatching error: " + throwable.getMessage());
}
});
} catch (ServicesException servicesException) {
servicesException.printStackTrace();
}
locationLayerPlugin = new LocationLayerPlugin(mapView, mapboxMap, null); locationLayerPlugin.setLocationLayerEnabled(LocationLayerMode.NAVIGATION);
}
private void getRoute(final Position origin, final Position destination) {
ArrayList<Position> positions = new ArrayList<>();
positions.add(origin);
positions.add(destination);
MapboxDirections client = new MapboxDirections.Builder()
.setAccessToken(Mapbox.getAccessToken())
.setOrigin(origin)
.setDestination(destination)
.setAlternatives(true)
.setProfile(DirectionsCriteria.PROFILE_DRIVING)
.setSteps(true)
.setOverview(DirectionsCriteria.OVERVIEW_FULL)
.setBearings(new double[]{60, 45}, new double[]{45, 45})
.setAnnotation(DirectionsCriteria.ANNOTATION_DISTANCE, DirectionsCriteria.ANNOTATION_DURATION)
.build();
client.enqueueCall(new Callback<DirectionsResponse>() {
#Override
public void onResponse(Call<DirectionsResponse> call, Response<DirectionsResponse> response) {
Log.d(TAG, "API call URL: " + call.request().url().toString());
Log.d(TAG, "Response code: " + response.code());
if (response.body() == null) {
Log.e(TAG, "No routes found, make sure you set the right user and access token.");
return;
}
// Print some info about the route
route = response.body().getRoutes().get(0);
//showMessage(String.format(Locale.US, "Route is %.1f meters long.", currentRoute.getDistance()));
// Draw the route on the map
drawRoute(route, origin, destination);
}
#Override
public void onFailure(Call<MapMatchingResponse> call, Throwable throwable) {
}
});
} catch (ServicesException servicesException) {
servicesException.printStackTrace();
}
locationLayerPlugin = new LocationLayerPlugin(mapView, mapboxMap, null);
locationLayerPlugin.setLocationLayerEnabled(LocationLayerMode.NAVIGATION);
}`
Route is getting draw but from origin to destination.
For example, if complete route is like, A-B-C-D-E, so I want to draw route which connects A-B-C-D-E but I am able to connect directly A-E.
Please guide.
Thanks in advance.
i'm trying to get the coordinates of my kml layer in my android app, and i just cant seem to find how to do it.
i have this kml layer:
KmlLayer layer = new KmlLayer(mMap, R.raw.allowedarea, getApplicationContext());
and i'm trying to get the Latitude and Longtitude list of his boundries points.
ArrayList<LatLnt> latlitArray = layer.soemthing();
could find anything, please guys help.
Try this solution
try {
KmlLayer layer = new KmlLayer(googleMap, R.raw.zone, this);
layer.addLayerToMap();
Iterable<KmlContainer> containers = layer.getContainers();
accessContainers(containers);
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
}
public void accessContainers(Iterable<KmlContainer> containers) {
for (KmlContainer container : containers) {
if (container != null) {
if (container.hasContainers()) {
accessContainers(container.getContainers());
} else {
if (container.hasPlacemarks()) {
accessPlacemarks(container.getPlacemarks());
}
}
}
}
}
public void accessPlacemarks(Iterable<KmlPlacemark> placemarks) {
for (KmlPlacemark placemark : placemarks) {
if (placemark != null) {
KmlGeometry geometry = placemark.getGeometry();
if (geometry instanceof KmlPolygon) {
KmlPolygon polygon = (KmlPolygon) geometry;
mLatLngList.addAll(polygon.getOuterBoundaryCoordinates());
}
}
}
}
This will recursively access every placemark geometry inside the container. I'm not aware if the object obtained can actually be an instance of any other class or collection besides List and LatLng.
public void accessContainers(Iterable<KmlContainer> containers) {
for(KmlContainer c : containers) {
if(c.hasPlacemarks()) {
for(KmlPlacemark p : c.getPlacemarks()) {
KmlGeometry g = p.getGeometry();
Object object = g.getGeometryObject();
if(object instanceof LatLng) {
LatLng latlng = (LatLng)object;
//Do more stuff with the point
}
if(object instanceof List<?>) {
List<LatLng> list = (List<LatLng>)object;
//Do more stuff with the list of points
}
Log.d(TAG, g.getGeometryType() + ":" + object.toString());
}
}
if(c.hasContainers()) {
accessContainers(c.getContainers());
}
}
}
I am trying to get directions between two point in heremaps. But in routeManagerListener, I get an error that is INVALID_OPERATION. please help.
This is my listener.
private RouteManager.Listener routeManagerListener = new RouteManager.Listener() {
#Override
public void onCalculateRouteFinished(RouteManager.Error error, List<RouteResult> list) {
if (error == RouteManager.Error.NONE &&
list.get(0).getRoute() != null) {
// create a map route object and place it on the map
mapRoute = new MapRoute(list.get(0).getRoute());
map.addMapObject(mapRoute);
// Get the bounding box containing the route and zoom in
GeoBoundingBox gbb = list.get(0).getRoute().getBoundingBox();
map.zoomTo(gbb, Map.Animation.NONE,
Map.MOVE_PRESERVE_ORIENTATION);
textViewResult.setText(
String.format("Route calculated with %d maneuvers.",
list.get(0).getRoute().getManeuvers().size()));
}else{
textViewResult.setText(
String.format("Route calculation failed: %s",
error.toString()));
}
}
#Override
public void onProgress(int i) {
textViewResult.setText(
String.format("... %d percent done ...", i));
}
};
and this is getDirections method.
public void getDirections(View view) {
// 1. clear previous results
textViewResult.setText("");
if (map != null && mapRoute != null) {
map.removeMapObject(mapRoute);
mapRoute = null;
}
// 2. Initialize RouteManager
RouteManager routeManager = new RouteManager();
// 3. Select routing options via RoutingMode
RoutePlan routePlan = new RoutePlan();
RouteOptions routeOptions = new RouteOptions();
routeOptions.setTransportMode(RouteOptions.TransportMode.CAR);
routeOptions.setRouteType(RouteOptions.Type.FASTEST);
routePlan.setRouteOptions(routeOptions);
// 4. Select Waypoints for your routes
// START: Nokia, Burnaby
routePlan.addWaypoint(new GeoCoordinate(49.1966286, -123.0053635));
// END: Airport, YVR
routePlan.addWaypoint(new GeoCoordinate(49.1947289, -123.1762924));
// 5. Retrieve Routing information via RouteManagerListener
RouteManager.Error error =
routeManager.calculateRoute(routePlan, routeManagerListener);
if (error != RouteManager.Error.NONE) {
Toast.makeText(getApplicationContext(),
"Route calculation failed with: " + error.toString(),
Toast.LENGTH_SHORT)
.show();
}
}
and how ı call this method:
bgetDirection.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getDirections(view);
}
});