# Magic Lane Maps SDK for Android The Magic Lane Maps SDK for Android is a comprehensive mapping and navigation library that enables developers to integrate interactive maps, location-based search, turn-by-turn navigation, and offline mapping capabilities into Android applications. The SDK provides a complete solution for building navigation apps with features including real-time GPS positioning, route calculation, voice guidance, traffic information, and downloadable offline map content. The SDK is built around core components including `GemSurfaceView` for map rendering, `SearchService` for location search, `RoutingService` for route calculation, and `NavigationService` for turn-by-turn navigation. It supports both Android Views and Jetpack Compose architectures, with comprehensive APIs for customizing map appearance, handling user interactions, managing offline content, and integrating with device sensors. ## SDK Initialization Initialize the Magic Lane SDK with default settings in your Android application. ```kotlin import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.magiclane.sdk.core.GemSdk import com.magiclane.sdk.core.SdkSettings class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Set callback for API token rejection SdkSettings.onApiTokenRejected = { Toast.makeText(this, "TOKEN REJECTED", Toast.LENGTH_LONG).show() } // Initialize SDK with default parameters if (!GemSdk.initSdkWithDefaults(this)) { // SDK initialization failed finish() } } override fun onDestroy() { super.onDestroy() GemSdk.release() } } ``` ## GemSurfaceView - Display Map The primary component for rendering maps with built-in touch interaction handling and automatic SDK initialization. ```xml ``` ```kotlin // Configure GemSurfaceView programmatically val gemSurfaceView = findViewById(R.id.gem_surface) gemSurfaceView.onDefaultMapViewCreated = { mapView -> // Configure map view when ready mapView.centerOnCoordinates(Coordinates(48.85682, 2.34375), zoomLevel = 50) } gemSurfaceView.onSdkInitSucceeded = { Log.d("Map", "SDK initialized successfully") } // Access the MapView val mapView = gemSurfaceView.mapView ``` ## MapView - Center and Adjust Map Control map viewport, zoom levels, rotation angles, and centering behavior with the MapView class. ```kotlin val gemSurfaceView = findViewById(R.id.gem_surface) val mapView = gemSurfaceView.mapView // Center on coordinates with animation mapView?.centerOnCoordinates( Coordinates(52.14569, 1.0615), zoomLevel = 50, animation = Animation(EAnimation.Linear, 2000) ) // Center at specific screen position (one-third from top) val physicalHeight = mapView?.viewport?.height ?: 0 val physicalWidth = mapView?.viewport?.width ?: 0 mapView?.centerOnCoordinates( Coordinates(52.48209, -2.48888), zoomLevel = 40, xy = Xy(physicalWidth / 2, physicalHeight / 3) ) // Center on geographic area val topLeft = Coordinates(44.93343, 25.09946) val bottomRight = Coordinates(44.93324, 25.09987) val area = RectangleGeographicArea(topLeft, bottomRight) mapView?.centerOnArea(area) // Set zoom level and rotation mapView?.setZoomLevel(70) mapView?.preferences?.rotationAngle = 45.0 mapView?.preferences?.setViewAngle(60) // Set 3D perspective mapView?.preferences?.setMapViewPerspective(EMapViewPerspective.ThreeDimensional) mapView?.preferences?.buildingsVisibility = EBuildingsVisibility.ThreeDimensional // Convert between screen and WGS coordinates val wgsCoords = mapView?.transformScreenToWgs(Xy(100, 200)) val screenPos = mapView?.transformWgsToScreen(Coordinates(48.0, 2.0)) ``` ## SearchService - Location Search Search for locations using text queries, coordinates, and category filters with customizable preferences. ```kotlin import com.magiclane.sdk.places.SearchService import com.magiclane.sdk.places.SearchPreferences import com.magiclane.sdk.core.Coordinates import com.magiclane.sdk.core.GemError // Basic text search val preferences = SearchPreferences().apply { maxMatches = 40 allowFuzzyResults = true searchMapPOIsEnabled = true searchAddressesEnabled = true } val searchService = SearchService( preferences = preferences, onCompleted = { results, errorCode, hint -> when (errorCode) { GemError.NoError -> { if (results.isEmpty()) { Log.d("Search", "No results found") } else { results.forEach { landmark -> Log.d("Search", "Found: ${landmark.name} at ${landmark.coordinates}") } } } GemError.Cancel -> Log.d("Search", "Search cancelled") GemError.NetworkTimeout -> Log.e("Search", "Network timeout") else -> Log.e("Search", "Error: ${GemError.getMessage(errorCode)}") } } ) // Search by text filter near reference coordinates searchService.searchByFilter("Paris", Coordinates(45.0, 10.0)) // Search by category (gas stations only) searchService.searchByFilter( "Paris", Coordinates(45.0, 10.0), arrayListOf(EGenericCategoriesIDs.GasStation) ) // Search nearby without text filter searchService.searchAroundPosition(Coordinates(45.0, 10.0)) // Search within a specific area val searchArea = RectangleGeographicArea( Coordinates(41.98846, -73.12412), Coordinates(41.37716, -72.02342) ) val areaSearchService = SearchService( preferences = SearchPreferences().apply { maxMatches = 400 }, locationHint = searchArea, onCompleted = { results, errorCode, _ -> /* handle results */ } ) areaSearchService.searchByFilter("N", Coordinates(41.68905, -72.64296)) ``` ## RoutingService - Calculate Routes Calculate navigable routes between waypoints with customizable preferences, terrain profiles, and traffic information. ```kotlin import com.magiclane.sdk.routesandnavigation.RoutingService import com.magiclane.sdk.routesandnavigation.RoutePreferences import com.magiclane.sdk.places.Landmark import com.magiclane.sdk.core.Coordinates // Define waypoints val departureLandmark = Landmark().apply { coordinates = Coordinates(48.85682, 2.34375) // Paris } val destinationLandmark = Landmark().apply { coordinates = Coordinates(50.84644, 4.34587) // Brussels } // Configure route preferences val routePreferences = RoutePreferences().apply { buildTerrainProfile = true // Enable terrain profile } val waypoints = arrayListOf(departureLandmark, destinationLandmark) // Create routing service val routingService = RoutingService( preferences = routePreferences, onCompleted = { routes, errorCode, hint -> when (errorCode) { GemError.Success -> { Log.d("Routing", "Found ${routes.size} routes") routes.firstOrNull()?.let { route -> // Get time and distance val td = route.getTimeDistance(activePart = false) Log.d("Routing", "Distance: ${td?.totalDistance}m, Duration: ${td?.totalTime}s") // Get traffic events route.trafficEvents?.forEach { event -> Log.d("Routing", "Traffic: ${event.description}") } // Get terrain profile route.terrainProfile?.let { profile -> Log.d("Routing", "Elevation: ${profile.minElevation}m - ${profile.maxElevation}m") Log.d("Routing", "Total climb: ${profile.totalUp}m, descent: ${profile.totalDown}m") } // Get route segments and instructions route.segments?.forEach { segment -> segment.instructions?.forEach { instruction -> Log.d("Routing", "Turn: ${instruction.turnInstruction}") Log.d("Routing", "Follow: ${instruction.followRoadInstruction}") } } } } GemError.Cancel -> Log.d("Routing", "Route calculation cancelled") GemError.WaypointAccess -> Log.e("Routing", "Waypoint not accessible") else -> Log.e("Routing", "Error: $errorCode") } } ) // Calculate route val result = routingService.calculateRoute(waypoints) // Cancel ongoing calculation if needed // routingService.cancelRoute() ``` ## NavigationService - Turn-by-Turn Navigation Start real navigation or simulation with detailed navigation events and voice guidance support. ```kotlin import com.magiclane.sdk.routesandnavigation.NavigationService import com.magiclane.sdk.routesandnavigation.NavigationListener import com.magiclane.sdk.core.ProgressListener import com.magiclane.sdk.core.GemError class NavigationActivity : AppCompatActivity() { private val navigationService = NavigationService() private val navigationListener = NavigationListener.create( onNavigationStarted = { Log.d("Navigation", "Navigation started") }, onNavigationInstructionUpdated = { instruction -> // Update UI with current instruction val nextTurn = instruction.nextTurnInstruction val distance = instruction.timeDistanceToNextTurn?.totalDistance Log.d("Navigation", "Next: $nextTurn in ${distance}m") }, onNavigationSound = { sound -> // Play TTS voice guidance // sound.play() }, onWaypointReached = { landmark -> Log.d("Navigation", "Waypoint reached: ${landmark.name}") }, onDestinationReached = { landmark -> Log.d("Navigation", "Destination reached: ${landmark.name}") }, onRouteUpdated = { route -> Log.d("Navigation", "Route updated due to deviation or traffic") }, onBetterRouteDetected = { route, travelTime, delay, timeGain -> Log.d("Navigation", "Better route found: saves ${timeGain}s") // Accept or reject better route }, onNavigationError = { error -> when (error) { GemError.NoError -> Log.d("Navigation", "Success") GemError.Cancel -> Log.d("Navigation", "Cancelled") else -> Log.e("Navigation", "Error: ${GemError.getMessage(error)}") } }, onNotifyStatusChange = { status -> Log.d("Navigation", "Status: $status") } ) private val progressListener = ProgressListener.create( onStarted = { /* show loading */ }, onCompleted = { _, _ -> /* hide loading */ } ) fun startNavigation(route: Route, mapView: MapView) { val error = navigationService.startNavigation( route, navigationListener, progressListener ) if (error == GemError.NoError) { // Set camera to follow position during navigation mapView.followPosition() } } fun startSimulation(route: Route, mapView: MapView) { mapView.presentRoute(route) navigationService.startSimulation( route, navigationListener, progressListener, speedMultiplier = 2.0f // 2x speed ) mapView.followPosition() } fun stopNavigation() { navigationService.cancelNavigation(navigationListener) } } ``` ## PositionService - Location Updates Configure GPS positioning, receive location updates, and access map-matched position data. ```kotlin import android.Manifest import android.content.pm.PackageManager import androidx.core.content.ContextCompat import com.magiclane.sdk.sensordatasource.PositionService import com.magiclane.sdk.sensordatasource.DataSourceFactory import com.magiclane.sdk.sensordatasource.PositionListener import com.magiclane.sdk.sensordatasource.EDataType import com.magiclane.sdk.sensordatasource.ImprovedPositionData import com.magiclane.sdk.util.SdkCall // Check and request permissions if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { // Set up live GPS data source SdkCall.execute { val liveDataSource = DataSourceFactory.produceLive() liveDataSource?.let { PositionService.dataSource = it Log.d("Position", "Live data source configured") } } } // Listen for raw position updates val rawPositionListener = PositionListener { position -> if (position.isValid()) { Log.d("Position", "Raw: ${position.coordinates}") } } PositionService.addListener(rawPositionListener, EDataType.Position) // Listen for map-matched position updates val improvedPositionListener = PositionListener { position -> if (position.isValid() && position is ImprovedPositionData) { Log.d("Position", "Coords: ${position.coordinates}") Log.d("Position", "Speed: ${position.speed} m/s") Log.d("Position", "Speed limit: ${position.roadSpeedLimit} m/s") Log.d("Position", "Course: ${position.course} degrees") Log.d("Position", "Fix quality: ${position.fixQuality}") if (position.hasRoadLocalization()) { Log.d("Position", "Road: ${position.roadAddress}") } } } PositionService.addListener(improvedPositionListener, EDataType.ImprovedPosition) // Get current position synchronously SdkCall.execute { val currentPosition = PositionService.position val improvedPosition = PositionService.improvedPosition currentPosition?.let { Log.d("Position", "Current: ${it.coordinates}") } improvedPosition?.let { Log.d("Position", "Improved: ${it.coordinates}") } } // Remove listeners when done PositionService.removeListener(rawPositionListener) PositionService.removeListener(improvedPositionListener) ``` ## ContentStore - Offline Map Management Download, manage, and delete offline map content including road maps, styles, and voice files. ```kotlin import com.magiclane.sdk.content.ContentStore import com.magiclane.sdk.content.ContentStoreItem import com.magiclane.sdk.content.EContentType import com.magiclane.sdk.core.GemError val contentStore = ContentStore() // List available online content val (errorCode, progressListener) = contentStore.asyncGetStoreContentList( EContentType.RoadMap, onStarted = { Log.d("Content", "Fetching content list...") }, onProgress = { Log.d("Content", "Progress: $it%") }, onCompleted = { resultList, error, hint -> if (error == GemError.NoError) { resultList?.forEach { item -> Log.d("Content", "Map: ${item.name}, Size: ${item.totalSize} bytes") Log.d("Content", "Status: ${item.status}, Completed: ${item.isCompleted()}") } } else { Log.e("Content", "Error: ${GemError.getMessage(error)}") } } ) // List local (already downloaded) content val localItems = contentStore.getLocalContentList(EContentType.RoadMap) localItems?.forEach { item -> Log.d("Content", "Local map: ${item.name}") } // Download a content item fun downloadMap(item: ContentStoreItem) { val error = item.asyncDownload( allowChargedNetworks = false, onProgress = { progress -> Log.d("Download", "Progress: $progress%") }, onCompleted = { error, _ -> if (error == GemError.NoError) { Log.d("Download", "${item.name} downloaded successfully") } else { Log.e("Download", "Failed: ${GemError.getMessage(error)}") } } ) } // Pause and resume download fun pauseDownload(item: ContentStoreItem) { item.pauseDownload() } fun resumeDownload(item: ContentStoreItem) { item.asyncDownload(onCompleted = { _, _ -> }) } // Delete downloaded content fun deleteContent(item: ContentStoreItem) { if (item.canDeleteContent()) { val error = item.deleteContent() if (error == GemError.NoError) { Log.d("Content", "${item.name} deleted") } } } // Filter content by country and area val countries = arrayListOf("USA", "CAN") val area = RectangleGeographicArea( Coordinates(53.7731, -1.7990), Coordinates(38.4549, 21.1696) ) contentStore.asyncGetStoreFilteredList( EContentType.RoadMap, countries, area, onCompleted = { resultList, error, _ -> // Handle filtered results } ) ``` ## LandmarkStore - Custom Landmarks Create, manage, and persist custom landmarks with categories for search and display. ```kotlin import com.magiclane.sdk.places.Landmark import com.magiclane.sdk.places.LandmarkStore import com.magiclane.sdk.places.LandmarkStoreService import com.magiclane.sdk.places.LandmarkCategory import com.magiclane.sdk.places.ContactInfo import com.magiclane.sdk.places.EContactInfoFieldType // Create a landmark store val (store, errorCode) = LandmarkStoreService().createLandmarkStore("MyFavorites") store?.let { landmarkStore -> // Create and configure a landmark val landmark = Landmark("Coffee Shop", 48.8566, 2.3522).apply { description = "Best coffee in Paris" // Add contact info contactInfo = ContactInfo().apply { addField(EContactInfoFieldType.Phone, "+33123456789", "Main") addField(EContactInfoFieldType.Email, "info@coffeeshop.com", "Contact") } // Add extra info extraInfo = arrayListOf("Hours: 7am-8pm", "WiFi available") } // Add landmark to store landmarkStore.addLandmark(landmark) // Create custom category val category = LandmarkCategory().apply { name = "My Favorites" } landmarkStore.addCategory(category) // Add landmark to specific category val categorizedLandmark = Landmark("Restaurant", 48.8584, 2.2945) landmarkStore.addLandmark(categorizedLandmark, category.id) // Retrieve landmarks val allLandmarks = landmarkStore.getLandmarks() val categoryLandmarks = landmarkStore.getLandmarks(category.id) // Update landmark landmark.name = "Updated Coffee Shop" landmarkStore.updateLandmark(landmark) // Browse large stores efficiently val browseSession = landmarkStore.createLandmarkBrowseSession( LandmarkBrowseSessionSettings().apply { orderBy = ELandmarkOrder.Name descendingOrder = false nameFilter = "Coffee" } ) val paginatedLandmarks = browseSession?.getLandmarks(0, 10) // Remove landmark landmarkStore.removeLandmark(landmark) } // Get landmark store by name val existingStore = LandmarkStoreService().getLandmarkStoreByName("MyFavorites") // Remove landmark store store?.let { val storeId = it.id it.release() LandmarkStoreService().removeLandmarkStore(storeId) } ``` ## MapView - Display Routes and Landmarks Present routes, landmarks, and markers on the map with customizable rendering settings. ```kotlin val mapView = gemSurfaceView.mapView // Display a route on the map mapView?.presentRoute(route) // Display multiple routes mapView?.presentRoutes(arrayListOf(route1, route2)) // Hide routes mapView?.hideRoutes() // Center on route mapView?.centerOnRoute(route) // Add landmark store to map for display mapView?.preferences?.landmarkStores?.add(myLandmarkStore) // Highlight landmarks val landmarksToHighlight = arrayListOf(landmark1, landmark2) mapView?.activateHighlightLandmarks( landmarksToHighlight, highlightId = 1, renderSettings = HighlightRenderSettings().apply { imageSize = 48 options = EHighlightOptions.Selectable.value } ) // Deactivate highlight mapView?.deactivateHighlight(1) // Follow current position during navigation mapView?.followPosition() mapView?.stopFollowingPosition() // Check if following position val isFollowing = mapView?.isFollowingPosition() ``` ## AndroidManifest Configuration Configure required permissions and API token for the Magic Lane SDK. ```xml ``` ## Gradle Dependencies Add the Magic Lane Maps SDK dependency to your Android project. ```kotlin // settings.gradle.kts dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://developer.magiclane.com/packages/android") } } } // app/build.gradle.kts dependencies { implementation("com.magiclane:maps-kotlin:1.8.0") } ``` ## Summary The Magic Lane Maps SDK for Android provides a complete solution for building navigation and mapping applications. The primary integration pattern involves initializing `GemSdk` in your Activity's `onCreate()`, adding a `GemSurfaceView` to your layout for map rendering, and using the core services: `SearchService` for location queries, `RoutingService` for route calculation, and `NavigationService` for turn-by-turn guidance. The SDK supports both online and offline operation through the `ContentStore` API for downloading map data. Key use cases include building navigation apps with real-time GPS tracking, fleet management solutions with custom landmark stores, location-based search applications, and offline mapping solutions for areas with limited connectivity. The SDK's modular architecture allows developers to integrate only the features they need, from simple map display to full-featured navigation with voice guidance, traffic awareness, and route optimization. All components work together through a consistent callback-based API pattern, making it straightforward to build responsive, feature-rich mapping applications.