Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion mapconductor-bom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ val moduleInfo = mapOf(
"for-mapbox" to getModuleVersion(":mapconductor-for-mapbox"),
"for-maplibre" to getModuleVersion(":mapconductor-for-maplibre"),
"icons" to getModuleVersion(":mapconductor-icons"),
"marker-clustering" to getModuleVersion(":mapconductor-marker-clustering"),
"marker-native-strategy" to getModuleVersion(":mapconductor-marker-native-strategy"),
"marker-strategy" to getModuleVersion(":mapconductor-marker-strategy")
)
Expand Down Expand Up @@ -128,4 +129,4 @@ signing {
useInMemoryPgpKeys(signingKey, signingPassword)
sign(publishing.publications["release"])
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import com.mapconductor.core.info.InfoBubbleEntry
import com.mapconductor.core.map.MapOverlay
import com.mapconductor.core.map.MapOverlayRegistry
import com.mapconductor.core.marker.MarkerOverlay
import com.mapconductor.core.marker.MarkerState
import com.mapconductor.core.polygon.PolygonOverlay
import com.mapconductor.core.polygon.PolygonState
import com.mapconductor.core.polyline.PolylineOverlay
Expand All @@ -26,7 +25,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch

open class MapViewScope {
val markerCollector = com.mapconductor.core.marker.MarkerCollector()
val markerCollector =
com.mapconductor.core.marker
.MarkerCollector()
val bubbleFlow = MutableStateFlow<MutableMap<String, InfoBubbleEntry>>(mutableMapOf())
val polylineFlow = MutableStateFlow<MutableMap<String, PolylineState>>(mutableMapOf())
val polylineRemoveSharedFlow = MutableSharedFlow<String>(1000)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.mapconductor.core.marker

import com.mapconductor.core.controller.OverlayController
import com.mapconductor.core.features.GeoPointImpl
import com.mapconductor.core.features.GeoRectBounds
import com.mapconductor.core.map.MapCameraPositionImpl
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
Expand All @@ -27,6 +25,7 @@ abstract class AbstractMarkerController<ActualMarker>(
var dragEndListener: OnMarkerEventHandler? = null
var animateStartListener: OnMarkerEventHandler? = null
var animateEndListener: OnMarkerEventHandler? = null

init {
rendererRef.animateStartListener = { state -> dispatchAnimateStart(state) }
rendererRef.animateEndListener = { state -> dispatchAnimateEnd(state) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,7 @@ class StrategyMarkerController<ActualMarker>(

fun getEntity(id: String): MarkerEntity<ActualMarker>? = strategy.markerManager.getEntity(id)

override fun find(position: GeoPoint): MarkerEntity<ActualMarker>? {
return strategy.markerManager.findNearest(position)
}
override fun find(position: GeoPoint): MarkerEntity<ActualMarker>? = strategy.markerManager.findNearest(position)

override suspend fun onCameraChanged(mapCameraPosition: MapCameraPositionImpl) {
this.mapCameraPosition = mapCameraPosition
Expand Down
1 change: 1 addition & 0 deletions mapconductor-for-arcgis/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ dependencies {
compileOnly(libs.arcgis.maps.kotlin.toolkit.authentication)

compileOnly(project(":mapconductor-core"))
implementation(project(":mapconductor-marker-clustering"))

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import com.arcgismaps.mapping.ArcGISTiledElevationSource
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.mapping.view.SceneView
import com.arcgismaps.mapping.view.SurfacePlacement
import com.mapconductor.arcgis.ArcGISActualMarker
import com.mapconductor.arcgis.ArcGISMapViewHolder
import com.mapconductor.arcgis.circle.ArcGISCircleOverlayController
import com.mapconductor.arcgis.circle.ArcGISCircleOverlayRenderer
Expand Down Expand Up @@ -290,11 +289,10 @@ private fun getPolygonController(holder: ArcGISMapViewHolder): ArcGISPolygonOver
return controller
}

private fun getMarkerController(
holder: ArcGISMapViewHolder,
) = ArcGISMarkerController.create(
holder = holder,
)
private fun getMarkerController(holder: ArcGISMapViewHolder) =
ArcGISMarkerController.create(
holder = holder,
)

/**
* Default ArcGIS SDK initialization using API Key authentication.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,7 @@ class ArcGISMarkerController private constructor(
}

companion object {
fun create(
holder: ArcGISMapViewHolder,
): ArcGISMarkerController {
fun create(holder: ArcGISMapViewHolder): ArcGISMarkerController {
val markerLayer: GraphicsOverlay =
GraphicsOverlay().apply {
sceneProperties.surfacePlacement = SurfacePlacement.Relative
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.mapconductor.arcgis.marker

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.mapconductor.arcgis.ArcGISActualMarker
import com.mapconductor.arcgis.map.ArcGISMapViewScope
import com.mapconductor.core.circle.CircleState
import com.mapconductor.core.marker.ColorDefaultIcon
import com.mapconductor.core.marker.MarkerIcon
import com.mapconductor.marker.clustering.MarkerCluster
import com.mapconductor.marker.clustering.MarkerClusterStrategy

@Composable
fun ArcGISMapViewScope.MarkerClusterGroup(
clusterRadiusPx: Double = MarkerClusterStrategy.DEFAULT_CLUSTER_RADIUS_PX,
minClusterSize: Int = MarkerClusterStrategy.DEFAULT_MIN_CLUSTER_SIZE,
expandMargin: Double = MarkerClusterStrategy.DEFAULT_EXPAND_MARGIN,
clusterIconProvider: (Int) -> MarkerIcon = MarkerClusterStrategy.DEFAULT_ICON_PROVIDER,
onClusterClick: ((MarkerCluster) -> Unit)? = null,
debugClusterTurnLabel: Boolean = false,
showClusterRadiusCircle: Boolean = false,
clusterRadiusStrokeColor: Color = Color.Red,
clusterRadiusStrokeWidth: Dp = 1.dp,
clusterRadiusFillColor: Color = Color.Transparent,
content: @Composable () -> Unit,
) {
val iconProviderWithTurn =
remember(clusterIconProvider, debugClusterTurnLabel) {
if (debugClusterTurnLabel) {
{ _: Int, turn: Int -> ColorDefaultIcon(label = turn.toString()) }
} else {
null
}
}
val strategy =
remember(
clusterRadiusPx,
minClusterSize,
expandMargin,
clusterIconProvider,
onClusterClick,
debugClusterTurnLabel,
) {
MarkerClusterStrategy<ArcGISActualMarker>(
clusterRadiusPx = clusterRadiusPx,
minClusterSize = minClusterSize,
expandMargin = expandMargin,
clusterIconProvider = clusterIconProvider,
clusterIconProviderWithTurn = iconProviderWithTurn,
includeTurnInClusterId = debugClusterTurnLabel,
onClusterClick = onClusterClick,
)
}

val debugInfos by strategy.debugInfoFlow.collectAsState()
LaunchedEffect(showClusterRadiusCircle, debugInfos) {
val prefix = CLUSTER_CIRCLE_ID_PREFIX
val nextMap = circleFlow.value.toMutableMap()
nextMap.keys.filter { it.startsWith(prefix) }.forEach { nextMap.remove(it) }
if (showClusterRadiusCircle) {
debugInfos.forEach { info ->
val circleState =
CircleState(
center = info.center,
radiusMeters = info.radiusMeters,
clickable = false,
strokeColor = clusterRadiusStrokeColor,
strokeWidth = clusterRadiusStrokeWidth,
fillColor = clusterRadiusFillColor,
id = "$prefix${info.id}",
extra = info,
onClick = null,
)
nextMap[circleState.id] = circleState
}
}
circleFlow.value = nextMap
}

MarkerRenderingGroup(strategy = strategy, content = content)
}

private const val CLUSTER_CIRCLE_ID_PREFIX = "cluster-circle-"
1 change: 1 addition & 0 deletions mapconductor-for-googlemaps/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ dependencies {
// Google Maps SDK
implementation(libs.play.services.maps)
implementation(project(":mapconductor-core"))
implementation(project(":mapconductor-marker-clustering"))

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,7 @@ private fun getPolylineController(holder: GoogleMapViewHolder): GoogleMapPolylin
return controller
}

private fun getMarkerController(
holder: GoogleMapViewHolder,
) = GoogleMapMarkerController.create(
holder = holder,
)
private fun getMarkerController(holder: GoogleMapViewHolder) =
GoogleMapMarkerController.create(
holder = holder,
)
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ import com.mapconductor.core.polyline.PolylineState
import com.mapconductor.googlemaps.circle.GoogleMapCircleController
import com.mapconductor.googlemaps.groundimage.GoogleMapGroundImageController
import com.mapconductor.googlemaps.marker.DefaultGoogleMapMarkerEventController
import com.mapconductor.googlemaps.marker.GoogleMapMarkerEventController
import com.mapconductor.googlemaps.marker.GoogleMapMarkerController
import com.mapconductor.googlemaps.marker.GoogleMapMarkerEventController
import com.mapconductor.googlemaps.polygon.GoogleMapPolygonController
import com.mapconductor.googlemaps.polyline.GoogleMapPolylineController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class GoogleMapViewControllerImpl(
Expand All @@ -59,6 +61,8 @@ class GoogleMapViewControllerImpl(
OnMarkerDragListener,
GoogleMap.OnMapLoadedCallback {
private val markerEventControllers = mutableListOf<GoogleMapMarkerEventController>()
private val _mapLoadedState = MutableStateFlow(false)
val mapLoadedState: StateFlow<Boolean> = _mapLoadedState
private var markerClickListener: OnMarkerEventHandler? = null
private var markerDragStartListener: OnMarkerEventHandler? = null
private var markerDragListener: OnMarkerEventHandler? = null
Expand Down Expand Up @@ -317,6 +321,7 @@ class GoogleMapViewControllerImpl(
}

override fun onMapLoaded() {
_mapLoadedState.value = true
mapLoadedCallback?.invoke()
mapLoadedCallback = null

Expand All @@ -325,14 +330,26 @@ class GoogleMapViewControllerImpl(
}

// Trigger an initial camera update after the view and map are ready
private var initialCameraUpdateAttempts = 0

fun sendInitialCameraUpdate() {
val w = holder.mapView.width
val h = holder.mapView.height
if (w <= 0 || h <= 0) return
if (w <= 0 || h <= 0) {
if (initialCameraUpdateAttempts >= INITIAL_CAMERA_UPDATE_MAX_ATTEMPTS) return
initialCameraUpdateAttempts += 1
holder.mapView.post { sendInitialCameraUpdate() }
return
}
initialCameraUpdateAttempts = 0
val mapCameraPosition = getMapCameraPosition()
backCoroutine.launch { notifyMapCameraPosition(mapCameraPosition) }
}

companion object {
private const val INITIAL_CAMERA_UPDATE_MAX_ATTEMPTS = 10
}

internal fun registerMarkerEventController(controller: GoogleMapMarkerEventController) {
if (markerEventControllers.contains(controller)) return
markerEventControllers.add(controller)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import com.mapconductor.googlemaps.GoogleMapViewHolder
import com.mapconductor.googlemaps.toLatLng
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class GoogleMapCircleOverlayRenderer(
Expand Down Expand Up @@ -43,7 +42,7 @@ class GoogleMapCircleOverlayRenderer(
}

override suspend fun removeCircle(entity: CircleEntity<GoogleMapActualCircle>) {
coroutine.launch {
withContext(coroutine.coroutineContext) {
entity.circle.remove()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ class GoogleMapMarkerController private constructor(
) : AbstractMarkerController<GoogleMapActualMarker>(
markerManager = markerManager,
renderer = renderer,
)
{

) {
override fun find(position: GeoPoint): MarkerEntity<GoogleMapActualMarker>? {
return markerManager.findNearest(position)?.let { nearest ->
val zoom =
Expand All @@ -39,9 +37,7 @@ class GoogleMapMarkerController private constructor(
}

companion object {
fun create(
holder: GoogleMapViewHolder,
): GoogleMapMarkerController {
fun create(holder: GoogleMapViewHolder): GoogleMapMarkerController {
val markerManager = MarkerManager.defaultManager<GoogleMapActualMarker>()
val renderer =
GoogleMapMarkerRenderer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,13 @@ class GoogleMapMarkerRenderer(
}

override suspend fun onRemove(data: List<MarkerEntity<GoogleMapActualMarker>>) {
coroutine.launch {
data.forEach { params -> params.marker?.remove() }
withContext(coroutine.coroutineContext) {
data.forEach { params ->
params.marker?.let { marker ->
marker.isVisible = false
marker.remove()
}
}
}
}

Expand Down
Loading