Map Integration
Viax uses Flutter Map with Mapbox tiles for high-quality, interactive navigation.The app uses Mapbox for map tiles and routing, providing accurate navigation with real-time traffic data.
Map Features
Real-Time GPS
Continuous location tracking with high accuracy
Route Optimization
Best path calculation avoiding traffic
Turn-by-Turn
Voice and visual navigation guidance
Live Traffic
Real-time traffic conditions and alerts
Map Service Implementation
Mapbox Service
The app uses a centralized Mapbox service:Mapbox Service
class MapboxService {
static const String accessToken = 'YOUR_MAPBOX_TOKEN';
static const String baseUrl = 'https://api.mapbox.com';
/// Get route between waypoints
static Future<MapboxRoute> getRoute({
required List<LatLng> waypoints,
bool optimize = false,
bool avoidTraffic = true,
}) async {
if (waypoints.length < 2) {
throw ArgumentError('At least 2 waypoints required');
}
// Build coordinates string
final coordinates = waypoints
.map((point) => '${point.longitude},${point.latitude}')
.join(';');
// Build request URL
final url = Uri.parse(
'$baseUrl/directions/v5/mapbox/driving/$coordinates'
).replace(queryParameters: {
'access_token': accessToken,
'geometries': 'geojson',
'overview': 'full',
'steps': 'true',
'alternatives': 'true',
if (avoidTraffic) 'annotations': 'congestion,duration',
});
final response = await http.get(url);
if (response.statusCode == 200) {
final data = json.decode(response.body);
return MapboxRoute.fromJson(data['routes'][0]);
} else {
throw Exception('Failed to get route');
}
}
/// Get current traffic conditions
static Future<TrafficData> getTraffic({
required LatLng location,
double radiusKm = 5.0,
}) async {
// TomTom Traffic API integration
final tomtomUrl = Uri.parse(
'https://api.tomtom.com/traffic/services/4/flowSegmentData/absolute/10/json'
).replace(queryParameters: {
'key': 'YOUR_TOMTOM_KEY',
'point': '${location.latitude},${location.longitude}',
});
final response = await http.get(tomtomUrl);
if (response.statusCode == 200) {
return TrafficData.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to get traffic data');
}
}
}
Route Model
Mapbox Route Model
class MapboxRoute {
final double distance; // Distance in kilometers
final double duration; // Duration in seconds
final List<LatLng> geometry; // Route polyline points
final List<RouteStep> steps; // Turn-by-turn instructions
final String? congestion; // Traffic congestion level
MapboxRoute({
required this.distance,
required this.duration,
required this.geometry,
required this.steps,
this.congestion,
});
factory MapboxRoute.fromJson(Map<String, dynamic> json) {
final geometry = json['geometry']['coordinates'] as List;
final steps = (json['legs'][0]['steps'] as List)
.map((step) => RouteStep.fromJson(step))
.toList();
return MapboxRoute(
distance: (json['distance'] as num).toDouble() / 1000, // meters to km
duration: (json['duration'] as num).toDouble(),
geometry: geometry
.map((coord) => LatLng(coord[1], coord[0]))
.toList(),
steps: steps,
congestion: json['congestion'],
);
}
}
class RouteStep {
final String instruction; // "Turn left on Main St"
final double distance; // Distance for this step
final String maneuver; // "turn", "arrive", etc.
final LatLng location; // Where to execute maneuver
RouteStep({
required this.instruction,
required this.distance,
required this.maneuver,
required this.location,
});
factory RouteStep.fromJson(Map<String, dynamic> json) {
return RouteStep(
instruction: json['maneuver']['instruction'],
distance: (json['distance'] as num).toDouble(),
maneuver: json['maneuver']['type'],
location: LatLng(
json['maneuver']['location'][1],
json['maneuver']['location'][0],
),
);
}
}
Map Display
Flutter Map Widget
The driver app uses Flutter Map for rendering:Map Implementation
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
class DriverMapView extends StatefulWidget {
final LatLng? currentLocation;
final LatLng? pickupLocation;
final LatLng? destinationLocation;
final List<LatLng>? routePoints;
@override
State<DriverMapView> createState() => _DriverMapViewState();
}
class _DriverMapViewState extends State<DriverMapView> {
final MapController _mapController = MapController();
@override
Widget build(BuildContext context) {
return FlutterMap(
mapController: _mapController,
options: MapOptions(
center: widget.currentLocation ?? LatLng(4.6097, -74.0817),
zoom: 15.0,
maxZoom: 18.0,
minZoom: 10.0,
interactiveFlags: InteractiveFlag.all,
),
children: [
// Base map tiles
TileLayer(
urlTemplate: 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token={accessToken}',
additionalOptions: {
'accessToken': MapboxService.accessToken,
},
),
// Route polyline
if (widget.routePoints != null)
PolylineLayer(
polylines: [
Polyline(
points: widget.routePoints!,
strokeWidth: 6.0,
color: AppColors.primary,
borderStrokeWidth: 2.0,
borderColor: Colors.white,
),
],
),
// Markers
MarkerLayer(
markers: _buildMarkers(),
),
],
);
}
List<Marker> _buildMarkers() {
List<Marker> markers = [];
// Current location marker
if (widget.currentLocation != null) {
markers.add(
Marker(
point: widget.currentLocation!,
width: 40,
height: 40,
builder: (context) => _buildDriverMarker(),
),
);
}
// Pickup location marker
if (widget.pickupLocation != null) {
markers.add(
Marker(
point: widget.pickupLocation!,
width: 50,
height: 50,
builder: (context) => _buildPickupMarker(),
),
);
}
// Destination marker
if (widget.destinationLocation != null) {
markers.add(
Marker(
point: widget.destinationLocation!,
width: 50,
height: 50,
builder: (context) => _buildDestinationMarker(),
),
);
}
return markers;
}
Widget _buildDriverMarker() {
return Container(
decoration: BoxDecoration(
color: AppColors.primary,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 3),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Icon(
Icons.navigation,
color: Colors.white,
size: 20,
),
);
}
Widget _buildPickupMarker() {
return Column(
children: [
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.success,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 6,
),
],
),
child: Icon(
Icons.person_pin_circle,
color: Colors.white,
size: 24,
),
),
Container(
width: 2,
height: 10,
color: AppColors.success,
),
],
);
}
Widget _buildDestinationMarker() {
return Column(
children: [
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.error,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 6,
),
],
),
child: Icon(
Icons.location_pin,
color: Colors.white,
size: 24,
),
),
Container(
width: 2,
height: 10,
color: AppColors.error,
),
],
);
}
}
Turn-by-Turn Navigation
Navigation Instruction Display
Navigation Instruction Card
class NavigationInstructionCard extends StatelessWidget {
final RouteStep currentStep;
final double distanceToStep;
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(16),
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 12,
offset: Offset(0, 4),
),
],
),
child: Row(
children: [
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
_getManeuverIcon(currentStep.maneuver),
color: AppColors.primary,
size: 32,
),
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
currentStep.instruction,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 4),
Text(
'in ${_formatDistance(distanceToStep)}',
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
),
],
),
),
],
),
);
}
IconData _getManeuverIcon(String maneuver) {
switch (maneuver) {
case 'turn-left':
return Icons.turn_left;
case 'turn-right':
return Icons.turn_right;
case 'straight':
return Icons.straight;
case 'arrive':
return Icons.location_pin;
default:
return Icons.navigation;
}
}
String _formatDistance(double meters) {
if (meters < 1000) {
return '${meters.toInt()} m';
} else {
return '${(meters / 1000).toStringAsFixed(1)} km';
}
}
}
External Navigation Apps
Launch External Navigation
Drivers can use their preferred navigation app:Navigation Launcher Service
import 'package:url_launcher/url_launcher.dart';
import 'package:device_apps/device_apps.dart';
class NavigationLauncherService {
/// Launch navigation to coordinates
static Future<void> launchNavigation({
required double lat,
required double lng,
String? label,
}) async {
final apps = await _getAvailableNavigationApps();
if (apps.isEmpty) {
// Fallback to Google Maps web
await _launchGoogleMapsWeb(lat, lng, label);
return;
}
if (apps.length == 1) {
await _launchApp(apps.first, lat, lng, label);
} else {
// Show selection dialog
// User chooses their preferred app
}
}
static Future<List<String>> _getAvailableNavigationApps() async {
List<String> availableApps = [];
// Check for Google Maps
if (await DeviceApps.isAppInstalled('com.google.android.apps.maps')) {
availableApps.add('google_maps');
}
// Check for Waze
if (await DeviceApps.isAppInstalled('com.waze')) {
availableApps.add('waze');
}
return availableApps;
}
static Future<void> _launchApp(
String app,
double lat,
double lng,
String? label,
) async {
Uri? uri;
switch (app) {
case 'google_maps':
uri = Uri.parse(
'google.navigation:q=$lat,$lng&mode=d',
);
break;
case 'waze':
uri = Uri.parse(
'waze://?ll=$lat,$lng&navigate=yes',
);
break;
}
if (uri != null && await canLaunchUrl(uri)) {
await launchUrl(uri);
}
}
}
Traffic & Route Optimization
Real-Time Traffic Data
Traffic Integration
class TrafficService {
/// Get traffic conditions along route
static Future<List<TrafficSegment>> getRouteTraffic(
List<LatLng> routePoints,
) async {
List<TrafficSegment> segments = [];
// Sample points along route
for (int i = 0; i < routePoints.length - 1; i += 10) {
final point = routePoints[i];
final traffic = await MapboxService.getTraffic(location: point);
segments.add(TrafficSegment(
location: point,
congestionLevel: traffic.congestionLevel,
speed: traffic.currentSpeed,
freeFlowSpeed: traffic.freeFlowSpeed,
));
}
return segments;
}
}
class TrafficSegment {
final LatLng location;
final String congestionLevel; // "low", "moderate", "heavy", "severe"
final double speed; // Current average speed
final double freeFlowSpeed; // Speed with no traffic
Color get color {
switch (congestionLevel) {
case 'low':
return Colors.green;
case 'moderate':
return Colors.yellow;
case 'heavy':
return Colors.orange;
case 'severe':
return Colors.red;
default:
return Colors.grey;
}
}
}
GPS Best Practices
High Accuracy Mode
High Accuracy Mode
Always use high accuracy GPS for trips:
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
timeLimit: Duration(seconds: 30),
);
Battery Optimization
Battery Optimization
Balance accuracy and battery:
- Use
distanceFilter: 10to update every 10 meters - Reduce update frequency when stationary
- Stop tracking when trip is complete
Offline Support
Offline Support
Handle poor connectivity:
- Cache map tiles for frequent areas
- Store last known route
- Show offline indicator to user
Navigation Tips:
- Keep GPS on during entire trip
- Ensure location permissions are granted
- Use external navigation for complex routes
- Monitor battery level on long trips
Next Steps
Trip Execution
Complete trip workflow from start to finish
Earnings
Track your income from completed trips