コンテンツにスキップ

マーカー戦略(実験的)

mapconductor-marker-strategy モジュールは、大規模なデータセットでのパフォーマンスとユーザーエクスペリエンスを最適化するための高度なマーカーレンダリング戦略を提供します。この実験的モジュールは、異なるユースケースとパフォーマンス要件に合わせた複数のレンダリングアプローチを提供します。

⚠️ 実験的モジュール: このモジュールは実験的であり、API が変更される可能性があります。本番環境での使用には注意してください。

マーカー戦略モジュールは、基本的なマーカー表示を超えた洗練されたレンダリング戦略を提供します:

  • ビューポートベースのレンダリング: 現在のビューポートに表示されているマーカーのみをレンダリング
  • 動的追加/削除: ビューポートの変更に応じて効率的にマーカーを管理
  • 空間最適化: 大規模なデータセットのための高度な空間インデックス
  • リモートデータ統合: サーバーサイドのマーカーデータのサポート
  • クラスタリングサポート: パフォーマンス向上のために近くのマーカーをグループ化

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"
}

mapconductor-marker-strategy は、地図SDKごとの実装(renderer/controller)を provider 側が登録した capability 経由で利用します。 組み込みの地図SDKモジュールは MapServiceRegistry に自動登録するため、そのまま動作します(Map Service Registry)。

追加/削除操作を効率的に処理する 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()
)
  • 動的追加/削除: ビューポートに入るマーカーを追加し、離れるマーカーを削除
  • ビューポート拡張: 表示領域の少し外側のマーカーを事前読み込み
  • メモリ効率: 表示されているマーカーのみをメモリに保持
  • スムーズなスクロール: マップ移動中のポップイン/ポップアウトを削減

小規模なデータセットまたは異なるパフォーマンス特性を持つ地図SDK向けの軽量戦略:

import com.mapconductor.marker.strategy.SimpleMarkerStrategy
val simpleStrategy = SimpleMarkerStrategy<MapboxActualMarker>(
expandMargin = 0.15,
geocell = HexGeocell.defaultGeocell()
)
  • 簡略化されたロジック: より複雑でないビューポート管理
  • 低オーバーヘッド: 最小限の計算オーバーヘッド
  • Mapbox に適している: よりシンプルなマーカー管理を好む地図SDK向けに最適化

空間クラスタリングと最適化を備えた高度な戦略:

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()
)
  • 空間クラスタリング: 近くのマーカーをクラスターにグループ化
  • 密度管理: 密集したエリアでの視覚的な混雑を軽減
  • パフォーマンススケーリング: 非常に大きなデータセットを効率的に処理
  • カスタマイズ可能なクラスタリング: 設定可能なクラスタリングパラメータ
@Composable
fun 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
}
}
@Composable
fun 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
}
}
@Composable
fun 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
}
}
@Composable
fun 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
}
}

// JA 版では説明のみ維持(実装詳細は英語版を参照)

StrategyBest ForMemory UsageNetworkComplexity
DefaultMarkerStrategyGoogle Maps, ArcGISMediumNoneMedium
SimpleMarkerStrategyMapbox, HERELowNoneLow
SpatialMarkerStrategyLarge datasetsHighNoneHigh
RemoteSpatialMarkerStrategyServer-side dataLowHighHigh

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 を選ぶべき場合:”
  • マーカーがサーバー側に保存されている
  • オンデマンドロードを行いたい
  • ネットワーク接続が前提
  • アプリ側のメモリ使用量を最小限に抑えたい
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)
}
}
}
// High-performance configuration
val 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 configuration
val memoryStrategy = SimpleMarkerStrategy<ActualMarker>(
expandMargin = 0.05, // Minimal expansion
geocell = HexGeocell(
baseHexSideLength = 2000.0, // Very large cells
zoom = 12.0 // Lower resolution
)
)
@Composable
fun 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
}
}
}
  1. 戦略の選択: 特定のユースケースと地図SDKに基づいて戦略を選択する
  2. ビューポートマージン: 事前読み込み(より大きなマージン)とパフォーマンス(より小さなマージン)のバランスを取る
  3. 空間設定: データ密度に合わせてジオセルパラメータを調整する
  4. メモリ監視: 特に大規模なデータセットでは、本番環境でメモリ使用量を監視する
  5. テスト: 現実的なデータ量と使用パターンでテストする
  6. フォールバック: パフォーマンス問題のためのより単純な戦略をフォールバックとして用意する
  1. 過剰設計: シンプルなマーカーシナリオに複雑な戦略を使用しない
  2. メモリリーク: 戦略リソースの適切なクリーンアップを確保する
  3. 地図SDKの不一致: 地図SDKに間違った戦略を使用すると、パフォーマンスが低下する可能性がある
  4. 過剰な事前読み込み: 大きな拡張マージンはメモリ圧迫を引き起こす可能性がある
  5. スレッドセーフティ: 戦略は並行性を処理しますが、外部の変更には注意が必要
// Before: Basic marker management
@Composable
fun BasicMarkers() {
MapView(state = mapViewState) {
markers.forEach { markerData ->
Marker(
position = markerData.position,
icon = DefaultIcon()
)
}
}
}
// After: Strategy-based management
@Composable
fun 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
}
}

マーカー戦略モジュールは、大規模なデータセットや複雑なレンダリング要件を必要とするアプリケーションのための洗練されたマーカー管理機能を提供します。