================ LIBRARY RULES ================ - This project uses: flutter, dart, sdk # Magic Lane Maps SDK for Flutter Documentation ## Introduction The Magic Lane Maps SDK for Flutter (package: `magiclane_maps_flutter`) is a comprehensive mapping, routing, and navigation SDK that enables developers to build advanced location-based applications for Android and iOS platforms. The SDK leverages OpenStreetMap data to provide global coverage with offline-first capabilities, ensuring uninterrupted service even without internet connectivity. With features including 3D terrain topography, turn-by-turn navigation, voice guidance in multiple languages, and real-time traffic integration, Magic Lane transforms complex navigation requirements into simple, developer-friendly APIs. At its core, the SDK is designed to be lightweight and efficient, running smoothly even on lower-resource devices while maintaining high performance. It offers extensive customization options for map styles, routing preferences, and user interface elements, allowing developers to create branded experiences that seamlessly integrate with their applications. The SDK supports multiple transport modes (car, pedestrian, bicycle, truck) and provides advanced features such as landmark management, geofencing alarms, speed monitoring, and comprehensive sensor recording capabilities for activity tracking. ## SDK Initialization Initialize the SDK with an API token to enable all functionality. ```dart import 'package:flutter/material.dart' hide Route; 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: 'Hello Map', home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { @override void dispose() { GemKit.release(); 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 mapController) { // Map is ready for use } } ``` ## Manual SDK Initialization Initialize the SDK before displaying a map when operations are needed beforehand. ```dart // Manual initialization WidgetsFlutterBinding.ensureInitialized(); await GemKit.initialize(appAuthorization: projectApiToken); // Verify token validity SdkSettings.verifyAppAuthorization(token, (status) { switch (status) { case GemError.success: print('The token is set and is valid.'); break; case GemError.invalidInput: print('The token is invalid.'); break; case GemError.expired: print('The token is expired.'); break; case GemError.accessDenied: print('The token is blacklisted.'); break; default: print('Other error regarding token validation : $status.'); break; } }) ``` ## Map Display with GemMap Widget Display an interactive map with the GemMap widget and obtain a controller for map operations. ```dart class _MyHomePageState extends State { late GemMapController _mapController; @override void dispose() { GemKit.release(); 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 mapController) { _mapController = mapController; } } ``` ## Center Map on Coordinates Center the map on specific geographic coordinates with optional animation and zoom control. ```dart // Simple centering mapController.centerOnCoordinates(Coordinates(latitude: 45, longitude: 25)); // With animation controller.centerOnCoordinates( Coordinates(latitude: 52.14569, longitude: 1.0615), animation: GemAnimation(type: AnimationType.linear, duration: 2000)); // Center at specific screen position (1/3 from top) final physicalHeightPixels = mapController.viewport.height; final physicalWidthPixels = mapController.viewport.width; mapController.centerOnCoordinates( Coordinates(latitude: 52.48209, longitude: -2.48888), zoomLevel: 40, screenPosition: Point(physicalWidthPixels ~/ 2, physicalHeightPixels ~/ 3), ); ``` ## Center Map on Geographic Area Center the map to display a specific geographic area defined by boundary coordinates. ```dart final topLeftCoords = Coordinates(latitude: 44.93343, longitude: 25.09946); final bottomRightCoords = Coordinates(latitude: 44.93324, longitude: 25.09987); final area = RectangleGeographicArea(topLeft: topLeftCoords, bottomRight: bottomRightCoords); mapController.centerOnArea(area); ``` ## Coordinate Transformation Convert between screen pixels and WGS84 geographic coordinates. ```dart // Screen to WGS Coordinates coordsToCenter = mapController.transformScreenToWgs(Point(pos.x, pos.y)); mapController.centerOnCoordinates(coordsToCenter, zoomLevel: 70); // WGS to Screen Coordinates wgsCoordinates = Coordinates(latitude: 8, longitude: 25); Point screenPosition = mapController.transformWgsToScreen(wgsCoordinates); ``` ## Map Zoom Control Control the map camera zoom level with dynamic adjustments. ```dart final int zoomLevel = mapController.zoomLevel; mapController.setZoomLevel(50); // Check zoom limits final int maxZoom = mapController.maxZoomLevel; final int minZoom = mapController.minZoomLevel; // Check if zoom is possible if (mapController.canZoom(75)) { mapController.setZoomLevel(75); } ``` ## Map Rotation and View Angle Control the rotation angle and viewing perspective (tilt) of the map camera. ```dart // Set rotation angle (0-360 degrees, 0 = north-up) final double rotationAngle = mapController.preferences.mapAngle; mapController.preferences.mapAngle = 45; // Set view angle (tilt) final double viewAngle = mapController.preferences.viewAngle; mapController.preferences.setViewAngle(45); // 90° = top-down, lower values = more tilted // tiltAngle = 90 - viewAngle ``` ## Map Perspective and Building Visibility Switch between 2D and 3D map perspectives and control building rendering. ```dart // Set perspective final MapViewPerspective perspective = mapController.preferences.mapViewPerspective; mapController.preferences.setMapViewPerspective(MapViewPerspective.threeDimensional); // Control building visibility final BuildingsVisibility visibility = mapController.preferences.buildingsVisibility; mapController.preferences.buildingsVisibility = BuildingsVisibility.twoDimensional; // Options: defaultVisibility, hide, twoDimensional, threeDimensional ``` ## Map Gesture Recognition Register callbacks to handle user interactions including tap, long press, pan, and rotation. ```dart void _onMapCreated(GemMapController mapController) async { mapController = mapController; controller.registerOnMapAngleUpdate((angle) { print("Gesture: onMapAngleUpdate $angle"); }); controller.registerOnTouch((point) { print("Gesture: onTouch $point"); }); controller.registerOnMove((point1, point2) { print( 'Gesture: onMove from (${point1.x} ${point1.y}) to (${point2.x} ${point2.y})'); }); controller.registerOnLongPress((point) { print('Gesture: onLongPress $point'); }); controller.registerOnMapViewMoveStateChanged((hasStarted, rect) { if (hasStarted) { print( 'Gesture started at: ${rect.topLeft.toString()} , ${rect.bottomRight.toString()}'); } else { print( 'Gesture ended at: ${rect.topLeft.toString()} , ${rect.bottomRight.toString()}'); } }); } ``` ## Enable or Disable Gestures Selectively enable or disable specific touch gestures on the map. ```dart // Disable touch and move gestures mapController.preferences.enableTouchGestures([TouchGestures.onTouch, TouchGestures.onMove], false); // Check if gesture is enabled bool isTouchEnabled = mapController.preferences.isTouchGestureEnabled(TouchGestures.onTouch); ``` ## Landmark Selection Select landmarks on the map by tapping to interact with POIs and map features. ```dart mapController.registerOnTouch((pos) async { // Set the cursor position await mapController.setCursorScreenPosition(pos); // Get the landmarks at the cursor position final landmarks = mapController.cursorSelectionLandmarks(); for(final landmark in landmarks) { print("Selected landmark: ${landmark.name}"); // handle landmark } }); ``` ## Street Selection Select and retrieve street information under the cursor position. ```dart // Register touch callback to set cursor to tapped position mapController.registerOnTouch((point) async { await mapController.setCursorScreenPosition(point); final streets = mapController.cursorSelectionStreets(); String currentStreetName = streets.isEmpty ? "Unnamed street" : streets.first.name; print("Current street: $currentStreetName"); }); ``` ## Display Markers Add custom markers to the map with extensive customization including clustering for large datasets. ```dart Future addMarkers() async { final listPngs = await loadPngs(); final ByteData imageData = await rootBundle.load('assets/pois/GroupIcon.png'); final Uint8List imageBytes = imageData.buffer.asUint8List(); Random random = Random(); double minLat = 35.0; double maxLat = 71.0; double minLon = -10.0; double maxLon = 40.0; List markers = []; // Generate random coordinates for markers for (int i = 0; i < 8000; ++i) { double randomLat = minLat + random.nextDouble() * (maxLat - minLat); double randomLon = minLon + random.nextDouble() * (maxLon - minLon); final marker = MarkerJson( coords: [Coordinates(latitude: randomLat, longitude: randomLon)], name: "POI $i", ); final renderSettings = MarkerRenderSettings( image: GemImage( image: listPngs[random.nextInt(listPngs.length)], format: ImageFileFormat.png, ), labelTextSize: 2.0, ); final markerWithRenderSettings = MarkerWithRenderSettings( marker, renderSettings, ); markers.add(markerWithRenderSettings); } // Create the settings for the collections final settings = MarkerCollectionRenderSettings(); settings.labelGroupTextSize = 2; settings.pointsGroupingZoomLevel = 35; settings.image = GemImage(image: imageBytes, format: ImageFileFormat.png); // Add the markers and the settings on the map _mapController.preferences.markers.addList( list: markers, settings: settings, name: "Markers", ); } ``` ## Point Type Markers Display icon-based markers at specific locations with multiple parts. ```dart final marker = Marker() ..add(Coordinates(latitude: 52.1459, longitude: 1.0613), part: 0) ..add(Coordinates(latitude: 52.14569, longitude: 1.0615), part: 0) ..add(Coordinates(latitude: 52.14585, longitude: 1.06186), part: 1) ..add(Coordinates(latitude: 52.14611, longitude: 1.06215), part: 1); final markerCollection = MarkerCollection(markerType: MarkerType.point, name: "myCollection"); markerCollection.add(marker); mapController.preferences.markers.add(markerCollection); controller.centerOnArea(markerCollection.area); ``` ## Marker Customization Customize marker appearance with images, colors, sizes, and labels. ```dart import 'package:flutter/services.dart' show rootBundle; final ByteData imageData = await rootBundle.load('assets/poi83.png'); final Uint8List pngImage = imageData.buffer.asUint8List(); final renderSettings = MarkerCollectionRenderSettings( image: GemImage(image: pngImage, format: ImageFileFormat.png), labelingMode: { MarkerLabelingMode.itemLabelVisible, MarkerLabelingMode.textAbove, MarkerLabelingMode.iconBottomCenter } ); mapController.preferences.markers.add(markerCollection, settings: renderSettings); ``` ## Marker Sketches Customize individual markers with unique styles using MarkerSketches. ```dart final sketches = ctrl.preferences.markers.getSketches(MarkerType.point); final marker1 = Marker()..add(Coordinates(latitude: 39.76741, longitude: -46.8962)); marker1.name = "HelloMarker"; sketches.addMarker(marker1, settings: MarkerRenderSettings( labelTextColor: Colors.red, labelTextSize: 3.0, image: GemImage(imageId: GemIcon.toll.id)), index: 0); // Update existing marker's appearance sketches.setRenderSettings( 0, // marker index MarkerRenderSettings( labelTextColor: Colors.red, labelTextSize: 3.0, image: GemImage(imageId: GemIcon.toll.id)) ); ``` ## Get Current Location Access the device's current location with proper permission handling. ```dart // Request location permissions final locationPermissionStatus = await Permission.locationWhenInUse.request(); if (locationPermissionStatus == PermissionStatus.granted) { // After permission granted, set the live data source GemError setLiveDataSourceError = PositionService.setLiveDataSource(); showSnackbar("Set live datasource with result: $setLiveDataSourceError"); } if (locationPermissionStatus == PermissionStatus.denied) { showSnackbar("Location permission denied"); } if (locationPermissionStatus == PermissionStatus.permanentlyDenied) { showSnackbar("Location permission permanently denied"); } ``` ## Listen for Position Updates Register callbacks to receive continuous position updates with map-matched or raw GPS data. ```dart // Listen for map-matched positions PositionService.addImprovedPositionListener((GemImprovedPosition position) { // Current coordinates Coordinates coordinates = position.coordinates; print("New position: ${coordinates}"); // Speed in m/s (-1 if not available) double speed = position.speed; // Speed limit in m/s on the current road (0 if not available) double speedLimit = position.speedLimit; // Heading angle in degrees (N=0, E=90, S=180, W=270, -1 if not available) double course = position.course; // Information about current road Set roadModifiers = position.roadModifiers; // Quality of the current position PositionQuality fixQuality = position.fixQuality; // Horizontal and vertical accuracy in meters double accuracyHorizontal = position.accuracyH; double accuracyVertical = position.accuracyV; }); // Listen for raw position updates PositionService.addPositionListener((GemPosition position) { // Process the position }); ``` ## Get Current Position (One-Time) Retrieve the current location without registering for continuous updates. ```dart GemPosition? position = PositionService.position; if (position == null) { showSnackbar("No position"); } else { showSnackbar("Position: ${position.coordinates}"); } // For map-matched position GemImprovedPosition? improvedPosition = PositionService.improvedPosition; ``` ## Follow Position on Map Make the map camera automatically follow the user's current position. ```dart // Start following position mapController.startFollowingPosition(); // With custom settings final prefs = mapController.preferences.followPositionPreferences; prefs.setMapRotationMode(FollowPositionMapRotationMode.positionHeading); prefs.setAccuracyCircleVisibility(true); // Set camera focus position (2/3 from left, 3/5 from top) double twoThirdsX = 2 / 3; double threeFifthsY = 3 / 5; Point position = Point(twoThirdsX, threeFifthsY); GemError error = prefs.setCameraFocus(position); mapController.startFollowingPosition(); // Stop following position mapController.stopFollowingPosition(); ``` ## Customize Position Icon Change the appearance of the position tracker to match your app's design. ```dart // Read the file and load the image as a binary resource final imageByteData = (await rootBundle.load('assets/navArrow.png')); // Convert the binary data to Uint8List final imageUint8List = imageByteData.buffer.asUint8List(); // Customize the position tracker MapSceneObject.customizeDefPositionTracker(imageUint8List, SceneObjectFileFormat.tex); // Change scale MapSceneObject mapSceneObject = MapSceneObject.getDefPositionTracker(); mapSceneObject.scale = 0.5; // Change visibility mapSceneObject.visibility = false; ``` ## Text Search Search for landmarks, addresses, and POIs using text queries with geographic hints. ```dart const text = "Paris"; final coords = Coordinates(latitude: 45, longitude: 10); final preferences = SearchPreferences( maxMatches: 40, allowFuzzyResults: true, ); TaskHandler? taskHandler = SearchService.search( text, coords, preferences: preferences, (err, results) async { if (err == GemError.success) { if (results.isEmpty) { showSnackbar("No results"); } else { showSnackbar("Number of results: ${results.length}"); for (final landmark in results) { print("Found: ${landmark.name}"); } } } else { showSnackbar("Error: $err"); } }, ); ``` ## Search by Category Filter search results by predefined categories such as gas stations, parking, restaurants. ```dart const textFilter = "Paris"; final coords = Coordinates(latitude: 45, longitude: 10); final preferences = SearchPreferences( maxMatches: 40, allowFuzzyResults: true, searchMapPOIs: true, searchAddresses: false, ); final categories = GenericCategories.categories; final firstCategory = categories[0]; // Gas stations final secondCategory = categories[1]; // Parking preferences.landmarks.addStoreCategoryId( firstCategory.landmarkStoreId, firstCategory.id, ); preferences.landmarks.addStoreCategoryId( secondCategory.landmarkStoreId, secondCategory.id, ); TaskHandler? taskHandler = SearchService.searchAroundPosition( coords, textFilter: textFilter, preferences: preferences, (err, results) async { if (err == GemError.success) { if (results.isEmpty) { showSnackbar("No results"); } else { showSnackbar("Number of results: ${results.length}"); } } else { showSnackbar("Error: $err"); } }, ); ``` ## Search Around Position Find all landmarks near a specific location without a text query. ```dart final coords = Coordinates(latitude: 45, longitude: 10); final preferences = SearchPreferences( maxMatches: 40, allowFuzzyResults: true, ); TaskHandler? taskHandler = SearchService.searchAroundPosition( coords, preferences: preferences, (err, results) async { if (err == GemError.success) { if (results.isEmpty) { showSnackbar("No results"); } else { showSnackbar("Number of results: ${results.length}"); } } else { showSnackbar("Error: $err"); } }, ); ``` ## Search in Area Limit search results to a specific geographic area. ```dart final coords = Coordinates(latitude: 41.68905, longitude: -72.64296); final searchArea = RectangleGeographicArea( topLeft: Coordinates(latitude: 41.98846, longitude: -73.12412), bottomRight: Coordinates(latitude: 41.37716, longitude: -72.02342)); SearchService.search('N', coords, (err, result) { if (err == GemError.success) { print("Found ${result.length} results"); } }, preferences: SearchPreferences(maxMatches: 400), locationHint: searchArea, ); ``` ## Calculate Route Calculate routes between two or more waypoints with customizable preferences. ```dart // Define the departure final departureLandmark = Landmark.withLatLng(latitude: 48.85682, longitude: 2.34375); // Define the destination final destinationLandmark = Landmark.withLatLng(latitude: 50.84644, longitude: 4.34587); // Define the route preferences (all default) final routePreferences = RoutePreferences(); TaskHandler? taskHandler = RoutingService.calculateRoute( [departureLandmark, destinationLandmark], routePreferences, (err, routes) { if (err == GemError.success) { showSnackbar("Number of routes: ${routes.length}"); // Display routes on map final routesMap = _mapController.preferences.routes; for (final route in routes) { routesMap.add(route, route == routes.first, label: getMapLabel(route)); } // Center the camera on routes _mapController.centerOnRoutes(routes: routes); } else if (err == GemError.cancel) { showSnackbar("Route computation canceled"); } else { showSnackbar("Error: $err"); } }); // Cancel route calculation if needed // RoutingService.cancelRoute(taskHandler); ``` ## Get Route Time and Distance Retrieve estimated time of arrival (ETA) and distance information for calculated routes. ```dart TimeDistance td = route.getTimeDistance(activePart: false); final totalDistance = td.totalDistanceM; // same with: //final totalDistance = td.unrestrictedDistanceM + td.restrictedDistanceM; final totalDuration = td.totalTimeS; // same with: //final totalDuration = td.unrestrictedTimeS + td.restrictedTimeS; // Get remaining distance (for active navigation) TimeDistance remainTd = route.getTimeDistance(activePart: true); final totalRemainDistance = remainTd.totalDistanceM; final totalRemainDuration = remainTd.totalTimeS; // Helper functions String convertDistance(int meters) { if (meters >= 1000) { double kilometers = meters / 1000; return '${kilometers.toStringAsFixed(1)} km'; } else { return '${meters.toString()} m'; } } String convertDuration(int seconds) { int hours = seconds ~/ 3600; int minutes = (seconds % 3600) ~/ 60; String hoursText = (hours > 0) ? '$hours h ' : ''; String minutesText = '$minutes min'; return hoursText + minutesText; } ``` ## Get Traffic Information Access real-time traffic events and conditions along the calculated route. ```dart List trafficEvents = route.trafficEvents; for (final event in trafficEvents) { RouteTransportMode transportMode = event.affectedTransportModes; String description = event.description; TrafficEventClass eventClass = event.eventClass; TrafficEventSeverity eventSeverity = event.eventSeverity; Coordinates from = event.from; Coordinates to = event.to; bool isRoadBlock = event.isRoadblock; print("Traffic event: $description at ${from.latitude}, ${from.longitude}"); } ``` ## Route Terrain Profile Get elevation and terrain information for a route, useful for cycling and hiking applications. ```dart // Enable terrain profile in route preferences final routePreferences = RoutePreferences( buildTerrainProfile: const BuildTerrainProfile(enable: true), ); // Calculate route with terrain profile RoutingService.calculateRoute([departure, destination], routePreferences, (err, routes) { if (err == GemError.success && routes.isNotEmpty) { RouteTerrainProfile? terrainProfile = routes.first.terrainProfile; if (terrainProfile != null) { double minElevation = terrainProfile.minElevation; double maxElevation = terrainProfile.maxElevation; int minElevDist = terrainProfile.minElevationDistance; int maxElevDist = terrainProfile.maxElevationDistance; double totalUp = terrainProfile.totalUp; double totalDown = terrainProfile.totalDown; // elevation at 100m from the route start double elevation = terrainProfile.getElevation(100); for (final section in terrainProfile.roadTypeSections) { RoadType roadType = section.type; int startDistance = section.startDistanceM; } for (final section in terrainProfile.surfaceSections) { SurfaceType surfaceType = section.type; int startDistance = section.startDistanceM; } for (final section in terrainProfile.climbSections) { Grade grade = section.grade; double slope = section.slope; int startDistanceM = section.startDistanceM; int endDistanceM = section.endDistanceM; } } } }); ``` ## Route Instructions Get turn-by-turn navigation instructions for a calculated route. ```dart Route route = routes.first; List segments = route.segments; for (final segment in segments) { List instructions = segment.instructions; for (final instruction in instructions) { // Basic instruction information String turnText = instruction.turnInstruction; String followRoadText = instruction.followRoadInstruction; Coordinates location = instruction.coordinates; // Time and distance information TimeDistance traveled = instruction.traveledTimeDistance; TimeDistance remaining = instruction.remainingTravelTimeDistance; TimeDistance toNextTurn = instruction.timeDistanceToNextTurn; // Road information List roadInfo = instruction.roadInfo; // Turn details and images TurnDetails? turnDetails = instruction.turnDetails; if (turnDetails != null && turnDetails.abstractGeometryImg.isValid) { Uint8List? imgBytes = turnDetails.abstractGeometryImg.getRenderableImageBytes( renderSettings: AbstractGeometryImageRenderSettings(), size: Size(100, 100) ); } print("Turn: $turnText, Follow: $followRoadText"); } } ``` ## Start Navigation Begin turn-by-turn navigation on a calculated route using real GPS data. ```dart void navigationInstructionUpdated(NavigationInstruction instruction, Set events) { for (final event in events) { switch (event) { case NavigationInstructionUpdateEvents.nextTurnUpdated: showSnackbar("Turn updated"); break; case NavigationInstructionUpdateEvents.nextTurnImageUpdated: showSnackbar("Turn image updated"); break; case NavigationInstructionUpdateEvents.laneInfoUpdated: showSnackbar("Lane info updated"); break; } } final instructionText = instruction.nextTurnInstruction; // Update UI with instruction } void onDestinationReached(Landmark destination) { showSnackbar("Destination reached!"); } void onError(GemError err) { showSnackbar("Navigation error: $err"); } TaskHandler? handler = NavigationService.startNavigation(route, onNavigationInstruction: navigationInstructionUpdated, onDestinationReached: onDestinationReached, onError: onError); // Set the camera to follow position mapController.startFollowingPosition(); // Cancel navigation at any time // NavigationService.cancelNavigation(taskHandler); ``` ## Start Simulation Simulate navigation along a route without requiring real GPS data. ```dart void simulationInstructionUpdated(NavigationInstruction instruction, Set events) { for (final event in events) { switch (event) { case NavigationInstructionUpdateEvents.nextTurnUpdated: showSnackbar("Turn updated"); break; case NavigationInstructionUpdateEvents.nextTurnImageUpdated: showSnackbar("Turn image updated"); break; case NavigationInstructionUpdateEvents.laneInfoUpdated: showSnackbar("Lane info updated"); break; } } final instructionText = instruction.nextTurnInstruction; // Update UI with instruction } mapController.preferences.routes.add(route, true); TaskHandler? taskHandler = NavigationService.startSimulation( route, onNavigationInstruction: simulationInstructionUpdated, speedMultiplier: 2, // 2x speed ); // Set the camera to follow position mapController.startFollowingPosition(); // Cancel simulation // NavigationService.cancelNavigation(taskHandler); ``` ## Navigation Events Listen to various navigation events including waypoint reached, route updates, and better route detection. ```dart void onNavigationInstruction(NavigationInstruction navigationInstruction, Set events) { print("New instruction: ${navigationInstruction.nextTurnInstruction}"); } void onNavigationStarted() { print("Navigation started"); } void onTextToSpeechInstruction(String text) { print("TTS: $text"); // Pass to TTS engine } void onWaypointReached(Landmark landmark) { print("Waypoint reached: ${landmark.name}"); } void onDestinationReached(Landmark landmark) { print("Destination reached: ${landmark.name}"); } void onRouteUpdated(Route route) { print("Route updated"); } void onBetterRouteDetected( Route route, int travelTime, int delay, int timeGain) { print("Better route detected! Time gain: $timeGain seconds"); } void onBetterRouteRejected(GemError error) { print("Better route check failed: $error"); } void onBetterRouteInvalidated() { print("Better route no longer valid"); } void onSkipNextIntermediateDestinationDetected() { print("Skip waypoint suggested"); // Optionally skip: NavigationService.skipNextIntermediateDestination(); } void onTurnAround() { print("Turn around!"); } void onRouteCalculationStarted() { print("Recalculating route..."); } void onRouteCalculationCompleted(GemError error) { if (error == GemError.success) { print("Route recalculation completed"); } else { print("Route recalculation failed: $error"); } } TaskHandler? taskHandler = NavigationService.startNavigation( route, onNavigationInstruction: onNavigationInstruction, onNavigationStarted: onNavigationStarted, onTextToSpeechInstruction: onTextToSpeechInstruction, onWaypointReached: onWaypointReached, onDestinationReached: onDestinationReached, onRouteUpdated: onRouteUpdated, onBetterRouteDetected: onBetterRouteDetected, onBetterRouteRejected: onBetterRouteRejected, onBetterRouteInvalidated: onBetterRouteInvalidated, onSkipNextIntermediateDestinationDetected: onSkipNextIntermediateDestinationDetected, onTurnAround: onTurnAround, onRouteCalculationStarted: onRouteCalculationStarted, onRouteCalculationCompleted: onRouteCalculationCompleted, ); ``` ## Apply Predefined Map Styles Download and apply various predefined map styles to customize the map's appearance. ```dart List _stylesList = []; void getStyles() { ContentStore.asyncGetStoreContentList(ContentType.viewStyleLowRes, (err, items, isCached) { if (err == GemError.success && items.isNotEmpty) { for (final item in items) { _stylesList.add(item); } } }); } Future _downloadStyle(ContentStoreItem style) async { Completer completer = Completer(); style.asyncDownload((err) { if (err != GemError.success) { completer.complete(false); return; } completer.complete(true); }, onProgress: (progress) { print('progress: $progress'); }, allowChargedNetworks: true); return await completer.future; } // Apply the style void applyStyle() async { if (_stylesList.isEmpty) { getStyles(); return; } ContentStoreItem currentStyle = _stylesList[0]; if (currentStyle.isCompleted == false) { final didDownloadSuccessfully = await _downloadStyle(currentStyle); if (didDownloadSuccessfully == false) return; } final String filename = currentStyle.fileName; mapController.preferences.setMapStyleByPath(filename); } ``` ## Apply Custom Map Styles Load and apply custom map styles created in Magic Lane Map Studio. ```dart // Method to load style and return it as bytes Future _loadStyle() async { // Load style into memory final data = await rootBundle.load('assets/Basic_1_Oldtime-1_21_656.style'); // Convert it to Uint8List final bytes = data.buffer.asUint8List(); return bytes; } // Apply custom style void applyCustomStyle() async { final styleData = await _loadStyle(); mapController.preferences .setMapStyleByBuffer(styleData, smoothTransition: true); } // Or apply style at map initialization GemMap( appAuthorization: projectApiToken, initialMapStyleAsset: "assets/map-styles/my_map_style.style", ), ``` ## Download Offline Maps Download map regions for offline use, enabling full functionality without internet connection. ```dart // List available offline content final ProgressListener? listener = ContentStore.asyncGetStoreContentList( ContentType.roadMap, (err, items, isCached) { if (err != GemError.success){ showSnackbar("Failed to get list of content store items: $err"); } else { // Process items for (final item in items) { print("Available map: ${item.name}, Size: ${item.totalSize} bytes"); } } } ); // Download a specific map region void downloadMap(ContentStoreItem mapItem) { mapItem.asyncDownload( (error) { if (error == GemError.success) { showSnackbar("Map download completed"); } else { showSnackbar("Download failed: $error"); } }, onProgress: (progress){ print("Download progress: ${progress.toString()} / 100"); }, ); } // Get local content final List localItems = ContentStore.getLocalContentList(ContentType.roadMap); // Delete downloaded content if (contentStoreItem.canDeleteContent){ final error = contentStoreItem.deleteContent(); showSnackbar("Item ${contentStoreItem.name} deletion resulted with code $error"); } ``` ## Filter Available Content Get a filtered list of downloadable maps by country codes or geographic area. ```dart final contentStoreItemListCompleter = Completer?>(); ContentStore.asyncGetStoreFilteredList( type: ContentType.roadMap, area: RectangleGeographicArea( topLeft: Coordinates(latitude: 53.7731, longitude: -1.7990), bottomRight: Coordinates(latitude: 38.4549, longitude: 21.1696)), onComplete: (err, result) { contentStoreItemListCompleter.complete(result); }); final res = await contentStoreItemListCompleter.future; ``` ## Download Map Tiles Download individual map tiles for caching, primarily for visual display purposes. ```dart final service = MapDownloaderService(); final completer = Completer(); service.setMaxSquareKm = 300; service.startDownload([ // Area in which the tiles will be downloaded that is under 300 square kilometers RectangleGeographicArea( topLeft: Coordinates(latitude: 67.69866, longitude: 24.81115), bottomRight: Coordinates(latitude: 67.58326, longitude: 25.36093)) ], (err) { completer.complete(err); }); final res = await completer.future; if (res == GemError.success) { print("Tiles downloaded successfully"); } // Cancel download // service.cancelDownload(); ``` ## Alarm Service Monitor and receive notifications for various events like speed limits, boundary crossings, and landmark approaches. ```dart // Create alarm listener and specify callbacks AlarmListener alarmListener = AlarmListener( onBoundaryCrossed: (entered, exited) { print("Boundary crossed"); }, onMonitoringStateChanged: (isMonitoringActive) { print("Monitoring state: $isMonitoringActive"); }, onTunnelEntered: () { print("Entering tunnel"); }, onTunnelLeft: () { print("Exiting tunnel"); }, onLandmarkAlarmsUpdated: () { print("Landmark alarms updated"); }, onOverlayItemAlarmsUpdated: () { print("Overlay item alarms updated"); }, onLandmarkAlarmsPassedOver: () { print("Passed landmark alarm"); }, onOverlayItemAlarmsPassedOver: () { print("Passed overlay item alarm"); }, onHighSpeed: (limit, insideCityArea) { print("High speed! Limit: $limit, In city: $insideCityArea"); }, onSpeedLimit: (speed, limit, insideCityArea) { print("Speed: $speed, Limit: $limit, In city: $insideCityArea"); }, onNormalSpeed: (limit, insideCityArea) { print("Normal speed"); }, onEnterDayMode: () { print("Day mode"); }, onEnterNightMode: () { print("Night mode"); }, ); // Create alarm service based on the listener AlarmService alarmService = AlarmService(alarmListener); // Update listener at any time AlarmListener newAlarmListener = AlarmListener(); alarmService.alarmListener = newAlarmListener; // Override specific callbacks alarmListener.registerOnEnterDayMode(() { showSnackbar("Day mode entered"); }); ``` ## Create and Manage Landmark Stores Landmark stores are persistent collections of landmarks for search, display, and alarm monitoring. ```dart // Create a new landmark store LandmarkStore landmarkStore = LandmarkStoreService.createLandmarkStore('MyLandmarkStore'); // Create landmarks Landmark landmark1 = Landmark() ..coordinates = Coordinates(latitude: 25, longitude: 30) ..name = "My Custom Landmark1"; Landmark landmark2 = Landmark() ..coordinates = Coordinates(latitude: 25.005, longitude: 30.005) ..name = "My Custom Landmark2"; // Add landmarks to the store landmarkStore.addLandmark(landmark1); landmarkStore.addLandmark(landmark2); // Get landmark by id Landmark? retrievedLandmark = landmarkStore.getLandmark(landmark1.id); // Update landmark landmark1.name = "Updated Name"; landmarkStore.updateLandmark(landmark1); // Get all landmarks List allLandmarks = landmarkStore.getLandmarks(); // Remove landmark landmarkStore.removeLandmark(landmark1); // Get store by name LandmarkStore? storeByName = LandmarkStoreService.getLandmarkStoreByName('MyLandmarkStore'); // Get store by id LandmarkStore? storeById = LandmarkStoreService.getLandmarkStoreById(landmarkStore.id); // Get all stores List allStores = LandmarkStoreService.landmarkStores; // Remove store int landmarkStoreId = landmarkStore.id; landmarkStore.dispose(); LandmarkStoreService.removeLandmarkStore(landmarkStoreId); ``` ## Import Landmarks from Files Import landmarks from KML, GeoJSON, or other supported file formats. ```dart ProgressListener? listener = landmarkStore.importLandmarks( filePath: '/path/to/file.kml', format: LandmarkFileFormat.kml, image: landmarkImage, onComplete: (GemError error) { if (error == GemError.success) { showSnackbar("Import successful"); } else { showSnackbar("Import failed: $error"); } }, categoryId: yourCategoryId, ); // Import from data buffer ProgressListener? listener2 = landmarkStore.importLandmarksWithDataBuffer( buffer: fileBytes, format: LandmarkFileFormat.geoJson, image: landmarkImage, onComplete: (GemError error) { if (error == GemError.success) { showSnackbar("Import successful"); } else { showSnackbar("Import failed: $error"); } }, categoryId: yourCategoryId, ); ``` ## Browse Landmark Stores Efficiently browse large landmark stores with filtering and sorting options. ```dart LandmarkBrowseSession browseSession = landmarkStore.createLandmarkBrowseSession( settings: LandmarkBrowseSessionSettings( descendingOrder: false, orderBy: LandmarkOrder.name, nameFilter: "cafe", categoryIdFilter: LandmarkStore.invalidLandmarkCategId, coordinates: Coordinates(latitude: 45, longitude: 10), ), ); // Get total count int totalCount = browseSession.landmarkCount; // Get landmarks by range List landmarks = browseSession.getLandmarks(0, 10); // First 10 // Get landmark position in session int position = browseSession.getLandmarkPosition(landmarkId); ``` ## Summary The Magic Lane Maps SDK for Flutter provides a comprehensive solution for building location-based applications with minimal complexity. The SDK excels in scenarios requiring offline functionality, making it ideal for delivery services, fleet management, tourism apps, and outdoor navigation applications where internet connectivity may be unreliable. Its lightweight architecture ensures smooth performance across all device categories, while the extensive API coverage allows developers to implement everything from simple map displays to complex multi-stop navigation with real-time traffic awareness. The SDK's marker clustering capabilities efficiently handle thousands of POIs, and the built-in alarm service enables sophisticated geofencing and speed monitoring features without requiring third-party integrations. Integration patterns typically follow a progressive enhancement approach: starting with basic map display and positioning, then layering search capabilities, route calculation, and finally full turn-by-turn navigation. The SDK's modular design allows developers to adopt only the features they need, reducing app size and complexity. Cross-platform consistency between Android and iOS ensures that business logic and UI components can be shared effectively, while platform-specific configurations remain minimal and well-documented. The offline-first architecture, combined with comprehensive error handling and progress monitoring APIs, enables developers to build robust applications that gracefully handle network transitions and provide consistent user experiences regardless of connectivity status.