=============== LIBRARY RULES =============== - This project uses: flutter, dart, sdk # Magic Lane Maps SDK for Flutter Documentation ## Introduction The Magic Lane Maps SDK for Flutter is a comprehensive mapping and navigation solution designed for building feature-rich mobile applications on Android (API 23+) and iOS (14.0+) platforms. This documentation project serves as the complete reference for developers integrating advanced mapping capabilities into Flutter applications, covering version 3.1.3 of the SDK. The SDK provides access to global OpenStreetMap data with capabilities for displaying interactive maps, calculating routes, providing turn-by-turn navigation, searching for points of interest, and managing offline map functionality. The SDK architecture is built around Flutter/Dart (minimum Dart 3.9.0, Flutter 3.35.1) and offers seamless integration with native Android and iOS platforms through plugin interfaces. Core capabilities include 3D terrain topography rendering, real-time traffic integration, sensor data recording, driver behavior analysis, weather services, and comprehensive alarm systems for boundary crossing, speed monitoring, and tunnel detection. The documentation encompasses detailed guides, working code examples, API references for 500+ classes, and comprehensive troubleshooting resources to enable developers to build production-ready mapping applications with both online and offline functionality. ## API Documentation and Key Functions ### SDK Initialization and Map Display Initialize the SDK and display an interactive map widget in your Flutter application. ```dart import 'package:flutter/material.dart'; import 'package:magiclane_maps_flutter/magiclane_maps_flutter.dart'; const projectApiToken = String.fromEnvironment('GEM_TOKEN'); void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, title: 'Maps SDK App', home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { late GemMapController _mapController; @override void dispose() { GemKit.release(); // Clean up SDK resources super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Colors.deepPurple[900], title: const Text('Hello Map', style: TextStyle(color: Colors.white)), ), body: GemMap( appAuthorization: projectApiToken, onMapCreated: _onMapCreated, ), ); } void _onMapCreated(GemMapController controller) { _mapController = controller; // Map is ready - configure camera, gestures, etc. _mapController.centerOnCoordinates( Coordinates(latitude: 48.858844, longitude: 2.294351), zoomLevel: 80, ); } } ``` ### Route Calculation Calculate routes between multiple waypoints with customizable preferences and display them on the map. ```dart // Define departure and destination landmarks final departureLandmark = Landmark.withLatLng( latitude: 48.85682, // Paris longitude: 2.34375 ); final destinationLandmark = Landmark.withLatLng( latitude: 50.84644, // Brussels longitude: 4.34587 ); // Configure route preferences final routePreferences = RoutePreferences(); // Optional: Set transport mode // routePreferences.setTransportMode(RouteTransportMode.car); // routePreferences.setCarProfile(CarProfile()..avoidHighways = true); // Calculate route with callback handling TaskHandler? _routingHandler = RoutingService.calculateRoute( [departureLandmark, destinationLandmark], routePreferences, (err, routes) { if (err == GemError.success && routes.isNotEmpty) { // Display routes on map final routesMap = _mapController.preferences.routes; for (final route in routes) { // Add route with label showing distance and time final distance = route.getTimeDistance().totalDistanceM; final time = route.getTimeDistance().totalTimeS; final label = '${_formatDistance(distance)} \n${_formatDuration(time)}'; routesMap.add(route, route == routes.first, label: label); } // Center camera on calculated routes _mapController.centerOnRoutes(routes: routes); print('Calculated ${routes.length} routes'); } else if (err == GemError.cancel) { print('Route calculation cancelled'); } else { print('Route calculation error: $err'); } } ); // Optional: Cancel route calculation // RoutingService.cancelRoute(_routingHandler!); // Helper functions String _formatDistance(int meters) { if (meters >= 1000) { return '${(meters / 1000).toStringAsFixed(1)} km'; } return '$meters m'; } String _formatDuration(int seconds) { final hours = seconds ~/ 3600; final minutes = (seconds % 3600) ~/ 60; return hours > 0 ? '$hours h $minutes min' : '$minutes min'; } ``` ### Text and Location Search Search for landmarks, addresses, and points of interest with fuzzy matching and proximity ranking. ```dart // Perform text search near a specific location Future> searchLandmarks(String searchText) async { final searchCoordinates = Coordinates(latitude: 48.864716, longitude: 2.349014); // Configure search preferences final preferences = SearchPreferences( maxMatches: 40, allowFuzzyResults: true, ); final completer = Completer>(); // Execute search with SDK SearchService.search( searchText, searchCoordinates, preferences: preferences, (err, results) async { if (err != GemError.success) { print('Search error: $err'); completer.complete([]); return; } if (results.isEmpty) { print('No results found for: $searchText'); } else { print('Found ${results.length} results'); // Results are sorted by relevance and distance } completer.complete(results); } ); return completer.future; } // Display search result on map void displayLandmarkOnMap(Landmark landmark) { // Highlight the landmark _mapController.activateHighlight( [landmark], renderSettings: HighlightRenderSettings() ); // Center map on landmark with appropriate zoom _mapController.centerOnCoordinates( landmark.coordinates, zoomLevel: 70 ); // Optional: Save to history var historyStore = LandmarkStoreService.getLandmarkStoreByName("History"); historyStore ??= LandmarkStoreService.createLandmarkStore("History"); historyStore.addLandmark(landmark); } // Access landmark properties void displayLandmarkInfo(Landmark landmark) { print('Name: ${landmark.name}'); print('Address: ${landmark.getAddress()}'); print('Coordinates: ${landmark.coordinates.latitude}, ${landmark.coordinates.longitude}'); print('Categories: ${landmark.getCategories()}'); // Display landmark image if available if (landmark.img.isValid) { final imageBytes = landmark.img.getRenderableImageBytes(size: Size(50, 50)); // Use imageBytes in Image.memory widget } } ``` ### Turn-by-Turn Navigation Start navigation with real-time instruction updates, voice guidance, and comprehensive event handling. ```dart // Start navigation on a calculated route void startNavigation(Route route) { // Request location permissions first (see positioning setup below) PositionService.setLiveDataSource(); // Define navigation event handlers void navigationInstructionUpdated( NavigationInstruction instruction, Set events ) { // Handle instruction updates for (final event in events) { switch (event) { case NavigationInstructionUpdateEvents.nextTurnUpdated: print('Next turn updated'); break; case NavigationInstructionUpdateEvents.nextTurnImageUpdated: print('Turn image updated'); break; case NavigationInstructionUpdateEvents.laneInfoUpdated: print('Lane information updated'); break; } } // Get instruction details final instructionText = instruction.nextTurnInstruction; final distanceToTurn = instruction.getDistanceToNextTurn(); final turnAngle = instruction.getNextTurnAngle(); print('Next instruction: $instructionText'); print('Distance to turn: ${distanceToTurn}m'); // Update UI with instruction } void onTextToSpeech(String text) { // Pass to TTS engine for voice guidance print('TTS: $text'); // Example: await flutterTts.speak(text); } void onWaypointReached(Landmark waypoint) { print('Reached waypoint: ${waypoint.name}'); } void onDestinationReached(Landmark destination) { print('Destination reached: ${destination.name}'); _mapController.stopFollowingPosition(); } void onRouteUpdated(Route newRoute) { print('Route updated (recalculated due to deviation)'); // Display updated route on map } void onBetterRouteDetected(Route betterRoute, int travelTime, int delay, int timeGain) { print('Better route detected - saves ${timeGain}s'); // Optionally switch to better route } void onError(GemError err) { print('Navigation error: $err'); } // Start navigation with comprehensive callbacks TaskHandler? _navigationHandler = NavigationService.startNavigation( route, onNavigationInstruction: navigationInstructionUpdated, onNavigationStarted: () => print('Navigation started'), onTextToSpeechInstruction: onTextToSpeech, onWaypointReached: onWaypointReached, onDestinationReached: onDestinationReached, onRouteUpdated: onRouteUpdated, onBetterRouteDetected: onBetterRouteDetected, onTurnAround: () => print('Please turn around'), onError: onError, ); // Set camera to follow position during navigation _mapController.startFollowingPosition(); // Stop navigation at any time // NavigationService.cancelNavigation(_navigationHandler); } // Alternative: Start navigation simulation (no GPS required) void startSimulation(Route route) { TaskHandler? _simulationHandler = NavigationService.startSimulation( route, onNavigationInstruction: (instruction, events) { print('Simulation instruction: ${instruction.nextTurnInstruction}'); }, speedMultiplier: 2.0, // 2x speed simulation ); _mapController.startFollowingPosition(); } ``` ### Location Services and Positioning Configure GPS-based or custom positioning data sources for location tracking and navigation. ```dart import 'package:permission_handler/permission_handler.dart'; // Setup location services with permissions Future setupLocationServices() async { // Request location permissions final permissionStatus = await Permission.locationWhenInUse.request(); if (permissionStatus == PermissionStatus.granted) { // Use device GPS PositionService.setLiveDataSource(); print('GPS location enabled'); } else if (permissionStatus == PermissionStatus.denied) { print('Location permission denied'); // Fallback to custom positioning or show error } else if (permissionStatus == PermissionStatus.permanentlyDenied) { // Guide user to app settings openAppSettings(); } // Optional: Request background location (Android/iOS) // await Permission.locationAlways.request(); } // Display user location on map void showUserLocationOnMap() { // Enable position marker on map final positionMarker = _mapController.preferences.positionIndicator; positionMarker.enable(true); // Customize position marker appearance (optional) // positionMarker.setIcon(customIcon); // Start following user position _mapController.startFollowingPosition(); // Optional: Get current position final currentPosition = PositionService.getCurrentPosition(); if (currentPosition != null) { print('Current location: ${currentPosition.coordinates.latitude}, ${currentPosition.coordinates.longitude}'); print('Speed: ${currentPosition.speed} m/s'); print('Heading: ${currentPosition.heading} degrees'); print('Accuracy: ${currentPosition.accuracy} meters'); } } // Custom positioning with mock data (testing/simulation) void setupCustomPositioning() { // Create custom positions final customPositions = [ GemPosition( coordinates: Coordinates(latitude: 48.858844, longitude: 2.294351), speed: 0, heading: 0, accuracy: 5, timestamp: DateTime.now().millisecondsSinceEpoch, ), GemPosition( coordinates: Coordinates(latitude: 48.860000, longitude: 2.295000), speed: 10, heading: 45, accuracy: 5, timestamp: DateTime.now().millisecondsSinceEpoch + 1000, ), ]; // Create and configure custom data source final dataSource = DataSource(); for (final position in customPositions) { dataSource.addPosition(position); } // Set custom data source PositionService.setCustomDataSource(dataSource); // Start data source dataSource.start(); } ``` ### Alarm Services (Speed, Boundaries, Events) Monitor speed limits, geographic boundaries, tunnels, and other events during navigation. ```dart // Setup comprehensive alarm monitoring void setupAlarmServices() { // Create alarm listener with callbacks final alarmListener = AlarmListener( onHighSpeed: (limit, insideCityArea) { print('High speed detected! Limit: $limit km/h, In city: $insideCityArea'); // Show speed warning to user }, onSpeedLimit: (currentSpeed, limit, insideCityArea) { print('Speed: $currentSpeed km/h, Limit: $limit km/h'); // Update speed display in UI }, onBoundaryCrossed: (enteredAreas, exitedAreas) { print('Crossed ${enteredAreas.length} boundaries (entered)'); print('Exited ${exitedAreas.length} boundaries'); // Handle geographic boundary events }, onLandmarkInRange: (landmarks) { print('${landmarks.length} landmarks nearby'); // Alert user to nearby POIs }, onTunnelEntered: () { print('Entered tunnel'); // Switch to tunnel mode UI }, onTunnelExited: () { print('Exited tunnel'); // Restore normal UI }, onDayNightChange: (isNight) { print('Changed to ${isNight ? "night" : "day"} mode'); // Switch map style if (isNight) { _mapController.setMapStyle(MapStyle.night); } else { _mapController.setMapStyle(MapStyle.day); } }, onTrafficEvent: (trafficEvents) { print('Traffic events detected: ${trafficEvents.length}'); for (final event in trafficEvents) { print('Event: ${event.description}, Severity: ${event.severity}'); } }, ); // Initialize alarm service final alarmService = AlarmService(alarmListener); // Configure speed alarm preferences final speedAlarmPrefs = alarmService.getSpeedAlarmPreferences(); speedAlarmPrefs.enableHighSpeedAlarm = true; speedAlarmPrefs.highSpeedThreshold = 10; // 10 km/h over limit alarmService.setSpeedAlarmPreferences(speedAlarmPrefs); // Add custom boundary alarm (circular area) final boundaryArea = GeographicArea.circle( center: Coordinates(latitude: 48.858844, longitude: 2.294351), radius: 1000, // 1km radius ); alarmService.addBoundaryAlarm(boundaryArea); } ``` ### Offline Map Management Download, update, and manage offline maps for entire countries or specific regions. ```dart // Download offline map for a country/region Future downloadOfflineMap(String countryName) async { // Get content store final contentStore = ContentStore.getInstance(); // Search for available map content final availableContent = await contentStore.getAvailableContent(); // Find specific country ContentStoreItem? countryItem; for (final item in availableContent) { if (item.name.toLowerCase().contains(countryName.toLowerCase())) { countryItem = item; break; } } if (countryItem == null) { print('Country not found: $countryName'); return; } print('Downloading ${countryItem.name}...'); print('Size: ${countryItem.size / 1024 / 1024} MB'); // Start download with progress monitoring final downloadTask = contentStore.downloadContent( countryItem, onProgress: (progress) { print('Download progress: ${(progress * 100).toStringAsFixed(1)}%'); // Update UI progress bar }, onComplete: (err) { if (err == GemError.success) { print('Download complete: ${countryItem!.name}'); } else { print('Download failed: $err'); } }, ); // Optional: Cancel download // contentStore.cancelDownload(downloadTask); } // Check for and install map updates Future checkMapUpdates() async { final contentStore = ContentStore.getInstance(); // Get list of installed maps final installedMaps = await contentStore.getInstalledContent(); for (final map in installedMaps) { if (map.hasUpdate) { print('Update available for ${map.name}'); print('Current version: ${map.version}'); print('New version: ${map.latestVersion}'); // Download update contentStore.updateContent( map, onProgress: (progress) { print('Update progress: ${(progress * 100).toStringAsFixed(1)}%'); }, onComplete: (err) { if (err == GemError.success) { print('Update complete: ${map.name}'); } }, ); } } } // Configure automatic update preferences void configureUpdatePreferences() { final contentStore = ContentStore.getInstance(); // Enable automatic updates over WiFi only contentStore.setAutoUpdateEnabled(true); contentStore.setWiFiOnlyDownloads(true); // Set update check frequency contentStore.setUpdateCheckInterval(Duration(days: 7)); } // Remove offline map content Future removeOfflineMap(ContentStoreItem mapItem) async { final contentStore = ContentStore.getInstance(); await contentStore.removeContent(mapItem); print('Removed offline map: ${mapItem.name}'); } ``` ### Weather Service Integration Retrieve current weather conditions and forecasts for specific locations. ```dart // Get current weather for a location Future getCurrentWeather(Coordinates location) async { WeatherService.getCurrent( coords: [location], onComplete: (err, forecasts) { if (err == GemError.success && forecasts.isNotEmpty) { final weather = forecasts.first; print('Weather at ${location.latitude}, ${location.longitude}'); print('Conditions: ${weather.conditions}'); print('Temperature: ${weather.temperature}°C'); print('Humidity: ${weather.humidity}%'); print('Wind speed: ${weather.windSpeed} m/s'); print('Wind direction: ${weather.windDirection}°'); print('Precipitation: ${weather.precipitation} mm'); // Display weather icon // final weatherIcon = weather.getIcon(); } else { print('Weather data unavailable: $err'); } }, ); } // Get hourly weather forecast Future getHourlyForecast(Coordinates location) async { WeatherService.getHourly( coords: [location], onComplete: (err, forecasts) { if (err == GemError.success) { print('${forecasts.length} hour forecast:'); for (final hourlyForecast in forecasts) { print('${hourlyForecast.timestamp}: ${hourlyForecast.temperature}°C, ${hourlyForecast.conditions}'); } } }, ); } // Get daily weather forecast Future getDailyForecast(Coordinates location, int days) async { WeatherService.getDaily( coords: [location], days: days, onComplete: (err, forecasts) { if (err == GemError.success) { print('$days day forecast:'); for (final dailyForecast in forecasts) { print('${dailyForecast.date}: High ${dailyForecast.maxTemperature}°C, Low ${dailyForecast.minTemperature}°C'); print(' Conditions: ${dailyForecast.conditions}'); } } }, ); } ``` ### Map Markers and Annotations Add custom markers, shapes, and annotations to display points of interest and routes on the map. ```dart // Add single marker to map void addMarkerToMap(Coordinates location, String label) { final marker = Marker( coordinates: location, name: label, ); // Optionally customize marker icon // marker.setIcon(customIcon); // Add to map _mapController.preferences.markers.add(marker); } // Add marker collection with custom icons void addMarkerCollection() { final markerCollection = MarkerCollection(); // Create markers final parisMarker = Marker( coordinates: Coordinates(latitude: 48.858844, longitude: 2.294351), name: 'Eiffel Tower', ); final londonMarker = Marker( coordinates: Coordinates(latitude: 51.5074, longitude: -0.1278), name: 'Big Ben', ); markerCollection.add(parisMarker); markerCollection.add(londonMarker); // Add collection to map _mapController.preferences.markers.addCollection(markerCollection); // Center map on markers _mapController.centerOnMarkers(markerCollection); } // Draw shapes (polygon, polyline, circle) void drawShapesOnMap() { // Draw polygon final polygonPath = Path([ Coordinates(latitude: 48.85, longitude: 2.29), Coordinates(latitude: 48.86, longitude: 2.29), Coordinates(latitude: 48.86, longitude: 2.30), Coordinates(latitude: 48.85, longitude: 2.30), ]); final polygon = Polygon( path: polygonPath, fillColor: Colors.red.withOpacity(0.3), strokeColor: Colors.red, strokeWidth: 2, ); _mapController.preferences.shapes.addPolygon(polygon); // Draw polyline final routePath = Path([ Coordinates(latitude: 48.858844, longitude: 2.294351), Coordinates(latitude: 48.860000, longitude: 2.295000), Coordinates(latitude: 48.861000, longitude: 2.296000), ]); final polyline = Polyline( path: routePath, color: Colors.blue, width: 3, ); _mapController.preferences.shapes.addPolyline(polyline); // Draw circle final circle = Circle( center: Coordinates(latitude: 48.858844, longitude: 2.294351), radius: 500, // meters fillColor: Colors.green.withOpacity(0.2), strokeColor: Colors.green, strokeWidth: 2, ); _mapController.preferences.shapes.addCircle(circle); } // Clear all markers and shapes void clearMapAnnotations() { _mapController.preferences.markers.clear(); _mapController.preferences.shapes.clear(); } ``` ### Map Interaction and Camera Control Control camera position, zoom level, rotation, and handle user gestures on the map. ```dart // Camera control methods void controlMapCamera() { // Center on coordinates with zoom level _mapController.centerOnCoordinates( Coordinates(latitude: 48.858844, longitude: 2.294351), zoomLevel: 80, // 0-100 range ); // Center on geographic area final area = GeographicArea.rectangle( topLeft: Coordinates(latitude: 48.90, longitude: 2.20), bottomRight: Coordinates(latitude: 48.80, longitude: 2.40), ); _mapController.centerOnArea(area); // Animate to location _mapController.animateToCoordinates( Coordinates(latitude: 51.5074, longitude: -0.1278), duration: Duration(seconds: 2), ); // Set bearing (rotation) _mapController.setBearing(45); // degrees // Set tilt (3D perspective) _mapController.setTilt(60); // degrees // Reset camera orientation _mapController.resetBearing(); _mapController.resetTilt(); } // Handle map gestures and interactions void setupMapInteractions() { // Register tap gesture _mapController.registerOnTouch((position) async { // Get coordinates at touch position final coords = _mapController.transformScreenToWgs(position); print('Tapped at: ${coords.latitude}, ${coords.longitude}'); // Select objects at position await _mapController.setCursorScreenPosition(position); // Get selected landmarks final landmarks = _mapController.cursorSelectionLandmarks(); if (landmarks.isNotEmpty) { print('Selected landmark: ${landmarks.first.name}'); } // Get selected routes final routes = _mapController.cursorSelectionRoutes(); if (routes.isNotEmpty) { print('Selected route'); _mapController.preferences.routes.mainRoute = routes.first; } }); // Enable/disable gestures _mapController.setGesturesEnabled(true); _mapController.setZoomGestureEnabled(true); _mapController.setRotationGestureEnabled(true); _mapController.setPanGestureEnabled(true); } // Follow position mode (for navigation) void configureFollowMode() { // Start following position _mapController.startFollowingPosition(); // Configure follow mode settings final followSettings = _mapController.getFollowPositionSettings(); followSettings.perspective = FollowPerspective.threeDimensional; followSettings.bearing = FollowBearing.heading; // Follow user heading _mapController.setFollowPositionSettings(followSettings); // Stop following position // _mapController.stopFollowingPosition(); } ``` ### Project Integration and Configuration Complete setup instructions for integrating the SDK into Flutter projects for Android and iOS platforms. ```yaml # pubspec.yaml - Add SDK dependency dependencies: flutter: sdk: flutter magiclane_maps_flutter: permission_handler: ^11.0.0 # For location permissions ``` ```kotlin // android/build.gradle.kts - Add Maven repository allprojects { repositories { google() mavenCentral() maven { url = uri("https://developer.magiclane.com/packages/android") } } } // android/app/build.gradle.kts - Disable code shrinking for release android { buildTypes { release { signingConfig = signingConfigs.getByName("debug") isMinifyEnabled = false isShrinkResources = false } } } ``` ```xml ``` ```xml NSLocationWhenInUseUsageDescription This app needs your location to provide navigation and map services. NSLocationAlwaysAndWhenInUseUsageDescription This app needs your location in the background for continuous navigation. CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) ``` ```bash # Installation commands flutter clean flutter pub get # For iOS, set minimum deployment target to 14.0 # In Xcode: Runner target > Build Settings > iOS Deployment Target = 14.0 # Run the app flutter run # Build release version flutter build apk --release # Android flutter build ios --release # iOS ``` ## Summary and Integration Patterns The Magic Lane Maps SDK for Flutter provides a complete mapping and navigation solution suitable for a wide range of mobile applications including delivery services, ride-sharing platforms, field service management, outdoor recreation apps, and location-based social applications. Core use cases include route planning with real-time traffic awareness, turn-by-turn navigation with voice guidance, location-based search and discovery, offline map functionality for areas with limited connectivity, and comprehensive monitoring systems for speed limits and geographic boundaries. The SDK's driver behavior analysis capabilities make it particularly valuable for fleet management applications, while the sensor data recording features enable detailed analytics and route optimization. Integration patterns follow a layered architecture where the GemMap widget serves as the primary UI component, with GemMapController providing programmatic access to map functionality. Services are accessed through static methods on specialized service classes (RoutingService, SearchService, NavigationService, etc.) with asynchronous operations using callback patterns and TaskHandler objects for cancellation support. The SDK supports both imperative control through direct API calls and reactive patterns through event listeners and callbacks. Permission handling for location services follows platform-specific patterns using the permission_handler package, while offline functionality is managed through the ContentStore API with support for automatic updates and WiFi-only downloads. Error handling uses the GemError enum for consistent error reporting across all services, and resource cleanup is critical with GemKit.release() required in widget disposal methods to prevent memory leaks.