Skip to main content

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 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

Always use high accuracy GPS for trips:
final position = await Geolocator.getCurrentPosition(
  desiredAccuracy: LocationAccuracy.high,
  timeLimit: Duration(seconds: 30),
);
Balance accuracy and battery:
  • Use distanceFilter: 10 to update every 10 meters
  • Reduce update frequency when stationary
  • Stop tracking when trip is complete
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

Build docs developers (and LLMs) love