I need to perform turn-by-turn navigation using an already solved route stored in the database. Is it possible to do this with a JSON file, or is there any alternative using pre-solved route data? Below is my reference code, in which I am facing difficulty converting the JSON to a RouteResult.
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Location;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Navigation;
using Esri.ArcGISRuntime.Symbology;
using Esri.ArcGISRuntime.Tasks.NetworkAnalysis;
using Esri.ArcGISRuntime.UI;
using Location = Esri.ArcGISRuntime.Location.Location;
namespace PARSMobile.Pages;
public partial class NavigationRoute : ContentPage, IDisposable
{
// Variables for tracking the navigation route.
private RouteTracker _tracker;
private RouteResult _routeResult;
private Route _route;
// List of driving directions for the route.
private IReadOnlyList<DirectionManeuver> _directionsList;
// Cancellation token for speech synthesizer.
private CancellationTokenSource _speechToken = new CancellationTokenSource();
// Graphics to show progress along the route.
private Graphic _routeAheadGraphic;
private Graphic _routeTraveledGraphic;
// San Diego Convention Center.
private readonly MapPoint _conventionCenter = new MapPoint(-117.160386727, 32.706608, SpatialReferences.Wgs84);
// USS San Diego Memorial.
private readonly MapPoint _memorial = new MapPoint(-117.173034, 32.712327, SpatialReferences.Wgs84);
// RH Fleet Aerospace Museum.
private readonly MapPoint _aerospaceMuseum = new MapPoint(-117.147230, 32.730467, SpatialReferences.Wgs84);
// Feature service for routing in San Diego.
public NavigationRoute()
{
InitializeComponent();
_ = Initialize();
}
private async Task Initialize()
{
try
{
// Create the map view.
MyMapView.Map = new Esri.ArcGISRuntime.Mapping.Map(BasemapStyle.ArcGISStreets);
string routeFilePath = Path.Combine(FileSystem.Current.AppDataDirectory, "route.json");
if (File.Exists(routeFilePath))
{
//Load from JSON instead of solving
string json = File.ReadAllText(routeFilePath);
_routeResult = RouteResult.FromJson(json);
_route = _routeResult.Routes[0];
}
// Add a graphics overlay for the route graphics.
MyMapView.GraphicsOverlays.Add(new GraphicsOverlay());
// Add graphics for the stops.
SimpleMarkerSymbol stopSymbol = new SimpleMarkerSymbol(
SimpleMarkerSymbolStyle.Diamond,
System.Drawing.Color.FromArgb(255, 255, 69, 0), // Use System.Drawing.Color
20
);
MyMapView.GraphicsOverlays[0].Graphics.Add(new Graphic(_conventionCenter, stopSymbol));
MyMapView.GraphicsOverlays[0].Graphics.Add(new Graphic(_memorial, stopSymbol));
MyMapView.GraphicsOverlays[0].Graphics.Add(new Graphic(_aerospaceMuseum, stopSymbol));
// Create a graphic (with a dashed line symbol) to represent the route.
_routeAheadGraphic = new Graphic(_route.RouteGeometry) { Symbol = new SimpleLineSymbol(SimpleLineSymbolStyle.Dash, System.Drawing.Color.FromArgb(138, 43, 226), 5) };
// Create a graphic (solid) to represent the route that's been traveled (initially empty).
_routeTraveledGraphic = new Graphic { Symbol = new SimpleLineSymbol(SimpleLineSymbolStyle.Solid, System.Drawing.Color.FromArgb(173, 216, 230), 3) };
// Add the route graphics to the map view.
MyMapView.GraphicsOverlays[0].Graphics.Add(_routeAheadGraphic);
MyMapView.GraphicsOverlays[0].Graphics.Add(_routeTraveledGraphic);
// Set the map viewpoint to show the entire route.
await MyMapView.SetViewpointGeometryAsync(_route.RouteGeometry!, 100);
// Enable the navigation button.
StartNavigationButton.IsEnabled = true;
}
catch (Exception e)
{
await Application.Current!.Windows[0].Page!.DisplayAlert("Error", e.Message, "OK");
}
}
private void StartNavigation(object sender, EventArgs e)
{
// Disable the start navigation button.
StartNavigationButton.IsEnabled = false;
// Get the directions for the route.
_directionsList = _route.DirectionManeuvers;
// Create a route tracker.
_tracker = new RouteTracker(_routeResult, 0, true);
_tracker.NewVoiceGuidance += SpeakDirection;
// Handle route tracking status changes.
_tracker.TrackingStatusChanged += TrackingStatusUpdated;
// Turn on navigation mode for the map view.
MyMapView.LocationDisplay.AutoPanMode = LocationDisplayAutoPanMode.Navigation;
MyMapView.LocationDisplay.AutoPanModeChanged += AutoPanModeChanged;
// Add a data source for the location display.
var simulationParameters = new SimulationParameters(DateTimeOffset.Now, 40.0);
var simulatedDataSource = new SimulatedLocationDataSource();
simulatedDataSource.SetLocationsWithPolyline(_route.RouteGeometry, simulationParameters);
MyMapView.LocationDisplay.DataSource = new RouteTrackerDisplayLocationDataSource(simulatedDataSource, _tracker);
// Use this instead if you want real location:
// MyMapView.LocationDisplay.DataSource = new RouteTrackerLocationDataSource(new SystemLocationDataSource(), _tracker);
// Enable the location display (this wil start the location data source).
MyMapView.LocationDisplay.IsEnabled = true;
// Show the message block for text output.
MessagesTextBlock.IsVisible = true;
}
private void TrackingStatusUpdated(object sender, RouteTrackerTrackingStatusChangedEventArgs e)
{
TrackingStatus status = e.TrackingStatus;
// Start building a status message for the UI.
System.Text.StringBuilder statusMessageBuilder = new System.Text.StringBuilder();
// Check the destination status.
if (status.DestinationStatus == DestinationStatus.NotReached || status.DestinationStatus == DestinationStatus.Approaching)
{
statusMessageBuilder.AppendLine("Distance remaining: " +
status.RouteProgress.RemainingDistance.DisplayText + " " +
status.RouteProgress.RemainingDistance.DisplayTextUnits.PluralDisplayName);
statusMessageBuilder.AppendLine("Time remaining: " +
status.RouteProgress.RemainingTime.ToString(@hh\:mm\:ss));
if (status.CurrentManeuverIndex + 1 < _directionsList.Count)
{
statusMessageBuilder.AppendLine("Next direction: " + _directionsList[status.CurrentManeuverIndex + 1].DirectionText);
}
// Set geometries for progress and the remaining route.
_routeAheadGraphic.Geometry = status.RouteProgress.RemainingGeometry;
_routeTraveledGraphic.Geometry = status.RouteProgress.TraversedGeometry;
}
else if (status.DestinationStatus == DestinationStatus.Reached)
{
statusMessageBuilder.AppendLine("Destination reached.");
// Set the route geometries to reflect the completed route.
_routeAheadGraphic.Geometry = null;
_routeTraveledGraphic.Geometry = status.RouteResult.Routes[0].RouteGeometry;
// Navigate to the next stop (if there are stops remaining).
if (status.RemainingDestinationCount > 1)
{
_tracker.SwitchToNextDestinationAsync();
}
// Stop the simulated location data source.
MyMapView.LocationDisplay.DataSource.StopAsync();
}
Microsoft.Maui.ApplicationModel.MainThread.BeginInvokeOnMainThread(() =>
{
// Show the status information in the UI.
MessagesTextBlock.Text = statusMessageBuilder.ToString().TrimEnd('\n').TrimEnd('\r');
});
}
private void SpeakDirection(object sender, RouteTrackerNewVoiceGuidanceEventArgs e)
{
// Say the direction using voice synthesis.
if (e.VoiceGuidance.Text?.Length > 0)
{
_speechToken.Cancel();
_speechToken = new CancellationTokenSource();
TextToSpeech.Default.SpeakAsync(e.VoiceGuidance.Text, null, _speechToken.Token);
}
}
private void AutoPanModeChanged(object sender, LocationDisplayAutoPanMode e)
{
// Turn the recenter button on or off when the location display changes to or from navigation mode.
RecenterButton.IsEnabled = e != LocationDisplayAutoPanMode.Navigation;
}
private void RecenterButton_Click(object sender, EventArgs e)
{
// Change the mapview to use navigation mode.
MyMapView.LocationDisplay.AutoPanMode = LocationDisplayAutoPanMode.Navigation;
}
public void Dispose()
{
// Stop the tracker.
if (_tracker != null)
{
_tracker.TrackingStatusChanged -= TrackingStatusUpdated;
_tracker.NewVoiceGuidance -= SpeakDirection;
_tracker = null;
}
// Stop the location data source.
MyMapView.LocationDisplay?.DataSource?.StopAsync();
}
}
// This location data source uses an input data source and a route tracker.
// The location source that it updates is based on the snapped-to-route location from the route tracker.
public class RouteTrackerDisplayLocationDataSource : LocationDataSource
{
private LocationDataSource _inputDataSource;
private RouteTracker _routeTracker;
public RouteTrackerDisplayLocationDataSource(LocationDataSource dataSource, RouteTracker routeTracker)
{
// Set the data source
_inputDataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
// Set the route tracker.
_routeTracker = routeTracker ?? throw new ArgumentNullException(nameof(routeTracker));
// Change the tracker location when the source location changes.
_inputDataSource.LocationChanged += InputLocationChanged;
// Update the location output when the tracker location updates.
_routeTracker.TrackingStatusChanged += TrackingStatusChanged;
}
// Change the InputLocationChanged method signature to match EventHandler<Location>
private void InputLocationChanged(object sender, Location e)
{
// Update the tracker location with the new location from the source (simulation or GPS).
_routeTracker.TrackLocationAsync(e);
}
private void TrackingStatusChanged(object sender, RouteTrackerTrackingStatusChangedEventArgs e)
{
// Check if the tracking status has a location.
if (e.TrackingStatus.DisplayLocation != null)
{
// Call the base method for LocationDataSource to update the location with the tracked (snapped to route) location.
UpdateLocation(e.TrackingStatus.DisplayLocation);
}
}
protected override Task OnStartAsync() => _inputDataSource.StartAsync();
protected override Task OnStopAsync() => _inputDataSource.StartAsync();
}