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