Skip to main content

Overview

Service Areas define the geographic boundaries where Viax operates, ensuring drivers and clients can only request or accept trips within designated zones.
Service area configuration prevents trips outside operational zones and helps manage driver coverage.

Geographic Boundaries

Service areas are defined using geographic coordinates:
class ServiceArea {
  final int id;
  final String nombre;
  final String descripcion;
  final List<LatLng> boundaries; // Polygon coordinates
  final bool activo;
  final DateTime creadoEn;
  final DateTime? actualizadoEn;
}

Defining Service Areas

1

Create Area

Define a new service area with a descriptive name
2

Draw Boundaries

Use map interface to draw polygon boundaries
3

Set Properties

Configure area-specific settings:
  • Minimum drivers required
  • Vehicle types allowed
  • Operating hours
4

Activate Area

Enable the service area for operations

Colombia Service Areas

Major Cities

Department: CundinamarcaPopulation: ~8 millionCoverage: All 20 localitiesVehicle Types: All (moto, carro, moto_carga, carro_carga)

Point-in-Polygon Check

Determine if a location is within a service area:
bool isLocationInServiceArea(LatLng location, ServiceArea area) {
  // Ray casting algorithm
  int intersectCount = 0;
  final polygon = area.boundaries;
  
  for (int i = 0; i < polygon.length; i++) {
    final vertex1 = polygon[i];
    final vertex2 = polygon[(i + 1) % polygon.length];
    
    if (rayIntersectsSegment(location, vertex1, vertex2)) {
      intersectCount++;
    }
  }
  
  // Odd number of intersections = inside polygon
  return intersectCount % 2 == 1;
}

bool rayIntersectsSegment(LatLng point, LatLng vertex1, LatLng vertex2) {
  if (vertex1.latitude > vertex2.latitude) {
    final temp = vertex1;
    vertex1 = vertex2;
    vertex2 = temp;
  }
  
  if (point.latitude < vertex1.latitude || point.latitude > vertex2.latitude) {
    return false;
  }
  
  if (point.longitude >= max(vertex1.longitude, vertex2.longitude)) {
    return false;
  }
  
  if (point.longitude < min(vertex1.longitude, vertex2.longitude)) {
    return true;
  }
  
  final slope = (vertex2.longitude - vertex1.longitude) / 
                (vertex2.latitude - vertex1.latitude);
  final xIntersection = vertex1.longitude + 
                        (point.latitude - vertex1.latitude) * slope;
  
  return point.longitude < xIntersection;
}

Validation on Trip Request

Check both origin and destination:
Future<Result<bool>> validateTripLocations({
  required LatLng origin,
  required LatLng destination,
}) async {
  final serviceAreas = await getActiveServiceAreas();
  
  bool originValid = false;
  bool destinationValid = false;
  
  for (final area in serviceAreas) {
    if (isLocationInServiceArea(origin, area)) {
      originValid = true;
    }
    if (isLocationInServiceArea(destination, area)) {
      destinationValid = true;
    }
  }
  
  if (!originValid) {
    return Error(ValidationFailure(
      'El origen está fuera del área de servicio'
    ));
  }
  
  if (!destinationValid) {
    return Error(ValidationFailure(
      'El destino está fuera del área de servicio'
    ));
  }
  
  return Success(true);
}

Map Visualization

Display service areas on the map:
MapboxMap(
  initialCameraPosition: CameraPosition(
    target: LatLng(4.6097, -74.0817), // Bogotá
    zoom: 11,
  ),
  polygons: serviceAreas.map((area) => Polygon(
    polygonId: PolygonId(area.id.toString()),
    points: area.boundaries,
    strokeColor: AppColors.primary,
    strokeWidth: 2,
    fillColor: AppColors.primary.withOpacity(0.2),
  )).toSet(),
)

Database Schema

CREATE TABLE service_areas (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nombre VARCHAR(100) NOT NULL,
  descripcion TEXT,
  activo BOOLEAN DEFAULT TRUE,
  creado_en TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  actualizado_en TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE service_area_boundaries (
  id INT AUTO_INCREMENT PRIMARY KEY,
  service_area_id INT NOT NULL,
  latitud DECIMAL(10, 8) NOT NULL,
  longitud DECIMAL(11, 8) NOT NULL,
  orden INT NOT NULL, -- Order of point in polygon
  FOREIGN KEY (service_area_id) REFERENCES service_areas(id) ON DELETE CASCADE
);

CREATE TABLE service_area_restrictions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  service_area_id INT NOT NULL,
  tipo_vehiculo ENUM('moto', 'carro', 'moto_carga', 'carro_carga'),
  hora_inicio TIME,
  hora_fin TIME,
  activo BOOLEAN DEFAULT TRUE,
  FOREIGN KEY (service_area_id) REFERENCES service_areas(id) ON DELETE CASCADE
);

