マーカー戦略(実験的)
mapconductor-marker-strategy モジュールは、大規模なデータセットでのパフォーマンスとユーザーエクスペリエンスを最適化するための高度なマーカーレンダリング戦略を提供します。この実験的モジュールは、異なるユースケースとパフォーマンス要件に合わせた複数のレンダリングアプローチを提供します。
⚠️ 実験的モジュール: このモジュールは実験的であり、API が変更される可能性があります。本番環境での使用には注意してください。
マーカー戦略モジュールは、基本的なマーカー表示を超えた洗練されたレンダリング戦略を提供します:
- ビューポートベースのレンダリング: 現在のビューポートに表示されているマーカーのみをレンダリング
- 動的追加/削除: ビューポートの変更に応じて効率的にマーカーを管理
- 空間最適化: 大規模なデータセットのための高度な空間インデックス
- リモートデータ統合: サーバーサイドのマーカーデータのサポート
- クラスタリングサポート: パフォーマンス向上のために近くのマーカーをグループ化
インストール
Section titled “インストール”build.gradle にマーカー戦略モジュールを追加します:
dependencies { implementation "com.mapconductor:marker-strategy"
// 必須: Core モジュール implementation "com.mapconductor:mapconductor-bom:$version" // 必須: Core モジュール implementation "com.mapconductor:core"
// 地図SDKを選択 implementation "com.mapconductor:for-googlemaps"}地図SDKモジュールとの連携
Section titled “地図SDKモジュールとの連携”mapconductor-marker-strategy は、地図SDKごとの実装(renderer/controller)を provider 側が登録した capability 経由で利用します。
組み込みの地図SDKモジュールは MapServiceRegistry に自動登録するため、そのまま動作します(Map Service Registry)。
DefaultMarkerStrategy
Section titled “DefaultMarkerStrategy”追加/削除操作を効率的に処理する Google Maps と ArcGIS 地図SDKに最適:
import com.mapconductor.marker.strategy.DefaultMarkerStrategy
val defaultStrategy = DefaultMarkerStrategy<GoogleMapActualMarker>( expandMargin = 0.2, // 20% viewport expansion semaphore = Semaphore(1), geocell = HexGeocell.defaultGeocell())- 動的追加/削除: ビューポートに入るマーカーを追加し、離れるマーカーを削除
- ビューポート拡張: 表示領域の少し外側のマーカーを事前読み込み
- メモリ効率: 表示されているマーカーのみをメモリに保持
- スムーズなスクロール: マップ移動中のポップイン/ポップアウトを削減
SimpleMarkerStrategy
Section titled “SimpleMarkerStrategy”小規模なデータセットまたは異なるパフォーマンス特性を持つ地図SDK向けの軽量戦略:
import com.mapconductor.marker.strategy.SimpleMarkerStrategy
val simpleStrategy = SimpleMarkerStrategy<MapboxActualMarker>( expandMargin = 0.15, geocell = HexGeocell.defaultGeocell())- 簡略化されたロジック: より複雑でないビューポート管理
- 低オーバーヘッド: 最小限の計算オーバーヘッド
- Mapbox に適している: よりシンプルなマーカー管理を好む地図SDK向けに最適化
SpatialMarkerStrategy
Section titled “SpatialMarkerStrategy”空間クラスタリングと最適化を備えた高度な戦略:
import com.mapconductor.marker.strategy.SpatialMarkerStrategy
val spatialStrategy = SpatialMarkerStrategy<HereActualMarker>( clusteringEnabled = true, clusterRadius = 100, // Clustering radius (meters) maxMarkersPerCluster = 10, // Maximum markers in a cluster geocell = HexGeocell.defaultGeocell())- 空間クラスタリング: 近くのマーカーをクラスターにグループ化
- 密度管理: 密集したエリアでの視覚的な混雑を軽減
- パフォーマンススケーリング: 非常に大きなデータセットを効率的に処理
- カスタマイズ可能なクラスタリング: 設定可能なクラスタリングパラメータ
基本的な使用方法
Section titled “基本的な使用方法”デフォルト戦略の設定
Section titled “デフォルト戦略の設定”@Composablefun DefaultStrategyExample() { val mapViewState = rememberGoogleMapViewState()
val markerStrategy = remember { DefaultMarkerStrategy<GoogleMapActualMarker>( expandMargin = 0.2 ) }
// Configure strategy with map controller LaunchedEffect(mapViewState) { // Strategy setup depends on map provider implementation // This is typically handled by the map provider's marker controller }
// GoogleMapView、MapboxMapView など、選択した地図SDKに置き換えてください GoogleMapView(state = mapViewState) { // Markers are managed by the strategy // Add markers programmatically through the strategy }}戦略へのマーカーの追加
Section titled “戦略へのマーカーの追加”@Composablefun StrategyMarkerManagement() { val markerStrategy = remember { DefaultMarkerStrategy<GoogleMapActualMarker>() }
LaunchedEffect(Unit) { // Add markers to the strategy's manager val markers = loadMarkerData() // Your marker data
markers.forEach { markerData -> val entity = MarkerEntityInterface( state = MarkerState( id = markerData.id, position = markerData.position, icon = DefaultIcon(fillColor = markerData.color) ) )
// Register with strategy's marker manager markerStrategy.markerManager.registerEntity(entity) } }
// GoogleMapView、MapboxMapView など、選択した地図SDKに置き換えてください GoogleMapView(state = mapViewState) { // Strategy handles marker rendering automatically }}高度な使用方法
Section titled “高度な使用方法”動的マーカー読み込み
Section titled “動的マーカー読み込み”@Composablefun DynamicLoadingExample() { val mapViewState = rememberGoogleMapViewState() var currentBounds by remember { mutableStateOf<GeoRectBounds?>(null) } var loadedMarkers by remember { mutableStateOf<Set<String>>(emptySet()) }
val strategy = remember { DefaultMarkerStrategy<GoogleMapActualMarker>( expandMargin = 0.3 // Larger margin for preloading ) }
// Load markers dynamically based on viewport LaunchedEffect(currentBounds) { currentBounds?.let { bounds -> val newMarkers = fetchMarkersForBounds(bounds) // Your API call
newMarkers.forEach { markerData -> if (markerData.id !in loadedMarkers) { val entity = MarkerEntityInterface( state = MarkerState( id = markerData.id, position = markerData.position, icon = DefaultIcon() ) )
strategy.markerManager.registerEntity(entity) loadedMarkers = loadedMarkers + markerData.id } } } }
// GoogleMapView、MapboxMapView など、選択した地図SDKに置き換えてください GoogleMapView( state = mapViewState, onCameraMove = { cameraPosition -> currentBounds = cameraPosition.visibleRegion?.bounds } ) { // Strategy manages dynamic marker loading }}クラスタリング戦略
Section titled “クラスタリング戦略”@Composablefun ClusteringStrategyExample() { val clusterStrategy = remember { SpatialMarkerStrategy<GoogleMapActualMarker>( clusteringEnabled = true, clusterRadius = 50.0, // 50-meter clustering maxMarkersPerCluster = 5, // Small clusters geocell = HexGeocell( baseHexSideLength = 100.0, // Fine-grained spatial index zoom = 18.0 ) ) }
// Add dense marker dataset LaunchedEffect(Unit) { val denseMarkers = generateDenseMarkerSet( center = GeoPoint.fromLatLong(37.7749, -122.4194), count = 500, radiusMeters = 200.0 // 200-meter radius )
denseMarkers.forEach { markerData -> val entity = MarkerEntityInterface( state = MarkerState( id = markerData.id, position = markerData.position, icon = DefaultIcon( fillColor = markerData.category.color, scale = 0.8f ) ) )
clusterStrategy.markerManager.registerEntity(entity) } }
// GoogleMapView、MapboxMapView など、選択した地図SDKに置き換えてください GoogleMapView(state = mapViewState) { // Clustering strategy automatically groups nearby markers }}Remote Spatial Strategy
Section titled “Remote Spatial Strategy”// JA 版では説明のみ維持(実装詳細は英語版を参照)
パフォーマンス特性
Section titled “パフォーマンス特性”| Strategy | Best For | Memory Usage | Network | Complexity |
|---|---|---|---|---|
| DefaultMarkerStrategy | Google Maps, ArcGIS | Medium | None | Medium |
| SimpleMarkerStrategy | Mapbox, HERE | Low | None | Low |
| SpatialMarkerStrategy | Large datasets | High | None | High |
| RemoteSpatialMarkerStrategy | Server-side data | Low | High | High |
ユースケースガイドライン
Section titled “ユースケースガイドライン”DefaultMarkerStrategy を選ぶべき場合:
Section titled “DefaultMarkerStrategy を選ぶべき場合:”- Google Maps または ArcGIS を使用している
- 1,000〜50,000 の中規模マーカー数を扱う
- ビューポートベースのスムーズなレンダリングが必要
- マーカーがローカルでロードされる
SimpleMarkerStrategy を選ぶべき場合:
Section titled “SimpleMarkerStrategy を選ぶべき場合:”- Mapbox または HERE Maps を使用している
- 10,000 未満の小規模マーカー数を扱う
- 最小限のオーバーヘッドを求める
- シンプルなレンダリング要件である
SpatialMarkerStrategy を選ぶべき場合:
Section titled “SpatialMarkerStrategy を選ぶべき場合:”- 50,000 以上の非常に大きなマーカーデータセットを扱う
- クラスタリング機能が必要
- 高度な空間最適化が必要
- より高いメモリ使用量を許容できる
RemoteSpatialMarkerStrategy を選ぶべき場合:
Section titled “RemoteSpatialMarkerStrategy を選ぶべき場合:”- マーカーがサーバー側に保存されている
- オンデマンドロードを行いたい
- ネットワーク接続が前提
- アプリ側のメモリ使用量を最小限に抑えたい
カスタム戦略の開発
Section titled “カスタム戦略の開発”AbstractViewportStrategy の拡張
Section titled “AbstractViewportStrategy の拡張”class CustomMarkerStrategy<ActualMarker>( semaphore: Semaphore = Semaphore(1), geocell: HexGeocellInterface = HexGeocell.defaultGeocell()) : AbstractViewportStrategy<ActualMarker>(semaphore, geocell) {
override suspend fun onCameraChanged( cameraPosition: MapCameraPosition, renderer: MarkerOverlayRendererInterface<ActualMarker> ) { semaphore.withPermit { // カスタムレンダリングロジック val visibleBounds = cameraPosition.visibleRegion?.bounds ?: return
// 独自のマーカー管理ロジック val markersToShow = determineVisibleMarkers(visibleBounds) val markersToHide = determineHiddenMarkers(visibleBounds)
// renderer を使用して表示を更新 if (markersToHide.isNotEmpty()) { renderer.onRemove(markersToHide) }
if (markersToShow.isNotEmpty()) { val addParams = markersToShow.map { entity -> object : MarkerOverlayRendererInterface.AddParamsInterface { override val state: MarkerState = entity.state override val bitmapIcon: BitmapIcon = entity.state.icon?.toBitmapIcon() ?: defaultIcon } } renderer.onAdd(addParams) }
renderer.onPostProcess() } }
private fun determineVisibleMarkers(bounds: GeoRectBounds): List<MarkerEntityInterface<ActualMarker>> { // どのマーカーを表示すべきかを決定するカスタムロジック return markerManager.findMarkersInBounds(bounds) }
private fun determineHiddenMarkers(bounds: GeoRectBounds): List<MarkerEntityInterface<ActualMarker>> { // どのマーカーを非表示にすべきかを決定するカスタムロジック return markerManager.allEntities().filter { entity -> entity.isRendered && !bounds.contains(entity.state.position) } }}パフォーマンス最適化
Section titled “パフォーマンス最適化”// High-performance configurationval performanceStrategy = DefaultMarkerStrategy<ActualMarker>( expandMargin = 0.1, // Smaller margin for less preloading semaphore = Semaphore(2), // Allow some parallelism geocell = HexGeocell( baseHexSideLength = 1000.0, // Larger cells for better performance zoom = 15.0 // Lower resolution for speed ))
// Memory-optimized configurationval memoryStrategy = SimpleMarkerStrategy<ActualMarker>( expandMargin = 0.05, // Minimal expansion geocell = HexGeocell( baseHexSideLength = 2000.0, // Very large cells zoom = 12.0 // Lower resolution ))パフォーマンスの監視
Section titled “パフォーマンスの監視”@Composablefun StrategyPerformanceMonitoring() { val strategy = remember { DefaultMarkerStrategy<GoogleMapActualMarker>() } var performanceStats by remember { mutableStateOf<String>("") }
LaunchedEffect(Unit) { while (true) { delay(5000)
val stats = strategy.markerManager.getMemoryStats() performanceStats = buildString { appendLine("Entities: ${stats.entityCount}") appendLine("Memory: ${stats.estimatedMemoryKB} KB") appendLine("Spatial Index: ${stats.hasSpatialIndex}") } } }
Column { Text(performanceStats)
// GoogleMapView、MapboxMapView など、選択した地図SDKに置き換えてください GoogleMapView(state = mapViewState) { // Strategy-managed markers } }}ベストプラクティス
Section titled “ベストプラクティス”- 戦略の選択: 特定のユースケースと地図SDKに基づいて戦略を選択する
- ビューポートマージン: 事前読み込み(より大きなマージン)とパフォーマンス(より小さなマージン)のバランスを取る
- 空間設定: データ密度に合わせてジオセルパラメータを調整する
- メモリ監視: 特に大規模なデータセットでは、本番環境でメモリ使用量を監視する
- テスト: 現実的なデータ量と使用パターンでテストする
- フォールバック: パフォーマンス問題のためのより単純な戦略をフォールバックとして用意する
よくある落とし穴
Section titled “よくある落とし穴”- 過剰設計: シンプルなマーカーシナリオに複雑な戦略を使用しない
- メモリリーク: 戦略リソースの適切なクリーンアップを確保する
- 地図SDKの不一致: 地図SDKに間違った戦略を使用すると、パフォーマンスが低下する可能性がある
- 過剰な事前読み込み: 大きな拡張マージンはメモリ圧迫を引き起こす可能性がある
- スレッドセーフティ: 戦略は並行性を処理しますが、外部の変更には注意が必要
基本的なマーカー管理から
Section titled “基本的なマーカー管理から”// Before: Basic marker management@Composablefun BasicMarkers() { MapView(state = mapViewState) { markers.forEach { markerData -> Marker( position = markerData.position, icon = DefaultIcon() ) } }}
// After: Strategy-based management@Composablefun StrategyMarkers() { val strategy = remember { DefaultMarkerStrategy<ActualMarker>() }
LaunchedEffect(markers) { markers.forEach { markerData -> val entity = MarkerEntityInterface( state = MarkerState( id = markerData.id, position = markerData.position, icon = DefaultIcon() ) ) strategy.markerManager.registerEntity(entity) } }
// GoogleMapView、MapboxMapView など、選択した地図SDKに置き換えてください MapView(state = mapViewState) { // Strategy handles all marker rendering }}マーカー戦略モジュールは、大規模なデータセットや複雑なレンダリング要件を必要とするアプリケーションのための洗練されたマーカー管理機能を提供します。