Marker Native Strategy (Experimental)
The mapconductor-marker-native-strategy module provides high-performance marker management using native C++ spatial indexing. This experimental module dramatically improves performance for applications with large numbers of markers (10,000+).
⚠️ Experimental Module: This module is highly experimental and requires native library support. Use in production with extreme caution.
Overview
Section titled “Overview”The marker native strategy replaces Java-based spatial indexing with optimized C++ implementations, providing:
- 90% Memory Reduction: Compared to standard marker management
- Native Spatial Queries: C++ spatial indexing for maximum performance
- Efficient Viewport Culling: Only renders markers within the viewport
- Parallel Processing: Multi-threaded marker operations
- Minimal Java Overhead: Native code as single source of truth
Performance Characteristics
Section titled “Performance Characteristics”Memory Usage
Section titled “Memory Usage”- Standard MarkerManager: ~1MB per 1,000 markers
- NativeMarkerManager: ~100KB per 1,000 markers
- Optimized Storage: No duplicate entity storage
Query Performance
Section titled “Query Performance”- Standard Spatial Query: O(log n) with Java overhead
- Native Spatial Query: O(log n) with C++ optimization
- Large Dataset: 10x-100x performance improvement for 100,000+ markers
Installation
Section titled “Installation”Add the native strategy module to your build.gradle:
dependencies { implementation "com.mapconductor:marker-native-strategy"
// Required: Bom module implementation "com.mapconductor:mapconductor-bom:$version" // Required: Core module implementation "com.mapconductor:core"
// Choose your map provider implementation "com.mapconductor:for-googlemaps"}Provider Integration
Section titled “Provider Integration”Native strategies still need a provider-specific marker renderer/controller to show results on the map.
Built-in provider modules provide this via the MapServiceRegistry capability system (see Map Service Registry).
Native Library Setup
Section titled “Native Library Setup”The module requires native C++ libraries. Ensure your app supports the required ABIs:
android { defaultConfig { ndk { abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64' } }}Core Components
Section titled “Core Components”NativeMarkerManager
Section titled “NativeMarkerManager”High-performance marker manager using native spatial indexing:
import com.mapconductor.marker.nativestrategy.NativeMarkerManagerimport com.mapconductor.core.geocell.HexGeocell
// Create native marker managerval nativeManager = NativeMarkerManager<ActualMarker>( hexGeocell = HexGeocell.defaultGeocell())
// Use like standard MarkerManagernativeManager.registerEntity(markerEntity)val nearestMarker = nativeManager.findNearest(position)val markersInBounds = nativeManager.findMarkersInBounds(bounds)Native Rendering Strategies
Section titled “Native Rendering Strategies”SimpleNativeParallelStrategy
Section titled “SimpleNativeParallelStrategy”Parallel marker rendering with native indexing:
import com.mapconductor.marker.nativestrategy.SimpleNativeParallelStrategy
val strategy = SimpleNativeParallelStrategy<ActualMarker>( expandMargin = 0.2, // Viewport expansion maxConcurrency = 4, // Parallel threads geocell = HexGeocell.defaultGeocell())Basic Usage
Section titled “Basic Usage”Simple Native Manager
Section titled “Simple Native Manager”@Composablefun BasicNativeExample() { // Create native marker manager val nativeManager = remember { NativeMarkerManager<GoogleMapActualMarker>( hexGeocell = HexGeocell.defaultGeocell() ) }
// Add markers to native manager LaunchedEffect(Unit) { val markers = generateLargeMarkerDataset() // 10,000+ markers markers.forEach { markerData -> val entity = MarkerEntityInterface( state = MarkerState( id = markerData.id, position = markerData.position, icon = DefaultIcon() ) ) nativeManager.registerEntity(entity) } }
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { // Markers are managed by native strategy // No need to manually add Marker composables }
DisposableEffect(Unit) { onDispose { nativeManager.destroy() // Important: cleanup native resources } }}Performance Monitoring
Section titled “Performance Monitoring”@Composablefun NativePerformanceExample() { val nativeManager = remember { NativeMarkerManager<GoogleMapActualMarker>( hexGeocell = HexGeocell.defaultGeocell() ) }
var stats by remember { mutableStateOf<NativeMarkerManagerStats?>(null) }
// Monitor performance LaunchedEffect(Unit) { while (true) { delay(5000) // Update every 5 seconds stats = nativeManager.getNativeMemoryStats() } }
Column { stats?.let { s -> Text("Markers: ${s.entityCount}") Text("Native Index: ${s.nativeIndexCount}") Text("Memory: ${s.estimatedMemoryKB} KB") Text("Pure Native: ${s.usesPureNativeIndex}") }
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { // Native-managed markers } }}Advanced Usage
Section titled “Advanced Usage”Parallel Rendering Strategy
Section titled “Parallel Rendering Strategy”@Composablefun ParallelRenderingExample() { val parallelStrategy = remember { SimpleNativeParallelStrategy<GoogleMapActualMarker>( expandMargin = 0.3, // Larger viewport expansion maxConcurrency = 6, // More parallel threads geocell = HexGeocell( baseHexSideLength = 1000.0, // Optimized cell size zoom = 15.0 ) ) }
// Configure marker controller with parallel strategy LaunchedEffect(mapViewState) { mapViewState.getMapViewHolder()?.let { holder -> // Setup parallel strategy with map controller // Implementation depends on map provider } }
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { // Parallel-rendered markers }}Dynamic Marker Loading
Section titled “Dynamic Marker Loading”@Composablefun DynamicNativeLoadingExample() { val nativeManager = remember { NativeMarkerManager<GoogleMapActualMarker>( hexGeocell = HexGeocell.defaultGeocell() ) }
var currentBounds by remember { mutableStateOf<GeoRectBounds?>(null) } var visibleMarkers by remember { mutableStateOf<List<MarkerEntityInterface<GoogleMapActualMarker>>>(emptyList()) }
// Load markers dynamically based on viewport LaunchedEffect(currentBounds) { currentBounds?.let { bounds -> // Native spatial query is extremely fast visibleMarkers = nativeManager.findMarkersInBounds(bounds) } }
Column { Text("Visible Markers: ${visibleMarkers.size}")
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView( state = mapViewState, onCameraMove = { cameraPosition -> currentBounds = cameraPosition.visibleRegion?.bounds } ) { // Only visible markers are processed } }}Clustering with Native Strategy
Section titled “Clustering with Native Strategy”@Composablefun NativeClusteringExample() { val clusteringStrategy = remember { NativeSpatialMarkerStrategy<GoogleMapActualMarker>( clusteringEnabled = true, clusterThreshold = 50, // Cluster when > 50 markers nearby clusterRadius = 100.0, // 100-meter clustering radius geocell = HexGeocell.defaultGeocell() ) }
val nativeManager = remember { NativeMarkerManager<GoogleMapActualMarker>( hexGeocell = HexGeocell.defaultGeocell() ) }
// Add large dataset for clustering LaunchedEffect(Unit) { val denseMarkers = generateDenseMarkerCluster( center = GeoPoint.fromLatLong(37.7749, -122.4194), count = 1000, radiusMeters = 500.0 // 500 meters )
denseMarkers.forEach { markerData -> val entity = MarkerEntityInterface( state = MarkerState( id = markerData.id, position = markerData.position, icon = DefaultIcon(fillColor = Color.Blue, scale = 0.8f) ) ) nativeManager.registerEntity(entity) } }
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { // Native clustering automatically groups nearby markers }}Native Index Operations
Section titled “Native Index Operations”Direct Spatial Queries
Section titled “Direct Spatial Queries”fun performNativeQueries(nativeManager: NativeMarkerManager<ActualMarker>) { val center = GeoPoint.fromLatLong(37.7749, -122.4194) val bounds = GeoRectBounds( southWest = GeoPoint.fromLatLong(37.7700, -122.4250), northEast = GeoPoint.fromLatLong(37.7800, -122.4150) )
// Native spatial queries are extremely fast val nearestMarker = nativeManager.findNearest(center) val boundedMarkers = nativeManager.findMarkersInBounds(bounds) val totalMarkers = nativeManager.allEntities().size
// Memory and performance stats val stats = nativeManager.getNativeMemoryStats() println("Query performance: ${stats.nativeIndexCount} indexed markers") println("Memory usage: ${stats.estimatedMemoryKB} KB")}Batch Operations
Section titled “Batch Operations”suspend fun batchNativeOperations(nativeManager: NativeMarkerManager<ActualMarker>) { val markersBatch = generateMarkerBatch(10000) // 10,000 markers
// Efficient batch registration withContext(Dispatchers.Default) { markersBatch.forEach { markerData -> val entity = MarkerEntityInterface( state = MarkerState( id = markerData.id, position = markerData.position, icon = DefaultIcon() ) ) nativeManager.registerEntity(entity) } }
// Verify registration val totalMarkers = nativeManager.allEntities().size println("Registered $totalMarkers markers in native index")}Memory Management
Section titled “Memory Management”Resource Cleanup
Section titled “Resource Cleanup”@Composablefun ResourceManagementExample() { val nativeManager = remember { NativeMarkerManager<GoogleMapActualMarker>( hexGeocell = HexGeocell.defaultGeocell() ) }
// Proper cleanup is crucial for native resources DisposableEffect(nativeManager) { onDispose { // Cleanup native resources nativeManager.destroy() } }
// Monitor memory usage var memoryStats by remember { mutableStateOf<NativeMarkerManagerStats?>(null) }
LaunchedEffect(Unit) { while (true) { delay(10000) // Check every 10 seconds memoryStats = nativeManager.getNativeMemoryStats()
// Log memory usage for monitoring memoryStats?.let { stats -> if (stats.estimatedMemoryKB > 10000) { // > 10MB println("High memory usage detected: ${stats.estimatedMemoryKB} KB") } } } }
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { // Native-managed markers }}Performance Optimization
Section titled “Performance Optimization”Configuration Tuning
Section titled “Configuration Tuning”// Optimal configuration for large datasetsval optimizedGeocell = HexGeocell( baseHexSideLength = 500.0, // Smaller cells for dense data zoom = 18.0 // Higher resolution)
val optimizedManager = NativeMarkerManager<ActualMarker>( hexGeocell = optimizedGeocell)
// Optimal parallel strategyval optimizedStrategy = SimpleNativeParallelStrategy<ActualMarker>( expandMargin = 0.1, // Smaller expansion for performance maxConcurrency = Runtime.getRuntime().availableProcessors(), geocell = optimizedGeocell)Benchmark Testing
Section titled “Benchmark Testing”suspend fun benchmarkNativePerformance() { val standardManager = MarkerManager<ActualMarker>(HexGeocell.defaultGeocell()) val nativeManager = NativeMarkerManager<ActualMarker>(HexGeocell.defaultGeocell())
val testMarkers = generateTestDataset(50000) // 50,000 markers
// Benchmark registration val standardTime = measureTimeMillis { testMarkers.forEach { standardManager.registerEntity(it) } }
val nativeTime = measureTimeMillis { testMarkers.forEach { nativeManager.registerEntity(it) } }
// Benchmark spatial queries val testBounds = GeoRectBounds( southWest = GeoPoint.fromLatLong(37.7700, -122.4250), northEast = GeoPoint.fromLatLong(37.7800, -122.4150) )
val standardQueryTime = measureTimeMillis { repeat(1000) { standardManager.findMarkersInBounds(testBounds) } }
val nativeQueryTime = measureTimeMillis { repeat(1000) { nativeManager.findMarkersInBounds(testBounds) } }
println("Registration - Standard: ${standardTime}ms, Native: ${nativeTime}ms") println("Queries - Standard: ${standardQueryTime}ms, Native: ${nativeQueryTime}ms")}Best Practices
Section titled “Best Practices”- Resource Management: Always call
destroy()on NativeMarkerManager when done - Batch Operations: Use batch registration for large datasets
- Memory Monitoring: Monitor native memory usage in production
- Testing: Thoroughly test with your specific data patterns
- Fallback Strategy: Have non-native fallback for unsupported devices
Limitations and Considerations
Section titled “Limitations and Considerations”- Platform Support: Requires native library support for target ABIs
- Memory Management: Native memory is not garbage collected
- Debugging: Native crashes are harder to debug than Java crashes
- Binary Size: Increases APK size due to native libraries
- Compatibility: May not work on all devices/emulators
Troubleshooting
Section titled “Troubleshooting”Native Library Loading Issues
Section titled “Native Library Loading Issues”// Check native library availabilitytry { val nativeManager = NativeMarkerManager<ActualMarker>( hexGeocell = HexGeocell.defaultGeocell() ) // Native library loaded successfully} catch (e: UnsatisfiedLinkError) { // Fallback to standard marker manager val standardManager = MarkerManager<ActualMarker>( hexGeocell = HexGeocell.defaultGeocell() )}Memory Leak Prevention
Section titled “Memory Leak Prevention”class MarkerActivity : ComponentActivity() { private var nativeManager: NativeMarkerManager<ActualMarker>? = null
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
nativeManager = NativeMarkerManager(HexGeocell.defaultGeocell()) }
override fun onDestroy() { super.onDestroy()
// Critical: cleanup native resources nativeManager?.destroy() nativeManager = null }}The marker native strategy module provides significant performance improvements for marker-heavy applications, but requires careful resource management and thorough testing due to its experimental nature.