Operating Hours

Restrict service availability by time:
class OperatingHours {
  final TimeOfDay start;
  final TimeOfDay end;
  final List<int> daysOfWeek; // 1-7 (Monday-Sunday)
  
  bool isCurrentlyOperating() {
    final now = DateTime.now();
    final currentTime = TimeOfDay.fromDateTime(now);
    final currentDay = now.weekday;
    
    if (!daysOfWeek.contains(currentDay)) {
      return false;
    }
    
    final currentMinutes = currentTime.hour * 60 + currentTime.minute;
    final startMinutes = start.hour * 60 + start.minute;
    final endMinutes = end.hour * 60 + end.minute;
    
    return currentMinutes >= startMinutes && currentMinutes <= endMinutes;
  }
}

Geocoding Integration

Use Nominatim (OpenStreetMap) for address validation:
Future<LatLng?> geocodeAddress(String address) async {
  final url = 'https://nominatim.openstreetmap.org/search';
  final params = {
    'q': address,
    'format': 'json',
    'countrycodes': 'co', // Colombia only
    'limit': '1',
  };
  
  final response = await http.get(Uri.parse(url).replace(
    queryParameters: params,
  ));
  
  if (response.statusCode == 200) {
    final data = jsonDecode(response.body);
    if (data.isNotEmpty) {
      return LatLng(
        double.parse(data[0]['lat']),
        double.parse(data[0]['lon']),
      );
    }
  }
  
  return null;
}

Driver Coverage Zones

Track which service areas have adequate driver coverage:
class CoverageMetrics {
  final ServiceArea area;
  final int activeDrivers;
  final int minimumRequired;
  final double averageResponseTime; // seconds
  
  bool get hasAdequateCoverage => activeDrivers >= minimumRequired;
  
  CoverageStatus get status {
    if (activeDrivers == 0) return CoverageStatus.noCoverage;
    if (activeDrivers < minimumRequired * 0.5) return CoverageStatus.low;
    if (activeDrivers < minimumRequired) return CoverageStatus.medium;
    return CoverageStatus.high;
  }
}

enum CoverageStatus {
  noCoverage,
  low,
  medium,
  high,
}

Expansion Planning

Analyze demand for new service areas:

Request Density

Track trip requests outside current areas

Driver Interest

Survey drivers on expansion preferences

Competition Analysis

Research competitor coverage

Multi-Area Trips

Handle trips that cross service area boundaries:
enum TripAreaPolicy {
  // Both origin and destination must be in same area
  sameAreaOnly,
  
  // Origin must be in an area, destination can be anywhere
  originAreaOnly,
  
  // Either origin or destination must be in an area
  eitherInArea,
  
  // Both origin and destination must be in areas (can be different)
  bothInAreas,
}

bool validateMultiAreaTrip({
  required LatLng origin,
  required LatLng destination,
  required TripAreaPolicy policy,
}) {
  final originArea = findServiceAreaForLocation(origin);
  final destArea = findServiceAreaForLocation(destination);
  
  switch (policy) {
    case TripAreaPolicy.sameAreaOnly:
      return originArea != null && 
             destArea != null && 
             originArea.id == destArea.id;
    
    case TripAreaPolicy.originAreaOnly:
      return originArea != null;
    
    case TripAreaPolicy.eitherInArea:
      return originArea != null || destArea != null;
    
    case TripAreaPolicy.bothInAreas:
      return originArea != null && destArea != null;
  }
}

Best Practices

Begin with smaller, well-defined service areas and expand based on demand and driver availability.
Regularly check driver coverage in all service areas to ensure adequate response times.
Align service areas with natural or administrative boundaries (municipalities, neighborhoods).
Expand service areas based on commute patterns and high-demand routes.
Clearly display service area boundaries to users so they know where service is available.

Pricing Configuration

Zone-based pricing (future)

Driver Management

Assign drivers to zones

Trip Monitoring

Monitor trips by area

Build docs developers (and LLMs) love