From 70b854123fe211203f4a8d0e046cf84e8574b589 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Sat, 2 Aug 2025 22:48:05 +0900 Subject: [PATCH 01/29] TempSave --- .../main/res/drawable/newark_nj_1922_0.jpg | Bin 0 -> 143740 bytes .../main/res/drawable/newark_nj_1922_1.jpg | Bin 0 -> 137215 bytes .../com/mapconductor/core/OverlayProvider.kt | 27 ++++- .../core/controller/MapViewController.kt | 29 ++++- .../core/groundimage/GroundImage.kt | 66 +++++++++++ .../core/groundimage/GroundImageComponent.kt | 35 ++++++ .../core/groundimage/GroundImageEntity.kt | 14 +++ .../groundimage/GroundImageOverlayManager.kt | 111 ++++++++++++++++++ .../core/groundimage/GroundImageRenderer.kt | 38 ++++++ .../com/mapconductor/core/map/MapViewBase.kt | 4 +- .../com/mapconductor/core/map/MapViewState.kt | 2 +- .../core/polyline/PolylineComponent.kt | 2 +- .../arcgis/ArcGISMapViewController.kt | 4 +- .../ArcGISGroundOverlayRenderer.kt | 13 ++ .../com/mapconductor/googlemaps/GeoRect.kt | 23 ++++ .../googlemaps/GoogleMapViewController.kt | 29 ++++- .../GoogleMapGroundImageRenderer.kt | 91 ++++++++++++++ .../here/HereMapViewController.kt | 27 ++++- .../groundimage/HereGroundImageRenderer.kt | 13 ++ .../mapbox/MapboxMapViewController.kt | 4 +- .../groundImage/MapboxGroundImageRenderer.kt | 13 ++ 21 files changed, 527 insertions(+), 18 deletions(-) create mode 100644 example-app/src/main/res/drawable/newark_nj_1922_0.jpg create mode 100644 example-app/src/main/res/drawable/newark_nj_1922_1.jpg create mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt create mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt create mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageEntity.kt create mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt create mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageRenderer.kt create mode 100644 mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundoverlay/ArcGISGroundOverlayRenderer.kt create mode 100644 mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoRect.kt create mode 100644 mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt create mode 100644 mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt create mode 100644 mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt diff --git a/example-app/src/main/res/drawable/newark_nj_1922_0.jpg b/example-app/src/main/res/drawable/newark_nj_1922_0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1f4ae595ae1da7d13a7f436546a8bea35ffd7795 GIT binary patch literal 143740 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 literal 0 HcmV?d00001 diff --git a/example-app/src/main/res/drawable/newark_nj_1922_1.jpg b/example-app/src/main/res/drawable/newark_nj_1922_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c025cf693f6bbbb015e7b5b5203ee9a302c897a3 GIT binary patch literal 137215 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 hdX; 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 I4~jqk+2w0Z2><{9 literal 0 HcmV?d00001 diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt b/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt index 27aa3d43..4b7c3028 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.compositionLocalOf import com.mapconductor.core.circle.CircleState import com.mapconductor.core.controller.MapViewController +import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.info.InfoBubbleEntry import com.mapconductor.core.map.MapOverlay import com.mapconductor.core.map.MapOverlayRegistry @@ -62,12 +63,14 @@ open class MapViewScope { val bubbleFlow = MutableStateFlow>(emptyList()) val polylineFlow = MutableStateFlow>(emptyList()) val circleFlow = MutableStateFlow>(emptyList()) + val groundImageFlow = MutableStateFlow>(emptyList()) fun buildRegistry(): MapOverlayRegistry { val registry = MapOverlayRegistry() registry.register(MarkerOverlay(markerFlow)) registry.register(CircleOverlay(circleFlow)) registry.register(PolylineOverlay(polylineFlow)) + registry.register(GroundImageOverlay(groundImageFlow)) return registry } } @@ -77,7 +80,7 @@ class MarkerOverlay( ) : MapOverlay { override suspend fun render( data: List, - controller: MapViewController<*, *, *>, + controller: MapViewController<*, *, *, *>, ) { controller.addMarkers(data) } @@ -88,7 +91,7 @@ class CircleOverlay( ) : MapOverlay { override suspend fun render( data: List, - controller: MapViewController<*, *, *>, + controller: MapViewController<*, *, *, *>, ) { controller.addCircles(data) } @@ -104,12 +107,23 @@ class PolylineOverlay( ) : MapOverlay { override suspend fun render( data: List, - controller: MapViewController<*, *, *>, + controller: MapViewController<*, *, *, *>, ) { controller.addPolylines(data) } } +class GroundImageOverlay( + override val flow: StateFlow>, +) : MapOverlay { + override suspend fun render( + data: List, + controller: MapViewController<*, *, *, *>, + ) { + controller.addGroundImages(data) + } +} + val LocalPolylineCollector = compositionLocalOf>> { error("Polyline must be under the ") @@ -120,10 +134,15 @@ val LocalCircleCollector = error("Circle must be under the ") } +val LocalGroundImageCollector = + compositionLocalOf>> { + error("GroundImage must be under the ") + } + @Composable fun CollectAndRenderOverlays( registry: MapOverlayRegistry, - controller: MapViewController<*, *, *>, + controller: MapViewController<*, *, *, *>, ) { registry.getAll().forEach { overlay -> @Suppress("UNCHECKED_CAST") diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt b/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt index 6f1a0d73..2dfc0372 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt @@ -7,6 +7,9 @@ import com.mapconductor.core.features.IGeoPoint import com.mapconductor.core.geocell.HexCell import com.mapconductor.core.geocell.HexCoord import com.mapconductor.core.geocell.HexGeocell +import com.mapconductor.core.groundimage.GroundImageOverlayManager +import com.mapconductor.core.groundimage.GroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapViewHolder import com.mapconductor.core.map.OnCameraMoveHandler import com.mapconductor.core.map.OnMapEventHandler @@ -19,13 +22,14 @@ import com.mapconductor.core.polyline.PolylineRenderer import com.mapconductor.core.polyline.PolylineState import kotlinx.coroutines.CoroutineScope -interface MapViewController { +interface MapViewController { val holder: MapViewHolder<*, *> val coroutine: CoroutineScope val markerOverlayManager: MarkerOverlayManager val hexGeocell: HexGeocell val circleManager: CircleManager val polylineOverlayManager: PolylineOverlayManager + val groundImageOverlayManager: GroundImageOverlayManager suspend fun addMarkers(data: List) @@ -35,6 +39,10 @@ interface MapViewController { suspend fun updatePolyline(state: PolylineState) + suspend fun addGroundImages(data: List) + + suspend fun updateGroundImage(state: GroundImageState) + suspend fun addCircles(data: List) suspend fun updateCircle(state: CircleState) @@ -60,8 +68,8 @@ data class SearchRangeAnalysis( val markersInRange: List, ) -abstract class BaseMapViewController : - MapViewController { +abstract class BaseMapViewController : + MapViewController { abstract val markerRenderer: MarkerRenderer override val markerOverlayManager: MarkerOverlayManager by lazy { @@ -80,6 +88,15 @@ abstract class BaseMapViewController + + override val groundImageOverlayManager: GroundImageOverlayManager by lazy { + createGroundImageOverlayManager().also { overlayManager -> + groundImageRenderer.init(overlayManager) + onGroundImageOverlayManagerInitialized(overlayManager) + } + } + protected open fun onMarkerOverlayManagerInitialized(overlayManager: MarkerOverlayManager) { // Stub } @@ -88,10 +105,16 @@ abstract class BaseMapViewController) { + // Stub + } + protected abstract fun createMarkerOverlayManager(): MarkerOverlayManager protected abstract fun createPolylineOverlayManager(): PolylineOverlayManager + protected abstract fun createGroundImageOverlayManager(): GroundImageOverlayManager + var cameraMoveListener: (OnCameraMoveHandler)? = null var mapClickListener: OnMapEventHandler? = null var mapLongClickListener: OnMapEventHandler? = null diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt new file mode 100644 index 00000000..9cea665d --- /dev/null +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt @@ -0,0 +1,66 @@ +package com.mapconductor.core.groundimage + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import android.os.Parcelable +import com.mapconductor.core.StateFlowDelegate +import com.mapconductor.core.features.GeoRectBounds +import android.graphics.drawable.Drawable +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged + +class GroundImageState( + bounds: GeoRectBounds, + image: Drawable, + alpha: Float = 1.0f, + id: String? = null, + extra: Parcelable? = null, +) { + val id = (id ?: generateId(bounds, image, alpha, extra)).toString() + + var bounds by StateFlowDelegate(bounds) + var image by mutableStateOf(image) + var alpha by mutableStateOf(alpha) + var extra by mutableStateOf(extra) + + fun fingerPrint(): GroundImageFingerPrint = GroundImageFingerPrint( + id = id.hashCode(), + bounds = bounds.hashCode(), + image = image.hashCode(), + alpha = alpha.hashCode(), + extra = extra?.hashCode() ?: 0, + ) + + fun asFlow(): Flow = + snapshotFlow { fingerPrint() }.distinctUntilChanged() + + private fun generateId( + bounds: GeoRectBounds, + image: Drawable, + alpha: Float, + extra: Parcelable? + ): Int { + var result = bounds.hashCode() + result = 31 * result + image.hashCode() + result = 31 * result + alpha.hashCode() + result = 31 * result + (extra?.hashCode() ?: 0) + return result + } + + override fun equals(other: Any?): Boolean = + (other as? GroundImageState)?.hashCode() == this.hashCode() + + override fun hashCode(): Int = fingerPrint().hashCode() +} + +data class GroundImageFingerPrint( + val id: Int, + val bounds: Int, + val image: Int, + val alpha: Int, + val extra: Int, +) + +typealias OnGroundImageEventHandler = (GroundImageState) -> Unit diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt new file mode 100644 index 00000000..5c65e824 --- /dev/null +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt @@ -0,0 +1,35 @@ +package com.mapconductor.core.groundimage + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember +import com.mapconductor.core.MapViewScope +import com.mapconductor.core.features.GeoRectBounds +import android.graphics.drawable.Drawable +import android.os.Parcelable + +@Composable +fun MapViewScope.GroundImage(state: GroundImageState) { + val rememberState = remember { state } + SideEffect { + groundImageFlow.value += rememberState + } +} + +@Composable +fun MapViewScope.GroundImage( + bounds: GeoRectBounds, + image: Drawable, + alpha: Float = 0.5f, + id: String? = null, + extra: Parcelable? = null, +) { + val state = GroundImageState( + bounds = bounds, + image = image, + alpha = alpha, + id = id, + extra = extra, + ) + GroundImage(state) +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageEntity.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageEntity.kt new file mode 100644 index 00000000..ab2e4f13 --- /dev/null +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageEntity.kt @@ -0,0 +1,14 @@ +package com.mapconductor.core.groundimage + +interface GroundImageEntity { + val groundImage: ActualGroundImage + val state: GroundImageState + val stateHashCode: Int +} + +data class GroundImageEntityImpl( + override val groundImage: ActualGroundImage, + override val state: GroundImageState, +) : GroundImageEntity { + override val stateHashCode: Int = state.hashCode() +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt new file mode 100644 index 00000000..08a03dd4 --- /dev/null +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt @@ -0,0 +1,111 @@ +package com.mapconductor.core.groundimage + +import kotlinx.coroutines.sync.Semaphore + +interface GroundImageOverlayManager { + suspend fun addGroundImages(overlays: List) + + suspend fun updateGroundImage(overlay: GroundImageState) + + suspend fun clearGroundImages() + + fun getGroundImageState(id: String): GroundImageState? + + fun getAllEntities(): List> +} + +class GroundImageOverlayManagerImpl( + val onAdd: suspend (List) -> List, + val onChange: suspend (List>) -> List, + + val onRemove: suspend (List>) -> Unit, + val onPostProcess: (suspend () -> Unit)? = null, +) : GroundImageOverlayManager { + +val groundImageEntities = mutableMapOf>() + val semaphore = Semaphore(1) + + override suspend fun addGroundImages(images: List) { + semaphore.acquire() + val previous = groundImageEntities.keys.toMutableSet() + val added = mutableListOf() + val updated = mutableListOf>() + val removed = mutableListOf>() + + images.forEach { + if (previous.contains(it.id)) { + val prevEntity = groundImageEntities[it.id]!! + updated.add(object : GroundImageRenderer.UpdateParams { + override val entity = GroundImageEntityImpl( + groundImage = prevEntity.groundImage, + state = it + ) + override val prevEntity = prevEntity + }) + previous.remove(it.id) + } else { + added.add(it) + previous.remove(it.id) + } + } + + previous.forEach { remainId -> + groundImageEntities.remove(remainId)?.let { removedEntity -> + removed.add(removedEntity) + } + } + + if (added.isNotEmpty()) { + val actualOverlays = onAdd(added) + actualOverlays.forEachIndexed { index, actualOverlay -> + actualOverlay?.let { + val state = added[index] + val entity = GroundImageEntityImpl( + groundImage = it, + state = state + ) + groundImageEntities[state.id] = entity + } + } + } + + if (updated.isNotEmpty()) { + val actualOverlays = onChange(updated.toList()) + actualOverlays.forEachIndexed { index, actualOverlay -> + actualOverlay?.let { + val state = updated[index].entity.state + val entity = GroundImageEntityImpl( + groundImage = it, + state = state + ) + groundImageEntities[state.id] = entity + } + } + } + + if (removed.isNotEmpty()) { + onRemove(removed) + } + + onPostProcess?.invoke() + semaphore.release() + } + + override suspend fun updateGroundImage(overlay: GroundImageState) { + semaphore.acquire() + // Implement fine-grained update logic if needed + semaphore.release() + } + + override suspend fun clearGroundImages() { + semaphore.acquire() + val entities = groundImageEntities.values.toList() + onRemove(entities) + groundImageEntities.clear() + semaphore.release() + } + + override fun getGroundImageState(id: String): GroundImageState? = groundImageEntities[id]?.state + + override fun getAllEntities(): List> = groundImageEntities.values.toList() +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageRenderer.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageRenderer.kt new file mode 100644 index 00000000..d123fe47 --- /dev/null +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageRenderer.kt @@ -0,0 +1,38 @@ +package com.mapconductor.core.groundimage + +import com.mapconductor.core.map.MapViewHolder +import kotlinx.coroutines.CoroutineScope + +interface GroundImageRendererFactory { + fun create( + onAdd: suspend (List) -> List, + onChange: suspend (List>) -> List, + onRemove: suspend (List>) -> Unit, + onPostProcess: (suspend () -> Unit)? = null, + ): GroundImageOverlayManager +} + +interface GroundImageRenderer { + interface UpdateParams { + val entity: GroundImageEntity + val prevEntity: GroundImageEntity + } + + fun init(groundImageOverlayManager: GroundImageOverlayManager) + + suspend fun addGroundImages(newGroundImages: List): List + + suspend fun removeGroundImages(removeEntities: List>) + + suspend fun changeGroundImages(changes: List>): List +} + +abstract class AbstractGroundImageRenderer : GroundImageRenderer { + protected lateinit var groundImageOverlayManager: GroundImageOverlayManager + abstract val holder: MapViewHolder<*, *> + abstract val coroutine: CoroutineScope + + override fun init(groundImageOverlayManager: GroundImageOverlayManager) { + this.groundImageOverlayManager = groundImageOverlayManager + } +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt index d05185a3..4b8c1fa7 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt @@ -25,6 +25,7 @@ import com.mapconductor.core.CollectAndRenderOverlays import com.mapconductor.core.LocalCircleCollector import com.mapconductor.core.LocalMarkerCollector import com.mapconductor.core.LocalPolylineCollector +import com.mapconductor.core.LocalGroundImageCollector import com.mapconductor.core.MapViewScope import com.mapconductor.core.ResourceProvider import com.mapconductor.core.controller.MapViewController @@ -47,7 +48,7 @@ fun < SpecificState : MapViewState<*>, // Replace Any with a base MapViewController if you have one // Generic type for the actual Android Map View (e.g., com.google.android.gms.maps.MapView) - SpecificController : MapViewController<*, *, *>, + SpecificController : MapViewController<*, *, *, *>, ActualMapView : View, // Generic type for the actual Map SDK object (e.g., GoogleMap, HereMapSDK.MapController) ActualMap : Any, @@ -86,6 +87,7 @@ fun < LocalInfoBubbleCollector provides scope.bubbleFlow, LocalCircleCollector provides scope.circleFlow, LocalPolylineCollector provides scope.polylineFlow, + LocalGroundImageCollector provides scope.groundImageFlow, ) { with(scope) { content?.invoke(this) diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt index a8d0af2b..05150d1e 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt @@ -87,7 +87,7 @@ interface MapOverlay { suspend fun render( data: List, - controller: MapViewController<*, *, *>, + controller: MapViewController<*, *, *, *>, ) } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/polyline/PolylineComponent.kt b/mapconductor-core/src/main/java/com/mapconductor/core/polyline/PolylineComponent.kt index b1860d50..07b9f380 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/polyline/PolylineComponent.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/polyline/PolylineComponent.kt @@ -14,7 +14,7 @@ import android.os.Parcelable fun MapViewScope.Polyline(state: PolylineState) { val rememberState = remember { state } SideEffect { - polylineFlow.value = polylineFlow.value + rememberState + polylineFlow.value = polylineFlow.value + rememberState } } diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt index 063cd3f4..4247ea67 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt @@ -46,7 +46,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -interface IArcGISMapViewController : MapViewController { +interface IArcGISMapViewController : MapViewController { fun moveCamera( dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback? = null, @@ -88,7 +88,7 @@ class ArcGISMapViewController( private val polylineRendererFactory: PolylineRendererFactory = DefaultArcGISPolylineRenderer(), override val circleManager: CircleManager = CircleManager(), -) : BaseMapViewController(), +) : BaseMapViewController(), IArcGISMapViewController { override val markerRenderer: MarkerRenderer = ArcGISMarkerRenderer( diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundoverlay/ArcGISGroundOverlayRenderer.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundoverlay/ArcGISGroundOverlayRenderer.kt new file mode 100644 index 00000000..a9152d4e --- /dev/null +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundoverlay/ArcGISGroundOverlayRenderer.kt @@ -0,0 +1,13 @@ +package com.mapconductor.arcgis.groundoverlay + +import com.mapconductor.core.groundimage.GroundImageState + +class ArcGISGroundImageRenderer { + fun addOverlay(state: GroundImageState) { + // Not implemented + } + + fun updateOverlay(state: GroundImageState) { + // Not implemented + } +} diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoRect.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoRect.kt new file mode 100644 index 00000000..e212758f --- /dev/null +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoRect.kt @@ -0,0 +1,23 @@ +package com.mapconductor.googlemaps + +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.mapconductor.core.features.GeoPoint +import com.mapconductor.core.features.GeoRectBounds + +fun GeoRectBounds.toLatLngBounds(): LatLngBounds? { + val sw = southWest ?: return null + val ne = northEast ?: return null + + return LatLngBounds( + LatLng(sw.latitude, sw.longitude), + LatLng(ne.latitude, ne.longitude) + ) +} + +fun LatLngBounds.toGeoRectBounds(): GeoRectBounds { + return GeoRectBounds( + southWest = GeoPoint(southwest.latitude, southwest.longitude), + northEast = GeoPoint(northeast.latitude, northeast.longitude) + ) +} diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt index 4bd2a67f..257c40d1 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt @@ -14,6 +14,7 @@ import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.Circle import com.google.android.gms.maps.model.CircleOptions +import com.google.android.gms.maps.model.GroundOverlay import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.Marker import com.google.android.gms.maps.model.Polyline @@ -24,6 +25,10 @@ import com.mapconductor.core.controller.BaseMapViewController import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.geocell.HexGeocell +import com.mapconductor.core.groundimage.GroundImageOverlayManager +import com.mapconductor.core.groundimage.GroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageRendererFactory +import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewState import com.mapconductor.core.marker.MarkerOverlayManager @@ -35,6 +40,8 @@ import com.mapconductor.core.polyline.PolylineRenderer import com.mapconductor.core.polyline.PolylineRendererFactory import com.mapconductor.core.polyline.PolylineState import com.mapconductor.core.projection.WebMercator +import com.mapconductor.googlemaps.groundimage.DefaultGoogleMapGroundImageRenderer +import com.mapconductor.googlemaps.groundimage.GoogleMapGroundImageRenderer import com.mapconductor.googlemaps.marker.DefaultGoogleMapMarkerRenderer import com.mapconductor.googlemaps.marker.GoogleMapMarkerRenderer import com.mapconductor.googlemaps.polyline.DefaultGoogleMapPolylineRenderer @@ -43,7 +50,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -interface IGoogleMapViewController : MapViewController { +interface IGoogleMapViewController : MapViewController { fun moveCamera( dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback? = null, @@ -67,7 +74,8 @@ class GoogleMapViewController( private val markerRendererFactory: MarkerRendererFactory = DefaultGoogleMapMarkerRenderer(), private val polylineRendererFactory: PolylineRendererFactory = DefaultGoogleMapPolylineRenderer(), override val circleManager: CircleManager = CircleManager(), -) : BaseMapViewController(), + private val groundImageRendererFactory: GroundImageRendererFactory = DefaultGoogleMapGroundImageRenderer(), +) : BaseMapViewController(), IGoogleMapViewController, OnCameraMoveStartedListener, OnCameraMoveCanceledListener, @@ -107,6 +115,19 @@ class GoogleMapViewController( onRemove = polylineRenderer::removeLines, ) + override val groundImageRenderer: GroundImageRenderer = + GoogleMapGroundImageRenderer( + holder = holder, + coroutine = coroutine, + ) + + override fun createGroundImageOverlayManager(): GroundImageOverlayManager = + groundImageRendererFactory.create( + onAdd = groundImageRenderer::addGroundImages, + onChange = groundImageRenderer::changeGroundImages, + onRemove = groundImageRenderer::removeGroundImages, + ) + init { setupListeners() markerRenderer.init(markerOverlayManager) @@ -192,6 +213,10 @@ class GoogleMapViewController( override suspend fun updatePolyline(state: PolylineState) = polylineOverlayManager.updatePolyline(state) + override suspend fun addGroundImages(data: List) = groundImageOverlayManager.addGroundImages(data) + + override suspend fun updateGroundImage(state: GroundImageState) = groundImageOverlayManager.updateGroundImage(state) + override fun onCameraMove() { cameraMoveListener?.let { coroutine.launch { it(holder.map.cameraPosition) } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt new file mode 100644 index 00000000..471c92cb --- /dev/null +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt @@ -0,0 +1,91 @@ +package com.mapconductor.googlemaps.groundimage + +import androidx.core.graphics.drawable.toBitmap +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.GroundOverlay +import com.google.android.gms.maps.model.GroundOverlayOptions +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.mapconductor.core.features.GeoRectBounds +import com.mapconductor.core.groundimage.AbstractGroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageEntity +import com.mapconductor.core.groundimage.GroundImageOverlayManager +import com.mapconductor.core.groundimage.GroundImageOverlayManagerImpl +import com.mapconductor.core.groundimage.GroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageRendererFactory +import com.mapconductor.core.groundimage.GroundImageState +import com.mapconductor.googlemaps.GoogleMapViewHolder +import com.mapconductor.googlemaps.toLatLngBounds +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class DefaultGoogleMapGroundImageRenderer : GroundImageRendererFactory { + override fun create( + onAdd: suspend (List) -> List, + onChange: suspend (List>) -> List, + onRemove: suspend (List>) -> Unit, + onPostProcess: (suspend () -> Unit)? + ): GroundImageOverlayManager = + GroundImageOverlayManagerImpl( + onRemove = onRemove, + onAdd = onAdd, + onChange = onChange, + onPostProcess = onPostProcess, + ) +} + +class GoogleMapGroundImageRenderer( + override val holder: GoogleMapViewHolder, + override val coroutine: CoroutineScope, +) : AbstractGroundImageRenderer() { + override suspend fun addGroundImages(newImages: List): List { + return withContext(coroutine.coroutineContext) { + return@withContext newImages.map { state -> + val bounds = state.bounds.toLatLngBounds() ?: return@withContext emptyList() + val image = BitmapDescriptorFactory.fromBitmap(state.image.toBitmap()) + val alpha = state.alpha + val options = + GroundOverlayOptions() + .image(image) + .positionFromBounds(bounds) + .transparency(alpha) + holder.map.addGroundOverlay(options)?.also { + it.tag = state.id + } + } + } + } + + override suspend fun removeGroundImages(removeEntities: List>) { + coroutine.launch { + removeEntities.forEach { params -> params.groundImage.remove() } + } + } + + override suspend fun changeGroundImages(changes: List>): List { + return withContext(coroutine.coroutineContext) { + return@withContext changes.map { params-> + val groundOverlay = params.entity.groundImage + val finger = params.entity.state.fingerPrint() + val prevFinger = params.prevEntity.fingerPrint + if (finger.bounds != prevFinger.bounds) { + val bounds = params.entity.state.bounds.map { GeoRectBounds.toLatLngBounds() } + groundOverlay.position = bounds + } + groundOverlay.transparency = params.prevEntity.state.alpha + groundOverlay.setImage(BitmapDescriptorFactory.fromBitmap(params.entity.state.image.toBitmap()) + return@map groundOverlay + } + } + } + + private fun GeoRectBounds.toLatLngBounds(): LatLngBounds? { + val sw = southWest ?: return null + val ne = northEast ?: return null + return LatLngBounds( + LatLng(sw.latitude, sw.longitude), + LatLng(ne.latitude, ne.longitude) + ) + } +} diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt index f03825c2..aba62fc1 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt @@ -13,6 +13,7 @@ import com.here.sdk.mapview.MapMarker import com.here.sdk.mapview.MapMeasure import com.here.sdk.mapview.MapPolygon import com.here.sdk.mapview.MapPolyline +import com.here.sdk.mapview.MapImage import com.here.sdk.mapview.MapScene import com.here.sdk.mapview.MapView import com.here.time.Duration @@ -23,6 +24,10 @@ import com.mapconductor.core.controller.BaseMapViewController import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.geocell.HexGeocell +import com.mapconductor.core.groundimage.GroundImageOverlayManager +import com.mapconductor.core.groundimage.GroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageRendererFactory +import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewHolder import com.mapconductor.core.map.MapViewState.MoveCameraCallback @@ -45,7 +50,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -interface IHereMapViewController : MapViewController { +interface IHereMapViewController : MapViewController { fun moveCamera( dstPosition: MapCameraPosition, listener: MoveCameraCallback? = null, @@ -68,8 +73,9 @@ class HereMapViewController( ), private val markerRendererFactory: MarkerRendererFactory = DefaultHereMapMarkerRenderer(), private val polylineRendererFactory: PolylineRendererFactory = DefaultHereMapPolylineRenderer(), + private val groundImageRendererFactory: GroundImageRendererFactory = DefaultHereMapGroundImageRenderer(), override val circleManager: CircleManager = CircleManager(), -) : BaseMapViewController(), +) : BaseMapViewController(), IHereMapViewController, MapCameraListener, TapListener, @@ -104,6 +110,19 @@ class HereMapViewController( onRemove = polylineRenderer::removeLines, ) + override val groundImageRenderer: GroundImageRenderer = + HereMapGroundImageRenderer( + holder = holder, + coroutine = coroutine, + ) + + override fun createGroundImageOverlayManager(): GroundImageOverlayManager = + groundImageRenderer.create( + onAdd = groundImageRenderer::addLines, + onChange = groundImageRenderer::changeLine, + onRemove = groundImageRenderer::removeLines, + ) + override suspend fun clearOverlays() { markerOverlayManager.clearOverlays() polylineOverlayManager.clearOverlays() @@ -123,6 +142,10 @@ class HereMapViewController( override suspend fun updatePolyline(state: PolylineState) = polylineOverlayManager.updatePolyline(state) + override suspend fun addGroundImages(data: List) = groundImageOverlayManager.addGroundImages(data) + + override suspend fun updateGroundImage(state: GroundImageState) = groundImageOverlayManager.updateGroundImage(state) + init { setupListeners() markerRenderer.init(markerOverlayManager) diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt new file mode 100644 index 00000000..020d94fe --- /dev/null +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt @@ -0,0 +1,13 @@ +package com.mapconductor.here.groundimage + +import com.mapconductor.core.groundimage.GroundImageState + +class HereGroundImageRenderer { + fun addOverlay(state: GroundImageState) { + // Not implemented + } + + fun updateOverlay(state: GroundImageState) { + // Not implemented + } +} diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt index efdef546..008b00f9 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt @@ -57,7 +57,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore -interface IMapboxMapViewController : MapViewController { +interface IMapboxMapViewController : MapViewController { fun moveCamera( dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback? = null, @@ -103,7 +103,7 @@ internal class MapboxMapViewController( layerId = "polyline-layer", ), override val circleManager: CircleManager = CircleManager(), -) : BaseMapViewController(), +) : BaseMapViewController(), IMapboxMapViewController, CameraChangedCallback, OnMapClickListener, diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt new file mode 100644 index 00000000..d5d564d9 --- /dev/null +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt @@ -0,0 +1,13 @@ +package com.mapconductor.mapbox.groundImage + +import com.mapconductor.core.groundimage.GroundImageState + +class MapboxGroundImageRenderer { + fun addOverlay(state: GroundImageState) { + // Not implemented + } + + fun updateOverlay(state: GroundImageState) { + // Not implemented + } +} From 19257b4abb8b763775da3a5551a425fd9f1bea30 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Mon, 4 Aug 2025 22:05:58 +0900 Subject: [PATCH 02/29] TempSave3 --- .../com/mapconductor/core/OverlayProvider.kt | 2 +- .../core/controller/MapViewController.kt | 3 +- .../core/groundimage/GroundImageEntity.kt | 4 +- .../arcgis/ArcGISMapViewController.kt | 25 ++++++++++ .../mapconductor/arcgis/ArcGISTypeAlias.kt | 1 + .../ArcGISGroundOverlayRenderer.kt | 22 +++++++-- .../GoogleMapGroundImageRenderer.kt | 7 +-- .../com/mapconductor/here/HereMapTypeAlias.kt | 2 + .../here/HereMapViewController.kt | 38 ++++++++++++-- .../groundimage/HereGroundImageRenderer.kt | 49 +++++++++++++++++-- .../mapbox/MapboxMapViewController.kt | 25 ++++++++++ .../mapconductor/mapbox/MapboxTypeAlias.kt | 1 + .../groundImage/MapboxGroundImageRenderer.kt | 22 +++++++-- 13 files changed, 175 insertions(+), 26 deletions(-) diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt b/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt index 7f160ac6..917b804b 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt @@ -121,7 +121,7 @@ class GroundImageOverlay( ) : MapOverlay { override suspend fun render( data: List, - controller: MapViewController<*, *, *, *>, + controller: MapViewControllerAlias, ) { controller.addGroundImages(data) } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt b/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt index 15bcff5d..db206e64 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt @@ -100,6 +100,7 @@ abstract class BaseMapViewController override val groundImageOverlayManager: GroundImageOverlayManager by lazy { @@ -109,8 +110,6 @@ abstract class BaseMapViewController) - abstract val circleRenderer: CircleRenderer override val circleOverlayManager: CircleOverlayManager by lazy { diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageEntity.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageEntity.kt index ab2e4f13..f8b6e8fd 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageEntity.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageEntity.kt @@ -3,12 +3,12 @@ package com.mapconductor.core.groundimage interface GroundImageEntity { val groundImage: ActualGroundImage val state: GroundImageState - val stateHashCode: Int + val fingerPrint: GroundImageFingerPrint } data class GroundImageEntityImpl( override val groundImage: ActualGroundImage, override val state: GroundImageState, ) : GroundImageEntity { - override val stateHashCode: Int = state.hashCode() + override val fingerPrint: GroundImageFingerPrint = state.fingerPrint() } diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt index cdeac514..2bf72246 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt @@ -11,6 +11,7 @@ import com.arcgismaps.mapping.view.UpEvent import com.arcgismaps.mapping.view.extensions.motionEvent import com.mapconductor.arcgis.circle.ArcGISCircleRenderer import com.mapconductor.arcgis.circle.DefaultArcGISCircleRenderer +import com.mapconductor.arcgis.groundoverlay.ArcGISGroundImageRenderer import com.mapconductor.arcgis.marker.ArcGISMarkerRenderer import com.mapconductor.arcgis.marker.DefaultArcGISMarkerRender import com.mapconductor.arcgis.polygon.ArcGISPolygonRenderer @@ -26,6 +27,9 @@ import com.mapconductor.core.circle.CircleState import com.mapconductor.core.controller.BaseMapViewController import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.geocell.HexGeocell +import com.mapconductor.core.groundimage.GroundImageOverlayManager +import com.mapconductor.core.groundimage.GroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewState import com.mapconductor.core.marker.MarkerOverlayManager @@ -52,6 +56,7 @@ interface IArcGISMapViewController : ArcGISActualCircle, ArcGISActualPolyline, ArcGISActualPolygon, + ArcGISActualGroundImage, > { fun moveCamera( dstPosition: MapCameraPosition, @@ -107,6 +112,7 @@ class ArcGISMapViewController( ArcGISActualCircle, ArcGISActualPolyline, ArcGISActualPolygon, + ArcGISActualGroundImage, >(), IArcGISMapViewController { override val markerRenderer: MarkerRenderer = @@ -162,6 +168,10 @@ class ArcGISMapViewController( onRemove = circleRenderer::removeCircles, ) + override fun createGroundImageOverlayManager(): GroundImageOverlayManager { + TODO("Not yet implemented") + } + override val circleRenderer: CircleRenderer = ArcGISCircleRenderer( circleLayer = circleLayer, @@ -169,6 +179,14 @@ class ArcGISMapViewController( coroutine = coroutine, ) + override fun onGroundImageOverlayManagerInitialized(overlayManager: GroundImageOverlayManager) { + } + + override val groundImageRenderer: GroundImageRenderer = ArcGISGroundImageRenderer( + holder = holder, + coroutine = coroutine, + ) + override fun onCircleOverlayManagerInitialized(overlayManager: CircleOverlayManager) { } @@ -333,6 +351,13 @@ class ArcGISMapViewController( override suspend fun addPolylines(data: List) = polylineOverlayManager.addPolylines(data) override suspend fun updatePolyline(state: PolylineState) = polylineOverlayManager.updatePolyline(state) + override suspend fun addGroundImages(data: List) { + TODO("Not yet implemented") + } + + override suspend fun updateGroundImage(state: GroundImageState) { + TODO("Not yet implemented") + } override suspend fun addCircles(data: List) = circleOverlayManager.addCircles(data) diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISTypeAlias.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISTypeAlias.kt index a6f35e5d..4a216b4e 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISTypeAlias.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISTypeAlias.kt @@ -6,3 +6,4 @@ typealias ArcGISActualMarker = Graphic typealias ArcGISActualCircle = Graphic typealias ArcGISActualPolyline = Graphic typealias ArcGISActualPolygon = Graphic +typealias ArcGISActualGroundImage = Graphic diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundoverlay/ArcGISGroundOverlayRenderer.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundoverlay/ArcGISGroundOverlayRenderer.kt index a9152d4e..483d019d 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundoverlay/ArcGISGroundOverlayRenderer.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundoverlay/ArcGISGroundOverlayRenderer.kt @@ -1,13 +1,25 @@ package com.mapconductor.arcgis.groundoverlay +import com.arcgismaps.mapping.view.Graphic +import com.mapconductor.arcgis.ArcGISMapViewHolder +import com.mapconductor.core.groundimage.AbstractGroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageEntity +import com.mapconductor.core.groundimage.GroundImageRenderer import com.mapconductor.core.groundimage.GroundImageState +import kotlinx.coroutines.CoroutineScope -class ArcGISGroundImageRenderer { - fun addOverlay(state: GroundImageState) { - // Not implemented +class ArcGISGroundImageRenderer( + override val holder: ArcGISMapViewHolder, + override val coroutine: CoroutineScope + ) : AbstractGroundImageRenderer() { + override suspend fun addGroundImages(newGroundImages: List): List { + return emptyList() } - fun updateOverlay(state: GroundImageState) { - // Not implemented + override suspend fun changeGroundImages(changes: List>): List { + return emptyList() + } + + override suspend fun removeGroundImages(removeEntities: List>) { } } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt index 471c92cb..b7eb65c6 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt @@ -70,11 +70,12 @@ class GoogleMapGroundImageRenderer( val finger = params.entity.state.fingerPrint() val prevFinger = params.prevEntity.fingerPrint if (finger.bounds != prevFinger.bounds) { - val bounds = params.entity.state.bounds.map { GeoRectBounds.toLatLngBounds() } - groundOverlay.position = bounds + params.entity.state.bounds.toLatLngBounds()?.let{ + groundOverlay.setPositionFromBounds(it) + } } groundOverlay.transparency = params.prevEntity.state.alpha - groundOverlay.setImage(BitmapDescriptorFactory.fromBitmap(params.entity.state.image.toBitmap()) + groundOverlay.setImage(BitmapDescriptorFactory.fromBitmap(params.entity.state.image.toBitmap())) return@map groundOverlay } } diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapTypeAlias.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapTypeAlias.kt index dc757271..7151aecb 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapTypeAlias.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapTypeAlias.kt @@ -1,5 +1,6 @@ package com.mapconductor.here +import com.here.sdk.mapview.MapImage import com.here.sdk.mapview.MapMarker import com.here.sdk.mapview.MapPolygon import com.here.sdk.mapview.MapPolyline @@ -8,3 +9,4 @@ typealias HereMapActualMarker = MapMarker typealias HereMapActualCircle = MapPolygon typealias HereMapActualPolyline = MapPolyline typealias HereMapActualPolygon = MapPolygon +typealias HereMapActualGroundImage = MapImage diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt index e7472eee..0368f990 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt @@ -10,6 +10,7 @@ import com.here.sdk.mapview.MapCamera import com.here.sdk.mapview.MapCameraAnimationFactory import com.here.sdk.mapview.MapCameraListener import com.here.sdk.mapview.MapCameraUpdateFactory +import com.here.sdk.mapview.MapImage import com.here.sdk.mapview.MapMarker import com.here.sdk.mapview.MapMeasure import com.here.sdk.mapview.MapPolygon @@ -25,6 +26,10 @@ import com.mapconductor.core.controller.BaseMapViewController import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.geocell.HexGeocell +import com.mapconductor.core.groundimage.GroundImageOverlayManager +import com.mapconductor.core.groundimage.GroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageRendererFactory +import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewHolder import com.mapconductor.core.map.MapViewState.MoveCameraCallback @@ -42,6 +47,8 @@ import com.mapconductor.core.polyline.PolylineState import com.mapconductor.core.projection.WebMercator import com.mapconductor.here.circle.DefaultHereMapCircleRenderer import com.mapconductor.here.circle.HereMapCircleRenderer +import com.mapconductor.here.groundimage.DefaultHereMapGroundImageRenderer +import com.mapconductor.here.groundimage.HereMapGroundImageRenderer import com.mapconductor.here.marker.DefaultHereMapMarkerRenderer import com.mapconductor.here.marker.HereMapMarkerRenderer import com.mapconductor.here.polygon.DefaultHereMapPolygonRenderer @@ -53,7 +60,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -interface IHereMapViewController : MapViewController { +interface IHereMapViewController : MapViewController { fun moveCamera( dstPosition: MapCameraPosition, listener: MoveCameraCallback? = null, @@ -75,16 +82,17 @@ class HereMapViewController( baseHexSideLength = 100000, // 100km - 中ズームレベルに適した値 ), private val markerRendererFactory: MarkerRendererFactory = DefaultHereMapMarkerRenderer(), - private val polylineRendererFactory: PolylineRendererFactory = - DefaultHereMapPolylineRenderer(), + private val polylineRendererFactory: PolylineRendererFactory = DefaultHereMapPolylineRenderer(), private val polygonRendererFactory: PolygonRendererFactory = DefaultHereMapPolygonRenderer(), private val circleRendererFactory: CircleRendererFactory = DefaultHereMapCircleRenderer(), + private val groundImageRendererFactory: GroundImageRendererFactory = DefaultHereMapGroundImageRenderer(), ) : BaseMapViewController< MapCamera.State, HereMapActualMarker, HereMapActualCircle, HereMapActualPolyline, HereMapActualPolygon, + HereMapActualGroundImage, >(), IHereMapViewController, MapCameraListener, @@ -129,6 +137,10 @@ class HereMapViewController( coroutine = coroutine, ) + override val groundImageRenderer: GroundImageRenderer = HereMapGroundImageRenderer( + holder = holder, + coroutine = coroutine, + ) override fun onCircleOverlayManagerInitialized(overlayManager: CircleOverlayManager) { } @@ -141,6 +153,9 @@ class HereMapViewController( override fun onMarkerOverlayManagerInitialized(overlayManager: MarkerOverlayManager) { } + override fun onGroundImageOverlayManagerInitialized(overlayManager: GroundImageOverlayManager) { + } + override fun createPolylineOverlayManager(): PolylineOverlayManager = polylineRendererFactory.create( onAdd = polylineRenderer::addLines, @@ -162,6 +177,15 @@ class HereMapViewController( onRemove = circleRenderer::removeCircles, ) + override fun createGroundImageOverlayManager(): GroundImageOverlayManager { + groundImageRendererFactory.create( + onAdd = groundImageRenderer::addGroundImages, + onChange = groundImageRenderer::changeGroundImages, + onRemove = groundImageRenderer::removeGroundImages, + ) + TODO("Not yet implemented") + } + override suspend fun clearOverlays() { markerOverlayManager.clearOverlays() polylineOverlayManager.clearOverlays() @@ -181,6 +205,14 @@ class HereMapViewController( override suspend fun updatePolyline(state: PolylineState) = polylineOverlayManager.updatePolyline(state) + override suspend fun addGroundImages(data: List) { + TODO("Not yet implemented") + } + + override suspend fun updateGroundImage(state: GroundImageState) { + TODO("Not yet implemented") + } + init { setupListeners() markerRenderer.init(markerOverlayManager) diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt index 020d94fe..3fd90e60 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt @@ -1,13 +1,52 @@ package com.mapconductor.here.groundimage +import com.here.sdk.mapview.MapImage +import com.mapconductor.core.groundimage.AbstractGroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageEntity +import com.mapconductor.core.groundimage.GroundImageOverlayManager +import com.mapconductor.core.groundimage.GroundImageOverlayManagerImpl +import com.mapconductor.core.groundimage.GroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageRendererFactory import com.mapconductor.core.groundimage.GroundImageState +import com.mapconductor.here.HereMapViewHolder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.newCoroutineContext +import kotlinx.coroutines.withContext -class HereGroundImageRenderer { - fun addOverlay(state: GroundImageState) { - // Not implemented +class DefaultHereMapGroundImageRenderer : GroundImageRendererFactory { + override fun create( + onAdd: suspend (List) -> List, + onChange: suspend (List>) -> List, + onRemove: suspend (List>) -> Unit, + onPostProcess: (suspend () -> Unit)? + ): GroundImageOverlayManager = + GroundImageOverlayManagerImpl( + onRemove = onRemove, + onAdd = onAdd, + onChange = onChange, + onPostProcess = onPostProcess, + ) +} + +class HereMapGroundImageRenderer( + override val holder: HereMapViewHolder, + override val coroutine: CoroutineScope +) : AbstractGroundImageRenderer() { + override suspend fun addGroundImages(newGroundImages: List): List { + return withContext(coroutine.newCoroutineContext){ + return@withContext newGroundImages.map { state -> + val bounds = state.bounds ?: return@withContext emptyList() + val image = + val alpha = + val options = + } + } + } + + override suspend fun changeGroundImages(changes: List>): List { + return emptyList() } - fun updateOverlay(state: GroundImageState) { - // Not implemented + override suspend fun removeGroundImages(removeEntities: List>) { } } diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt index ac4156e1..90dd2265 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt @@ -30,6 +30,9 @@ import com.mapconductor.core.circle.CircleState import com.mapconductor.core.controller.BaseMapViewController import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.geocell.HexGeocell +import com.mapconductor.core.groundimage.GroundImageOverlayManager +import com.mapconductor.core.groundimage.GroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewState import com.mapconductor.core.marker.MarkerOverlayManager @@ -44,6 +47,7 @@ import com.mapconductor.core.projection.WebMercator import com.mapconductor.mapbox.circle.DefaultMapboxCircleRenderer import com.mapconductor.mapbox.circle.MapboxCircleLayer import com.mapconductor.mapbox.circle.MapboxCircleRenderer +import com.mapconductor.mapbox.groundImage.MapboxGroundImageRenderer import com.mapconductor.mapbox.marker.DefaultMapboxMarkerRenderer import com.mapconductor.mapbox.marker.MapboxMarkerRenderer import com.mapconductor.mapbox.marker.MarkerDragLayer @@ -65,6 +69,7 @@ interface IMapboxMapViewController : MapboxActualCircle, MapboxActualPolyline, MapboxActualPolygon, + MapboxActualGroundImage, > { fun moveCamera( dstPosition: MapCameraPosition, @@ -123,6 +128,7 @@ internal class MapboxMapViewController( MapboxActualCircle, MapboxActualPolyline, MapboxActualPolygon, + MapboxActualGroundImage, >(), IMapboxMapViewController, CameraChangedCallback, @@ -171,6 +177,10 @@ internal class MapboxMapViewController( onPostProcess = circleRenderer::redraw, ) + override fun createGroundImageOverlayManager(): GroundImageOverlayManager { + TODO("Not yet implemented") + } + override fun onMarkerOverlayManagerInitialized(overlayManager: MarkerOverlayManager) { holder.map.getStyle { style -> style.addSource(circleLayer.source) @@ -204,6 +214,14 @@ internal class MapboxMapViewController( layer = circleLayer, ) + override fun onGroundImageOverlayManagerInitialized(overlayManager: GroundImageOverlayManager) { + } + + override val groundImageRenderer: GroundImageRenderer = MapboxGroundImageRenderer( + holder = holder, + coroutine = coroutine, + ) + override fun onCircleOverlayManagerInitialized(overlayManager: CircleOverlayManager) { } @@ -241,6 +259,13 @@ internal class MapboxMapViewController( override suspend fun addPolylines(data: List) = polylineOverlayManager.addPolylines(data) override suspend fun updatePolyline(state: PolylineState) = polylineOverlayManager.updatePolyline(state) + override suspend fun addGroundImages(data: List) { + TODO("Not yet implemented") + } + + override suspend fun updateGroundImage(state: GroundImageState) { + TODO("Not yet implemented") + } override suspend fun addCircles(data: List) = circleOverlayManager.addCircles(data) diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxTypeAlias.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxTypeAlias.kt index 0175622a..0138c3cd 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxTypeAlias.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxTypeAlias.kt @@ -6,3 +6,4 @@ typealias MapboxActualMarker = Feature typealias MapboxActualCircle = Feature typealias MapboxActualPolyline = Feature typealias MapboxActualPolygon = Feature +typealias MapboxActualGroundImage = Feature diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt index d5d564d9..208b2793 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt @@ -1,13 +1,25 @@ package com.mapconductor.mapbox.groundImage +import com.mapbox.geojson.Feature +import com.mapconductor.core.groundimage.AbstractGroundImageRenderer +import com.mapconductor.core.groundimage.GroundImageEntity +import com.mapconductor.core.groundimage.GroundImageRenderer import com.mapconductor.core.groundimage.GroundImageState +import com.mapconductor.mapbox.MapboxMapViewHolder +import kotlinx.coroutines.CoroutineScope -class MapboxGroundImageRenderer { - fun addOverlay(state: GroundImageState) { - // Not implemented +class MapboxGroundImageRenderer( + override val holder: MapboxMapViewHolder, + override val coroutine: CoroutineScope + ) : AbstractGroundImageRenderer() { + override suspend fun addGroundImages(newGroundImages: List): List { + return emptyList() } - fun updateOverlay(state: GroundImageState) { - // Not implemented + override suspend fun changeGroundImages(changes: List>): List { + return emptyList() + } + + override suspend fun removeGroundImages(removeEntities: List>) { } } From 5a87a980808121d02eb425950328fffd1917c587 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Mon, 4 Aug 2025 23:11:49 +0900 Subject: [PATCH 03/29] Resolve merge conflicts. --- .../mapconductor/here/groundimage/HereGroundImageRenderer.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt index f20ead06..c5d6e3c2 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt @@ -34,8 +34,6 @@ class HereMapGroundImageRenderer( ) : AbstractGroundImageRenderer() { override suspend fun addGroundImages(newGroundImages: List): List { return emptyList() - } -// override suspend fun addGroundImages(newGroundImages: List): List { // return withContext(coroutine.coroutineContext){ // return@withContext newGroundImages.map { state -> // val bounds = state.bounds ?: return@withContext emptyList() @@ -44,7 +42,7 @@ class HereMapGroundImageRenderer( // val options = state.extra // } // } -// } + } override suspend fun changeGroundImages(changes: List>): List { return emptyList() From 81d864529101351633d0184c97f53d16cb7ad2d9 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Wed, 6 Aug 2025 00:47:37 +0900 Subject: [PATCH 04/29] WIP: GroundImage bounds change detection in progress. --- .../com/mapconductor/example/DemoAppScreen.kt | 13 ++ .../mapconductor/example/MapViewContainer.kt | 11 ++ .../groundimage/GroundImageMapComponent.kt | 40 +++++ .../pages/groundimage/GroundImageMapPage.kt | 65 +++++++ .../GroundImageMapPageViewModel.kt | 160 ++++++++++++++++++ 5 files changed, 289 insertions(+) create mode 100644 example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt create mode 100644 example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt create mode 100644 example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt diff --git a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt index bf7d3965..47bf0d05 100644 --- a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt +++ b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Home import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -13,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.viewmodel.compose.viewModel import com.mapconductor.example.navigation.NavigationViewModel import com.mapconductor.example.pages.circle.CircleMapPage +import com.mapconductor.example.pages.groundimage.GroundImageMapPage import com.mapconductor.example.pages.stores.StoreMapPage import com.mapconductor.example.ui.sidebar.Sidebar import com.mapconductor.example.ui.sidebar.SidebarItem @@ -38,6 +40,12 @@ fun DemoAppScreen() { icon = Icons.Default.CheckCircle, route = "circle", ), + SidebarItem( + id = "groundImage", + title = "GroundImage Demo", + icon = Icons.Default.Favorite, + route = "groundImage", + ), // SidebarItem( // id = "examples", // title = "Map Examples", @@ -67,6 +75,11 @@ fun DemoAppScreen() { onToggleSidebar = navigationViewModel::toggleSidebar, ) } + "groundImage" -> { + GroundImageMapPage( + onToggleSidebar = navigationViewModel::toggleSidebar, + ) + } "settings" -> { // Placeholder for settings page Box( diff --git a/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt b/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt index 652617f1..6df047fc 100644 --- a/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt +++ b/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt @@ -6,6 +6,7 @@ import com.mapconductor.arcgis.ArcGISMapView import com.mapconductor.arcgis.ArcGISMapViewState import com.mapconductor.core.MapViewScope import com.mapconductor.core.circle.OnCircleEventHandler +import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.OnMapEventHandler import com.mapconductor.core.marker.OnMarkerEventHandler @@ -28,6 +29,8 @@ fun MapViewContainer( onMarkerAnimateStart: OnMarkerEventHandler? = null, onMarkerAnimateEnd: OnMarkerEventHandler? = null, onCircleClick: OnCircleEventHandler? = null, + onGroundImageClick: OnGroundImageEventHandler? = null, + onGroundImageChange: OnGroundImageEventHandler? = null, content: @Composable MapViewScope.() -> Unit, ) { when (state) { @@ -43,6 +46,8 @@ fun MapViewContainer( onMarkerAnimateStart = onMarkerAnimateStart, onMarkerAnimateEnd = onMarkerAnimateEnd, onCircleClick = onCircleClick, + onGroundImageClick = onGroundImageClick, + onGroundImageChange = onGroundImageChange, content = content, ) @@ -57,6 +62,8 @@ fun MapViewContainer( onMarkerDragEnd = onMarkerDragEnd, onMarkerAnimateStart = onMarkerAnimateStart, onMarkerAnimateEnd = onMarkerAnimateEnd, + onGroundImageClick = onGroundImageClick, + onGroundImageChange = onGroundImageChange, content = content, ) @@ -72,6 +79,8 @@ fun MapViewContainer( onMarkerAnimateStart = onMarkerAnimateStart, onMarkerAnimateEnd = onMarkerAnimateEnd, onCircleClick = onCircleClick, + onGroundImageClick = onGroundImageClick, + onGroundImageChange = onGroundImageChange, content = content, ) @@ -87,6 +96,8 @@ fun MapViewContainer( onMarkerAnimateStart = onMarkerAnimateStart, onMarkerAnimateEnd = onMarkerAnimateEnd, onCircleClick = onCircleClick, + onGroundImageClick = onGroundImageClick, + onGroundImageChange = onGroundImageChange, content = content, ) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt new file mode 100644 index 00000000..e11f6db6 --- /dev/null +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt @@ -0,0 +1,40 @@ +package com.mapconductor.example.pages.groundimage + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.key +import androidx.compose.ui.Modifier +import com.mapconductor.core.circle.Circle +import com.mapconductor.core.circle.OnCircleEventHandler +import com.mapconductor.core.groundimage.GroundImage +import com.mapconductor.core.groundimage.OnGroundImageEventHandler +import com.mapconductor.core.map.MapViewState +import com.mapconductor.core.map.OnMapEventHandler +import com.mapconductor.core.marker.Marker +import com.mapconductor.core.marker.OnMarkerEventHandler +import com.mapconductor.example.MapViewContainer +import com.mapconductor.example.pages.circle.CirclePageViewModel + +@Composable +fun GroundImageMapComponent( + mapViewState: MapViewState<*>?, + viewModel: GroundImageMapPageViewModel, + modifier: Modifier = Modifier, + onMapClick: OnMapEventHandler = {}, + onGroundImageClick: OnGroundImageEventHandler = {}, + onGroundImageChange: OnGroundImageEventHandler = {}, +) { + mapViewState?.let { mapViewState -> + MapViewContainer( + modifier = modifier, + state = mapViewState, + onMapClick = onMapClick, + onGroundImageClick = onGroundImageClick, + onGroundImageChange = onGroundImageChange, + ) { + // GroundImage + key(viewModel.groundImageState.id) { + GroundImage(viewModel.groundImageState) + } + } + } +} diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt new file mode 100644 index 00000000..093eacaa --- /dev/null +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt @@ -0,0 +1,65 @@ +package com.mapconductor.example.pages.groundimage + +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import com.mapconductor.example.pages.circle.CircleMapComponent +import com.mapconductor.example.pages.circle.CirclePageViewModel +import com.mapconductor.example.pages.circle.CirclePageViewModelImpl +import com.mapconductor.example.toast.ToastHost +import com.mapconductor.example.ui.DemoMapPageScaffold +import com.mapconductor.example.ui.MapViewStatePanel +import com.mapconductor.example.ui.MessageCard + +@Composable +fun GroundImageMapPage( + viewModel: GroundImageMapPageViewModel = GroundImageMapPageViewModelImpl(), + onToggleSidebar: () -> Unit = {}, +) { + DemoMapPageScaffold( + initCameraPosition = viewModel.initCameraPosition, + onToggleSidebar = onToggleSidebar, + onMapViewStateChanged = viewModel::onMapViewChanged, + ) { paddingValues -> + val mapViewState = viewModel.mapViewState.collectAsState() + + GroundImageMapComponent( + mapViewState = mapViewState.value, + viewModel = viewModel, + onMapClick = viewModel::onMapClick, + onGroundImageClick = viewModel::onGroundImageClick, + onGroundImageChange = viewModel::onGroundImageChange, + ) + + // Message Card + MessageCard( + modifier = + Modifier + .align(Alignment.BottomStart) + .padding( + bottom = paddingValues.calculateBottomPadding() + 16.dp, + start = paddingValues.calculateStartPadding(LayoutDirection.Ltr) + 16.dp, + end = paddingValues.calculateEndPadding(LayoutDirection.Ltr) + 16.dp, + ), + title = "Messages", + ) { + MapViewStatePanel( + mapViewState.value + ?.mapCameraPosition + ?.collectAsState() + ?.value, + ) + } + + ToastHost( + messages = viewModel.messages.collectAsState().value, + onDismiss = { viewModel.removeToast(it) }, + ) + } +} diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt new file mode 100644 index 00000000..1e246bad --- /dev/null +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt @@ -0,0 +1,160 @@ +package com.mapconductor.example.pages.groundimage + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.lifecycle.ViewModel +import com.mapconductor.core.circle.CircleClickEvent +import com.mapconductor.core.circle.CircleState +import com.mapconductor.core.features.GeoPoint +import com.mapconductor.core.groundimage.GroundImageState +import com.mapconductor.core.map.MapCameraPosition +import com.mapconductor.core.map.MapViewState +import com.mapconductor.core.marker.DefaultIcon +import com.mapconductor.core.marker.MarkerState +import com.mapconductor.core.spherical.calculatePositionAtDistance +import com.mapconductor.core.spherical.haversineDistance +import com.mapconductor.example.toast.ToastMessage +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +interface GroundImageMapPageViewModel { + val initCameraPosition: MapCameraPosition + val mapViewState: StateFlow?> + val messages: StateFlow> + + val circleCenter: GeoPoint + val radiusMeters: Double + val centerMarker: MarkerState + val edgeMarker: MarkerState + val groundImageState: GroundImageState + + fun onMapViewChanged(state: MapViewState<*>) + + fun cameraReset(listener: MapViewState.MoveCameraCallback? = null) + + fun onMapClick(clicked: GeoPoint) + + fun onGroundImageClick(event: GroundImageClickEvent) + + fun onGroundImageChange(event: GroundImageClickEvent) + + fun showToast(text: String) + + fun removeToast(toastMessage: ToastMessage) +} + +class GroundImageMapPageViewModelImpl : + ViewModel(), + GroundImageMapPageViewModel { + private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) + override val messages: StateFlow> = _messages.asStateFlow() + + override val initCameraPosition = + MapCameraPosition( + position = + GeoPoint.fromLatLong( + latitude = 21.382314, + longitude = -157.933097, + ), + zoom = 12.0, + bearing = 0.0, + tilt = 0.0, + paddings = null, + ) + +// override val circleCenter = GeoPoint.fromLatLong(21.382314, -157.933097) +// +// override val centerMarker = +// MarkerState( +// id = "center_marker", +// position = circleCenter, +// icon = +// DefaultIcon( +// fillColor = Color.Red, +// strokeColor = Color.White, +// label = "C", +// ), +// draggable = false, +// ) +// + private val _edgeMarker: MutableState = + mutableStateOf( + MarkerState( + id = "edge_marker", + position = + calculatePositionAtDistance( + center = circleCenter, + distanceMeters = 1000.0, + bearingDegrees = 90.0, // East + ), + icon = + DefaultIcon( + fillColor = Color.Green, + strokeColor = Color.White, + label = "E", + ), + draggable = true, + ), + ) + override val edgeMarker: MarkerState + get() = _edgeMarker.value +// +// override val radiusMeters by derivedStateOf { +// haversineDistance(circleCenter, _edgeMarker.value.position) +// } +// + private val _groundImageState: MutableState = + mutableStateOf( + GroundImageState( + id = "groundImage", + ), + ) + override val groundImageState: GroundImageState + get() = _groundImageState.value +// +// private val _mapViewState = MutableStateFlow?>(null) +// override val mapViewState: StateFlow?> = _mapViewState.asStateFlow() +// +// override fun onMapViewChanged(state: MapViewState<*>) { +// this._mapViewState.value = state +// } +// +// override fun cameraReset(listener: MapViewState.MoveCameraCallback?) { +// this.mapViewState.value?.moveCameraTo( +// cameraPosition = initCameraPosition, +// durationMs = 3000, +// listener = listener, +// ) +// } + + override fun onMapClick(clicked: GeoPoint) { + showToast("Map clicked at: ${clicked.toUrlValue()}") + } + + override fun onGroundImageClick(event: GroundImageClickEvent) { + event.state.fillColor = Color.Blue.copy(alpha = 0.5f) + showToast("GroundImage clicked - Radius: ${radiusMeters.toInt()}m") + } + + override fun onGroundImageChange(event: GroundImageClickEvent) { + event.state.fillColor = Color.Blue.copy(alpha = 0.5f) + showToast("GroundImage clicked - Radius: ${radiusMeters.toInt()}m") + } + + override fun showToast(text: String) { + this._messages.value = this._messages.value + ToastMessage(text = text) + } + + override fun removeToast(toastMessage: ToastMessage) { + this._messages.value = this._messages.value.filter { it != toastMessage } + } + + override fun onCleared() { + super.onCleared() + } +} From 1acf8a38a5609a809e2c33e6e1b342ac4118a144 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Fri, 8 Aug 2025 01:09:29 +0900 Subject: [PATCH 05/29] WIP: GroundImage rendered; modification handling not yet done. --- .../groundimage/GroundImageMapComponent.kt | 5 - .../pages/groundimage/GroundImageMapPage.kt | 10 +- .../GroundImageMapPageViewModel.kt | 101 ++++++------------ .../core/groundimage/GroundImage.kt | 7 +- .../com/mapconductor/arcgis/ArcGISMapView.kt | 3 + .../mapconductor/googlemaps/GoogleMapView.kt | 3 + .../GoogleMapGroundImageRenderer.kt | 2 +- .../java/com/mapconductor/here/HereMapView.kt | 3 + .../com/mapconductor/mapbox/MapboxMapView.kt | 3 + 9 files changed, 63 insertions(+), 74 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt index e11f6db6..e0d9bc71 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt @@ -3,16 +3,11 @@ package com.mapconductor.example.pages.groundimage import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Modifier -import com.mapconductor.core.circle.Circle -import com.mapconductor.core.circle.OnCircleEventHandler import com.mapconductor.core.groundimage.GroundImage import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.OnMapEventHandler -import com.mapconductor.core.marker.Marker -import com.mapconductor.core.marker.OnMarkerEventHandler import com.mapconductor.example.MapViewContainer -import com.mapconductor.example.pages.circle.CirclePageViewModel @Composable fun GroundImageMapComponent( diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt index 093eacaa..60717b96 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt @@ -7,8 +7,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import com.mapconductor.core.groundimage.GroundImageClickEvent +import com.mapconductor.example.R import com.mapconductor.example.pages.circle.CircleMapComponent import com.mapconductor.example.pages.circle.CirclePageViewModel import com.mapconductor.example.pages.circle.CirclePageViewModelImpl @@ -16,10 +20,14 @@ import com.mapconductor.example.toast.ToastHost import com.mapconductor.example.ui.DemoMapPageScaffold import com.mapconductor.example.ui.MapViewStatePanel import com.mapconductor.example.ui.MessageCard +import android.content.Context +import android.graphics.drawable.Drawable @Composable fun GroundImageMapPage( - viewModel: GroundImageMapPageViewModel = GroundImageMapPageViewModelImpl(), + context: Context = LocalContext.current, + image: Drawable = ContextCompat.getDrawable(context, R.drawable.newark_nj_1922_0)!!, + viewModel: GroundImageMapPageViewModel = GroundImageMapPageViewModelImpl(image), onToggleSidebar: () -> Unit = {}, ) { DemoMapPageScaffold( diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt index 1e246bad..044c156e 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt @@ -7,9 +7,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModel +import com.google.android.gms.maps.model.BitmapDescriptorFactory import com.mapconductor.core.circle.CircleClickEvent import com.mapconductor.core.circle.CircleState import com.mapconductor.core.features.GeoPoint +import com.mapconductor.core.features.GeoRectBounds +import com.mapconductor.core.groundimage.GroundImageClickEvent import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewState @@ -17,7 +20,9 @@ import com.mapconductor.core.marker.DefaultIcon import com.mapconductor.core.marker.MarkerState import com.mapconductor.core.spherical.calculatePositionAtDistance import com.mapconductor.core.spherical.haversineDistance +import com.mapconductor.example.R import com.mapconductor.example.toast.ToastMessage +import android.graphics.drawable.Drawable import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -27,10 +32,9 @@ interface GroundImageMapPageViewModel { val mapViewState: StateFlow?> val messages: StateFlow> - val circleCenter: GeoPoint - val radiusMeters: Double - val centerMarker: MarkerState - val edgeMarker: MarkerState + val bounds: GeoRectBounds + val image: Drawable + val alpha: Float val groundImageState: GroundImageState fun onMapViewChanged(state: MapViewState<*>) @@ -48,11 +52,9 @@ interface GroundImageMapPageViewModel { fun removeToast(toastMessage: ToastMessage) } -class GroundImageMapPageViewModelImpl : +class GroundImageMapPageViewModelImpl(override val image: Drawable) : ViewModel(), GroundImageMapPageViewModel { - private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) - override val messages: StateFlow> = _messages.asStateFlow() override val initCameraPosition = MapCameraPosition( @@ -67,83 +69,50 @@ class GroundImageMapPageViewModelImpl : paddings = null, ) -// override val circleCenter = GeoPoint.fromLatLong(21.382314, -157.933097) -// -// override val centerMarker = -// MarkerState( -// id = "center_marker", -// position = circleCenter, -// icon = -// DefaultIcon( -// fillColor = Color.Red, -// strokeColor = Color.White, -// label = "C", -// ), -// draggable = false, -// ) -// - private val _edgeMarker: MutableState = - mutableStateOf( - MarkerState( - id = "edge_marker", - position = - calculatePositionAtDistance( - center = circleCenter, - distanceMeters = 1000.0, - bearingDegrees = 90.0, // East - ), - icon = - DefaultIcon( - fillColor = Color.Green, - strokeColor = Color.White, - label = "E", - ), - draggable = true, - ), + private val _mapViewState = MutableStateFlow?>(null) + override val mapViewState: StateFlow?> = _mapViewState.asStateFlow() + + override fun onMapViewChanged(state: MapViewState<*>) { + this._mapViewState.value = state + } + + override fun cameraReset(listener: MapViewState.MoveCameraCallback?) { + this.mapViewState.value?.moveCameraTo( + cameraPosition = initCameraPosition, + durationMs = 3000, + listener = listener, ) - override val edgeMarker: MarkerState - get() = _edgeMarker.value -// -// override val radiusMeters by derivedStateOf { -// haversineDistance(circleCenter, _edgeMarker.value.position) -// } -// + } + + private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) + override val messages: StateFlow> = _messages.asStateFlow() + + override val bounds = GeoRectBounds( + southWest = GeoPoint.fromLatLong(20.842314, -158.458097), + northEast = GeoPoint.fromLatLong(21.922314, -157.408097) + ) + + override val alpha = 1.0f + private val _groundImageState: MutableState = mutableStateOf( GroundImageState( id = "groundImage", + bounds = bounds, + image = image, ), ) override val groundImageState: GroundImageState get() = _groundImageState.value -// -// private val _mapViewState = MutableStateFlow?>(null) -// override val mapViewState: StateFlow?> = _mapViewState.asStateFlow() -// -// override fun onMapViewChanged(state: MapViewState<*>) { -// this._mapViewState.value = state -// } -// -// override fun cameraReset(listener: MapViewState.MoveCameraCallback?) { -// this.mapViewState.value?.moveCameraTo( -// cameraPosition = initCameraPosition, -// durationMs = 3000, -// listener = listener, -// ) -// } override fun onMapClick(clicked: GeoPoint) { showToast("Map clicked at: ${clicked.toUrlValue()}") } override fun onGroundImageClick(event: GroundImageClickEvent) { - event.state.fillColor = Color.Blue.copy(alpha = 0.5f) - showToast("GroundImage clicked - Radius: ${radiusMeters.toInt()}m") } override fun onGroundImageChange(event: GroundImageClickEvent) { - event.state.fillColor = Color.Blue.copy(alpha = 0.5f) - showToast("GroundImage clicked - Radius: ${radiusMeters.toInt()}m") } override fun showToast(text: String) { diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt index 9cea665d..2351b803 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt @@ -63,4 +63,9 @@ data class GroundImageFingerPrint( val extra: Int, ) -typealias OnGroundImageEventHandler = (GroundImageState) -> Unit +data class GroundImageClickEvent( + val state: GroundImageState, + val bounds: GeoRectBounds, +) + +typealias OnGroundImageEventHandler = (GroundImageClickEvent) -> Unit diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt index 4c33a367..20cf41b9 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.node.Ref import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.LocalLifecycleOwner import com.mapconductor.core.circle.OnCircleEventHandler +import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewBase import com.mapconductor.core.map.OnMapEventHandler @@ -24,6 +25,8 @@ fun ArcGISMapView( onMarkerAnimateStart: OnMarkerEventHandler? = {}, onMarkerAnimateEnd: OnMarkerEventHandler? = {}, onCircleClick: OnCircleEventHandler? = {}, + onGroundImageClick: OnGroundImageEventHandler? = null, + onGroundImageChange: OnGroundImageEventHandler? = null, content: (@Composable ArcGISMapViewScope.() -> Unit)? = null, ) { val holderRef = remember { Ref() } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt index 78807155..fc7d502b 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt @@ -13,6 +13,7 @@ import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.model.CameraPosition import com.mapconductor.core.circle.OnCircleEventHandler import com.mapconductor.core.features.GeoPoint +import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapViewBase import com.mapconductor.core.map.OnMapEventHandler import com.mapconductor.core.marker.OnMarkerEventHandler @@ -30,6 +31,8 @@ fun GoogleMapsView( onMarkerAnimateStart: OnMarkerEventHandler? = {}, onMarkerAnimateEnd: OnMarkerEventHandler? = {}, onCircleClick: OnCircleEventHandler? = {}, + onGroundImageClick: OnGroundImageEventHandler? = null, + onGroundImageChange: OnGroundImageEventHandler? = null, content: (@Composable GoogleMapViewScope.() -> Unit)? = null, ) { val holderRef = remember { Ref() } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt index b7eb65c6..f3ba381f 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt @@ -49,7 +49,7 @@ class GoogleMapGroundImageRenderer( GroundOverlayOptions() .image(image) .positionFromBounds(bounds) - .transparency(alpha) +// .transparency(alpha) holder.map.addGroundOverlay(options)?.also { it.tag = state.id } diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt index 1fba4577..3fe4a0a5 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner +import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapViewBase import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.OnMapEventHandler @@ -30,6 +31,8 @@ fun HereMapView( onMarkerDragEnd: OnMarkerEventHandler? = {}, onMarkerAnimateStart: OnMarkerEventHandler? = {}, onMarkerAnimateEnd: OnMarkerEventHandler? = {}, + onGroundImageClick: OnGroundImageEventHandler? = null, + onGroundImageChange: OnGroundImageEventHandler? = null, content: (@Composable HereMapViewScope.() -> Unit)? = null, ) { val holderRef = remember { Ref() } diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt index f4d60b41..190e9739 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.node.Ref import androidx.compose.ui.platform.LocalContext import com.mapbox.maps.MapInitOptions import com.mapconductor.core.circle.OnCircleEventHandler +import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapViewBase import com.mapconductor.core.map.OnMapEventHandler import com.mapconductor.core.marker.OnMarkerEventHandler @@ -26,6 +27,8 @@ fun MapboxMapView( onMarkerAnimateStart: OnMarkerEventHandler? = {}, onMarkerAnimateEnd: OnMarkerEventHandler? = {}, onCircleClick: OnCircleEventHandler? = {}, + onGroundImageClick: OnGroundImageEventHandler? = null, + onGroundImageChange: OnGroundImageEventHandler? = null, content: (@Composable MapboxMapViewScope.() -> Unit)? = null, ) { val holderRef = remember { Ref() } From 4d515a41c7b12a3673ce473250dc5cffd5ede930 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Mon, 11 Aug 2025 08:04:58 +0900 Subject: [PATCH 06/29] Add support for swapping GroundImage (Google Maps API GroundOverlay) image on touch event --- .../com/mapconductor/example/DemoAppScreen.kt | 13 +++++ .../mapconductor/example/MapViewContainer.kt | 9 +--- .../groundimage/GroundImageMapComponent.kt | 6 +-- .../pages/groundimage/GroundImageMapPage.kt | 10 +--- .../GroundImageMapPageViewModel.kt | 47 +++++++++---------- .../pages/groundimage/GroundImageResources.kt | 8 ++++ .../core/controller/MapViewController.kt | 2 + .../core/groundimage/GroundImage.kt | 19 ++++---- .../core/groundimage/GroundImageComponent.kt | 4 +- .../groundimage/GroundImageOverlayManager.kt | 34 +++++++++++++- .../com/mapconductor/core/map/MapViewBase.kt | 8 ++++ .../com/mapconductor/arcgis/ArcGISMapView.kt | 5 +- .../mapconductor/googlemaps/GoogleMapView.kt | 6 +-- .../googlemaps/GoogleMapViewController.kt | 11 +++++ .../GoogleMapGroundImageRenderer.kt | 18 ++----- .../java/com/mapconductor/here/HereMapView.kt | 5 +- .../com/mapconductor/mapbox/MapboxMapView.kt | 5 +- 17 files changed, 125 insertions(+), 85 deletions(-) create mode 100644 example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageResources.kt diff --git a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt index 47bf0d05..3afc33fd 100644 --- a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt +++ b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt @@ -9,12 +9,16 @@ import androidx.compose.material.icons.filled.Home import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat import androidx.lifecycle.viewmodel.compose.viewModel import com.mapconductor.example.navigation.NavigationViewModel import com.mapconductor.example.pages.circle.CircleMapPage import com.mapconductor.example.pages.groundimage.GroundImageMapPage +import com.mapconductor.example.pages.groundimage.GroundImageResources import com.mapconductor.example.pages.stores.StoreMapPage import com.mapconductor.example.ui.sidebar.Sidebar import com.mapconductor.example.ui.sidebar.SidebarItem @@ -26,6 +30,14 @@ fun DemoAppScreen() { val currentPage by navigationViewModel.currentPage val isSidebarExpanded by navigationViewModel.isSidebarExpanded + val context = LocalContext.current + val groundImageResources = remember { + GroundImageResources( + imageToggle0 = ContextCompat.getDrawable(context, R.drawable.newark_nj_1922_0)!!, + imageToggle1 = ContextCompat.getDrawable(context, R.drawable.newark_nj_1922_1)!!, + ) + } + val sidebarItems = listOf( SidebarItem( @@ -77,6 +89,7 @@ fun DemoAppScreen() { } "groundImage" -> { GroundImageMapPage( + groundImageResources = groundImageResources, onToggleSidebar = navigationViewModel::toggleSidebar, ) } diff --git a/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt b/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt index 6df047fc..aa0369c1 100644 --- a/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt +++ b/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt @@ -6,7 +6,7 @@ import com.mapconductor.arcgis.ArcGISMapView import com.mapconductor.arcgis.ArcGISMapViewState import com.mapconductor.core.MapViewScope import com.mapconductor.core.circle.OnCircleEventHandler -import com.mapconductor.core.groundimage.OnGroundImageEventHandler +import com.mapconductor.core.groundimage.OnGroundImageClickEventHandler import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.OnMapEventHandler import com.mapconductor.core.marker.OnMarkerEventHandler @@ -29,8 +29,7 @@ fun MapViewContainer( onMarkerAnimateStart: OnMarkerEventHandler? = null, onMarkerAnimateEnd: OnMarkerEventHandler? = null, onCircleClick: OnCircleEventHandler? = null, - onGroundImageClick: OnGroundImageEventHandler? = null, - onGroundImageChange: OnGroundImageEventHandler? = null, + onGroundImageClick: OnGroundImageClickEventHandler? = null, content: @Composable MapViewScope.() -> Unit, ) { when (state) { @@ -47,7 +46,6 @@ fun MapViewContainer( onMarkerAnimateEnd = onMarkerAnimateEnd, onCircleClick = onCircleClick, onGroundImageClick = onGroundImageClick, - onGroundImageChange = onGroundImageChange, content = content, ) @@ -63,7 +61,6 @@ fun MapViewContainer( onMarkerAnimateStart = onMarkerAnimateStart, onMarkerAnimateEnd = onMarkerAnimateEnd, onGroundImageClick = onGroundImageClick, - onGroundImageChange = onGroundImageChange, content = content, ) @@ -80,7 +77,6 @@ fun MapViewContainer( onMarkerAnimateEnd = onMarkerAnimateEnd, onCircleClick = onCircleClick, onGroundImageClick = onGroundImageClick, - onGroundImageChange = onGroundImageChange, content = content, ) @@ -97,7 +93,6 @@ fun MapViewContainer( onMarkerAnimateEnd = onMarkerAnimateEnd, onCircleClick = onCircleClick, onGroundImageClick = onGroundImageClick, - onGroundImageChange = onGroundImageChange, content = content, ) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt index e0d9bc71..2b67495b 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Modifier import com.mapconductor.core.groundimage.GroundImage -import com.mapconductor.core.groundimage.OnGroundImageEventHandler +import com.mapconductor.core.groundimage.OnGroundImageClickEventHandler import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.OnMapEventHandler import com.mapconductor.example.MapViewContainer @@ -15,8 +15,7 @@ fun GroundImageMapComponent( viewModel: GroundImageMapPageViewModel, modifier: Modifier = Modifier, onMapClick: OnMapEventHandler = {}, - onGroundImageClick: OnGroundImageEventHandler = {}, - onGroundImageChange: OnGroundImageEventHandler = {}, + onGroundImageClick: OnGroundImageClickEventHandler = {}, ) { mapViewState?.let { mapViewState -> MapViewContainer( @@ -24,7 +23,6 @@ fun GroundImageMapComponent( state = mapViewState, onMapClick = onMapClick, onGroundImageClick = onGroundImageClick, - onGroundImageChange = onGroundImageChange, ) { // GroundImage key(viewModel.groundImageState.id) { diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt index 60717b96..bc8c0435 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt @@ -11,11 +11,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat -import com.mapconductor.core.groundimage.GroundImageClickEvent import com.mapconductor.example.R -import com.mapconductor.example.pages.circle.CircleMapComponent -import com.mapconductor.example.pages.circle.CirclePageViewModel -import com.mapconductor.example.pages.circle.CirclePageViewModelImpl import com.mapconductor.example.toast.ToastHost import com.mapconductor.example.ui.DemoMapPageScaffold import com.mapconductor.example.ui.MapViewStatePanel @@ -25,9 +21,8 @@ import android.graphics.drawable.Drawable @Composable fun GroundImageMapPage( - context: Context = LocalContext.current, - image: Drawable = ContextCompat.getDrawable(context, R.drawable.newark_nj_1922_0)!!, - viewModel: GroundImageMapPageViewModel = GroundImageMapPageViewModelImpl(image), + groundImageResources: GroundImageResources, + viewModel: GroundImageMapPageViewModel = GroundImageMapPageViewModelImpl(groundImageResources), onToggleSidebar: () -> Unit = {}, ) { DemoMapPageScaffold( @@ -42,7 +37,6 @@ fun GroundImageMapPage( viewModel = viewModel, onMapClick = viewModel::onMapClick, onGroundImageClick = viewModel::onGroundImageClick, - onGroundImageChange = viewModel::onGroundImageChange, ) // Message Card diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt index 044c156e..4609272a 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt @@ -1,26 +1,14 @@ package com.mapconductor.example.pages.groundimage import androidx.compose.runtime.MutableState -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModel -import com.google.android.gms.maps.model.BitmapDescriptorFactory -import com.mapconductor.core.circle.CircleClickEvent -import com.mapconductor.core.circle.CircleState import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.GeoRectBounds import com.mapconductor.core.groundimage.GroundImageClickEvent import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewState -import com.mapconductor.core.marker.DefaultIcon -import com.mapconductor.core.marker.MarkerState -import com.mapconductor.core.spherical.calculatePositionAtDistance -import com.mapconductor.core.spherical.haversineDistance -import com.mapconductor.example.R import com.mapconductor.example.toast.ToastMessage import android.graphics.drawable.Drawable import kotlinx.coroutines.flow.MutableStateFlow @@ -33,8 +21,9 @@ interface GroundImageMapPageViewModel { val messages: StateFlow> val bounds: GeoRectBounds + val imageResources: GroundImageResources val image: Drawable - val alpha: Float + val opacity: Float val groundImageState: GroundImageState fun onMapViewChanged(state: MapViewState<*>) @@ -43,16 +32,14 @@ interface GroundImageMapPageViewModel { fun onMapClick(clicked: GeoPoint) - fun onGroundImageClick(event: GroundImageClickEvent) - - fun onGroundImageChange(event: GroundImageClickEvent) + fun onGroundImageClick(clicked: GroundImageClickEvent) fun showToast(text: String) fun removeToast(toastMessage: ToastMessage) } -class GroundImageMapPageViewModelImpl(override val image: Drawable) : +class GroundImageMapPageViewModelImpl(override val imageResources: GroundImageResources) : ViewModel(), GroundImageMapPageViewModel { @@ -60,8 +47,8 @@ class GroundImageMapPageViewModelImpl(override val image: Drawable) : MapCameraPosition( position = GeoPoint.fromLatLong( - latitude = 21.382314, - longitude = -157.933097, + latitude = 40.7430785, + longitude = -74.175995, ), zoom = 12.0, bearing = 0.0, @@ -87,12 +74,14 @@ class GroundImageMapPageViewModelImpl(override val image: Drawable) : private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) override val messages: StateFlow> = _messages.asStateFlow() - override val bounds = GeoRectBounds( - southWest = GeoPoint.fromLatLong(20.842314, -158.458097), - northEast = GeoPoint.fromLatLong(21.922314, -157.408097) + override val bounds = GeoRectBounds( // ココが固定だと変化できないので、後日処理を書き換え。 + southWest = GeoPoint.fromLatLong(40.712216, -74.22655), + northEast = GeoPoint.fromLatLong(40.773941, -74.12544) ) - override val alpha = 1.0f + override val image = imageResources.imageToggle0 + + override val opacity = 1.0f private val _groundImageState: MutableState = mutableStateOf( @@ -102,6 +91,7 @@ class GroundImageMapPageViewModelImpl(override val image: Drawable) : image = image, ), ) + override val groundImageState: GroundImageState get() = _groundImageState.value @@ -109,10 +99,15 @@ class GroundImageMapPageViewModelImpl(override val image: Drawable) : showToast("Map clicked at: ${clicked.toUrlValue()}") } - override fun onGroundImageClick(event: GroundImageClickEvent) { - } + override fun onGroundImageClick(clicked: GroundImageClickEvent) { + if (clicked.state.image == imageResources.imageToggle0) { + _groundImageState.value.image = imageResources.imageToggle1 + } + else { + _groundImageState.value.image = imageResources.imageToggle0 + } - override fun onGroundImageChange(event: GroundImageClickEvent) { + showToast("Ground Image clicked.") } override fun showToast(text: String) { diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageResources.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageResources.kt new file mode 100644 index 00000000..ca3d2f3f --- /dev/null +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageResources.kt @@ -0,0 +1,8 @@ +package com.mapconductor.example.pages.groundimage + +import android.graphics.drawable.Drawable + +data class GroundImageResources ( + val imageToggle0: Drawable, + val imageToggle1: Drawable, +) diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt b/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt index db206e64..cac3cb9d 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt @@ -11,6 +11,7 @@ import com.mapconductor.core.geocell.HexGeocell import com.mapconductor.core.groundimage.GroundImageOverlayManager import com.mapconductor.core.groundimage.GroundImageRenderer import com.mapconductor.core.groundimage.GroundImageState +import com.mapconductor.core.groundimage.OnGroundImageClickEventHandler import com.mapconductor.core.map.MapViewHolder import com.mapconductor.core.map.OnCameraMoveHandler import com.mapconductor.core.map.OnMapEventHandler @@ -147,6 +148,7 @@ abstract class BaseMapViewController Unit +typealias OnGroundImageClickEventHandler = (GroundImageClickEvent) -> Unit diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt index 5c65e824..9068cf0e 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt @@ -20,14 +20,14 @@ fun MapViewScope.GroundImage(state: GroundImageState) { fun MapViewScope.GroundImage( bounds: GeoRectBounds, image: Drawable, - alpha: Float = 0.5f, + opacity: Float = 0.5f, id: String? = null, extra: Parcelable? = null, ) { val state = GroundImageState( bounds = bounds, image = image, - alpha = alpha, + opacity = opacity, id = id, extra = extra, ) diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt index 08a03dd4..c3bccc44 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt @@ -1,5 +1,6 @@ package com.mapconductor.core.groundimage +import com.mapconductor.core.features.IGeoPoint import kotlinx.coroutines.sync.Semaphore interface GroundImageOverlayManager { @@ -12,6 +13,8 @@ interface GroundImageOverlayManager { fun getGroundImageState(id: String): GroundImageState? fun getAllEntities(): List> + + fun find(position: IGeoPoint): GroundImageEntity? } class GroundImageOverlayManagerImpl( @@ -91,9 +94,30 @@ val groundImageEntities = mutableMapOf>() + semaphore.acquire() - // Implement fine-grained update logic if needed + val prevEntity = groundImageEntities[state.id]!! + updated.add(object : GroundImageRenderer.UpdateParams { + override val entity: GroundImageEntity = + GroundImageEntityImpl( + groundImage = prevEntity.groundImage, + state = state + ) + override val prevEntity: GroundImageEntity = prevEntity + }) + + val actualOverlays: List = onChange(updated) + actualOverlays.forEachIndexed { index, actualOverlay -> + actualOverlay?.let { + val entity = GroundImageEntityImpl( + groundImage = it, + state = state + ) + groundImageEntities[state.id] = entity + } + } semaphore.release() } @@ -108,4 +132,10 @@ val groundImageEntities = mutableMapOf> = groundImageEntities.values.toList() + + override fun find(position: IGeoPoint): GroundImageEntity? { + return groundImageEntities.values.find { entity -> + entity.state.bounds.contains(position) + } + } } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt index 960b5077..8920e323 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt @@ -117,6 +117,14 @@ fun < } } } + val groundImage = scope.groundImageFlow.collectAsState() + groundImage.value.forEach { groundImageState -> + LaunchedEffect(groundImageState.id) { + groundImageState.asFlow().debounce(Settings.Default.composeEventDebounce).collectLatest { + controller.updateGroundImage(groundImageState) + } + } + } } Box( diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt index 20cf41b9..141fee0c 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt @@ -7,7 +7,7 @@ import androidx.compose.ui.node.Ref import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.LocalLifecycleOwner import com.mapconductor.core.circle.OnCircleEventHandler -import com.mapconductor.core.groundimage.OnGroundImageEventHandler +import com.mapconductor.core.groundimage.OnGroundImageClickEventHandler import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewBase import com.mapconductor.core.map.OnMapEventHandler @@ -25,8 +25,7 @@ fun ArcGISMapView( onMarkerAnimateStart: OnMarkerEventHandler? = {}, onMarkerAnimateEnd: OnMarkerEventHandler? = {}, onCircleClick: OnCircleEventHandler? = {}, - onGroundImageClick: OnGroundImageEventHandler? = null, - onGroundImageChange: OnGroundImageEventHandler? = null, + onGroundImageClick: OnGroundImageClickEventHandler? = null, content: (@Composable ArcGISMapViewScope.() -> Unit)? = null, ) { val holderRef = remember { Ref() } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt index fc7d502b..9615197b 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt @@ -13,7 +13,7 @@ import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.model.CameraPosition import com.mapconductor.core.circle.OnCircleEventHandler import com.mapconductor.core.features.GeoPoint -import com.mapconductor.core.groundimage.OnGroundImageEventHandler +import com.mapconductor.core.groundimage.OnGroundImageClickEventHandler import com.mapconductor.core.map.MapViewBase import com.mapconductor.core.map.OnMapEventHandler import com.mapconductor.core.marker.OnMarkerEventHandler @@ -31,8 +31,7 @@ fun GoogleMapsView( onMarkerAnimateStart: OnMarkerEventHandler? = {}, onMarkerAnimateEnd: OnMarkerEventHandler? = {}, onCircleClick: OnCircleEventHandler? = {}, - onGroundImageClick: OnGroundImageEventHandler? = null, - onGroundImageChange: OnGroundImageEventHandler? = null, + onGroundImageClick: OnGroundImageClickEventHandler? = null, content: (@Composable GoogleMapViewScope.() -> Unit)? = null, ) { val holderRef = remember { Ref() } @@ -87,6 +86,7 @@ fun GoogleMapsView( controller.setOnMarkerAnimationStart(onMarkerAnimateStart) controller.setOnMarkerAnimationEnd(onMarkerAnimateEnd) controller.circleClickListener = onCircleClick + controller.groundImageClickListener = onGroundImageClick holderRef.value = controller.holder controllerRef.value = controller diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt index d8849159..03bde306 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt @@ -25,6 +25,7 @@ import com.mapconductor.core.circle.CircleState import com.mapconductor.core.controller.BaseMapViewController import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.geocell.HexGeocell +import com.mapconductor.core.groundimage.GroundImageClickEvent import com.mapconductor.core.groundimage.GroundImageOverlayManager import com.mapconductor.core.groundimage.GroundImageRenderer import com.mapconductor.core.groundimage.GroundImageRendererFactory @@ -295,6 +296,16 @@ class GoogleMapViewController( return } + groundImageOverlayManager.find(touchPosition)?.let { entity -> + val event = + GroundImageClickEvent( + state = entity.state, + position = touchPosition, + ) + groundImageClickListener?.invoke(event) + return + } + mapClickListener?.let { coroutine.launch { it(position.toGeoPoint()) } } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt index f3ba381f..8ae26675 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt @@ -4,9 +4,6 @@ import androidx.core.graphics.drawable.toBitmap import com.google.android.gms.maps.model.BitmapDescriptorFactory import com.google.android.gms.maps.model.GroundOverlay import com.google.android.gms.maps.model.GroundOverlayOptions -import com.google.android.gms.maps.model.LatLng -import com.google.android.gms.maps.model.LatLngBounds -import com.mapconductor.core.features.GeoRectBounds import com.mapconductor.core.groundimage.AbstractGroundImageRenderer import com.mapconductor.core.groundimage.GroundImageEntity import com.mapconductor.core.groundimage.GroundImageOverlayManager @@ -44,12 +41,12 @@ class GoogleMapGroundImageRenderer( return@withContext newImages.map { state -> val bounds = state.bounds.toLatLngBounds() ?: return@withContext emptyList() val image = BitmapDescriptorFactory.fromBitmap(state.image.toBitmap()) - val alpha = state.alpha + val opacity = state.opacity val options = GroundOverlayOptions() .image(image) .positionFromBounds(bounds) -// .transparency(alpha) + .transparency(1.0f - opacity) holder.map.addGroundOverlay(options)?.also { it.tag = state.id } @@ -74,19 +71,10 @@ class GoogleMapGroundImageRenderer( groundOverlay.setPositionFromBounds(it) } } - groundOverlay.transparency = params.prevEntity.state.alpha + groundOverlay.transparency = 1.0f - params.entity.state.opacity groundOverlay.setImage(BitmapDescriptorFactory.fromBitmap(params.entity.state.image.toBitmap())) return@map groundOverlay } } } - - private fun GeoRectBounds.toLatLngBounds(): LatLngBounds? { - val sw = southWest ?: return null - val ne = northEast ?: return null - return LatLngBounds( - LatLng(sw.latitude, sw.longitude), - LatLng(ne.latitude, ne.longitude) - ) - } } diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt index 3fe4a0a5..c3e6c29b 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt @@ -9,7 +9,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner -import com.mapconductor.core.groundimage.OnGroundImageEventHandler +import com.mapconductor.core.groundimage.OnGroundImageClickEventHandler import com.mapconductor.core.map.MapViewBase import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.OnMapEventHandler @@ -31,8 +31,7 @@ fun HereMapView( onMarkerDragEnd: OnMarkerEventHandler? = {}, onMarkerAnimateStart: OnMarkerEventHandler? = {}, onMarkerAnimateEnd: OnMarkerEventHandler? = {}, - onGroundImageClick: OnGroundImageEventHandler? = null, - onGroundImageChange: OnGroundImageEventHandler? = null, + onGroundImageClick: OnGroundImageClickEventHandler? = null, content: (@Composable HereMapViewScope.() -> Unit)? = null, ) { val holderRef = remember { Ref() } diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt index 190e9739..e5fb8ec4 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt @@ -7,7 +7,7 @@ import androidx.compose.ui.node.Ref import androidx.compose.ui.platform.LocalContext import com.mapbox.maps.MapInitOptions import com.mapconductor.core.circle.OnCircleEventHandler -import com.mapconductor.core.groundimage.OnGroundImageEventHandler +import com.mapconductor.core.groundimage.OnGroundImageClickEventHandler import com.mapconductor.core.map.MapViewBase import com.mapconductor.core.map.OnMapEventHandler import com.mapconductor.core.marker.OnMarkerEventHandler @@ -27,8 +27,7 @@ fun MapboxMapView( onMarkerAnimateStart: OnMarkerEventHandler? = {}, onMarkerAnimateEnd: OnMarkerEventHandler? = {}, onCircleClick: OnCircleEventHandler? = {}, - onGroundImageClick: OnGroundImageEventHandler? = null, - onGroundImageChange: OnGroundImageEventHandler? = null, + onGroundImageClick: OnGroundImageClickEventHandler? = null, content: (@Composable MapboxMapViewScope.() -> Unit)? = null, ) { val holderRef = remember { Ref() } From e255ab8df2a0c49b7240a44845f4197e3b145334 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Tue, 12 Aug 2025 01:53:40 +0900 Subject: [PATCH 07/29] Impliment edit opacity slider'. --- example-app/build.gradle.kts | 1 + .../pages/groundimage/GroundImageMapPage.kt | 29 +++++++++++++++---- .../GroundImageMapPageViewModel.kt | 16 ++++++++-- gradle/libs.versions.toml | 2 ++ 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/example-app/build.gradle.kts b/example-app/build.gradle.kts index bc1fe2b5..3ef09501 100644 --- a/example-app/build.gradle.kts +++ b/example-app/build.gradle.kts @@ -104,6 +104,7 @@ dependencies { implementation(project(":mapconductor-for-mapbox")) implementation(project(":mapconductor-for-arcgis")) implementation(libs.androidx.vectordrawable) + implementation(libs.androidx.room.runtime.android) testImplementation(libs.junit) testImplementation(libs.androidx.core) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt index bc8c0435..987bed92 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt @@ -1,12 +1,17 @@ package com.mapconductor.example.pages.groundimage +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp @@ -51,12 +56,24 @@ fun GroundImageMapPage( ), title = "Messages", ) { - MapViewStatePanel( - mapViewState.value - ?.mapCameraPosition - ?.collectAsState() - ?.value, - ) + Column { + Text("opacity: ${"%.2f".format(viewModel.opacity)}", color = Color.Black) + Slider( + value = viewModel.opacity, + onValueChange = { newValue -> + viewModel.setOpacity(newValue) + }, + valueRange = 0.0f..1.0f, // スライダー範囲 + steps = 0, // 中間ステップ数(範囲内を1000分割) + colors = SliderDefaults.colors( + thumbColor = Color.Black, // つまみの色 + activeTrackColor = Color.DarkGray, // 値までのトラック + inactiveTrackColor = Color.LightGray, // 残りのトラック + activeTickColor = Color.Black, // 有効ステップの目盛り + inactiveTickColor = Color.White // 無効ステップの目盛り + ) + ) + } } ToastHost( diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt index 4609272a..dfb8f3f1 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt @@ -1,8 +1,11 @@ package com.mapconductor.example.pages.groundimage import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import androidx.room.util.copy import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.GeoRectBounds import com.mapconductor.core.groundimage.GroundImageClickEvent @@ -34,6 +37,8 @@ interface GroundImageMapPageViewModel { fun onGroundImageClick(clicked: GroundImageClickEvent) + fun setOpacity(value: Float) + fun showToast(text: String) fun removeToast(toastMessage: ToastMessage) @@ -79,9 +84,16 @@ class GroundImageMapPageViewModelImpl(override val imageResources: GroundImageRe northEast = GeoPoint.fromLatLong(40.773941, -74.12544) ) - override val image = imageResources.imageToggle0 + override fun setOpacity(value: Float) { + val normalizedValue = value.coerceIn(0f, 1f) + _opacity = normalizedValue + _groundImageState.value.opacity = normalizedValue + } + private var _opacity by mutableStateOf(1.0f) + override val opacity + get() = _opacity - override val opacity = 1.0f + override val image = imageResources.imageToggle0 private val _groundImageState: MutableState = mutableStateOf( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b82ae1aa..ecb653b2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,6 +30,7 @@ secretsGradlePlugin = "2.0.1" arcgisMapsKotlin = "200.7.0" uiTooling = "1.8.1" vectordrawable = "1.2.0" +roomRuntimeAndroid = "2.7.2" [libraries] # Android 基本 @@ -78,6 +79,7 @@ androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-co secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secretsGradlePlugin" } ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" } androidx-vectordrawable = { group = "androidx.vectordrawable", name = "vectordrawable", version.ref = "vectordrawable" } +androidx-room-runtime-android = { group = "androidx.room", name = "room-runtime-android", version.ref = "roomRuntimeAndroid" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 1b1e051e45c2299195580611514e0a5ea7599418 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Thu, 14 Aug 2025 14:57:32 +0900 Subject: [PATCH 08/29] update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bc78d6ca..e0f4433a 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ KtLintに従います。ローカルで実行する場合は、下記コマン | Map | ☑ | ☑ | ☑ | ☑ | | Marker | ☑ | ☑ | ☑ | ☑ | | Circle | ☑ | ☑ | ☐ | ☐ (wip) | -| Polyline | ☐ | ☐ | ☐ | ☐ | -| Polygon | ☐ | ☐ | ☐ | ☐ | -| GroundOverlay | ☐ | ☐ | ☐ | ☐ | -| RasterTileLayer | ☐ | ☐ | ☐ | ☐ | -| VectorTileLayer | ☐ | ☐ | ☐ | ☐ | +| Polyline | ☑ | ☑ | ☑ | ☑ | +| Polygon | ☐ | ☐ | ☐ | ☐ | +| GroundOverlay | ☐ | ☐ | ☐ | ☐ | +| RasterTileLayer | ☐ | ☐ | ☐ | ☐ | +| VectorTileLayer | ☐ | ☐ | ☐ | ☐ | From 69c97578dcd203937e79947eef93e6dd24d65fb1 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Thu, 14 Aug 2025 19:35:28 +0900 Subject: [PATCH 09/29] Changes groundImageState.opacity following the slider value --- .../com/mapconductor/example/DemoAppScreen.kt | 14 ++-- .../example/navigation/NavigationViewModel.kt | 2 +- .../groundimage/GroundImageMapComponent.kt | 5 +- .../pages/groundimage/GroundImageMapPage.kt | 33 +++++---- .../GroundImageMapPageViewModel.kt | 57 +++++---------- .../pages/groundimage/GroundImageResources.kt | 6 +- .../core/controller/MapViewController.kt | 14 +++- .../core/groundimage/GroundImage.kt | 25 ++++--- .../core/groundimage/GroundImageComponent.kt | 20 +++--- .../groundimage/GroundImageOverlayManager.kt | 71 ++++++++++--------- .../com/mapconductor/core/map/MapViewBase.kt | 2 +- .../mapconductor/core/marker/MarkerCompose.kt | 2 +- .../arcgis/ArcGISMapViewController.kt | 16 +++-- .../ArcGISGroundImageRenderer.kt} | 22 +++--- .../com/mapconductor/googlemaps/GeoRect.kt | 9 ++- .../googlemaps/GoogleMapViewController.kt | 3 +- .../GoogleMapGroundImageRenderer.kt | 20 ++++-- .../java/com/mapconductor/here/HereMapView.kt | 2 +- .../here/HereMapViewController.kt | 20 ++++-- .../groundimage/HereGroundImageRenderer.kt | 12 ++-- .../mapbox/MapboxMapViewController.kt | 14 ++-- .../groundImage/MapboxGroundImageRenderer.kt | 15 ++-- 22 files changed, 201 insertions(+), 183 deletions(-) rename mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/{groundoverlay/ArcGISGroundOverlayRenderer.kt => groundimage/ArcGISGroundImageRenderer.kt} (51%) diff --git a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt index 57940b79..75a445bd 100644 --- a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt +++ b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt @@ -45,13 +45,13 @@ fun DemoAppScreen() { ) } - val context = LocalContext.current - val groundImageResources = remember { - GroundImageResources( - imageToggle0 = ContextCompat.getDrawable(context, R.drawable.newark_nj_1922_0)!!, - imageToggle1 = ContextCompat.getDrawable(context, R.drawable.newark_nj_1922_1)!!, - ) - } + val groundImageResources = + remember { + GroundImageResources( + image = ContextCompat.getDrawable(context, R.drawable.newark_nj_1922_0)!!, + clickedImage = ContextCompat.getDrawable(context, R.drawable.newark_nj_1922_1)!!, + ) + } val sidebarItems = listOf( diff --git a/example-app/src/main/java/com/mapconductor/example/navigation/NavigationViewModel.kt b/example-app/src/main/java/com/mapconductor/example/navigation/NavigationViewModel.kt index e771adcf..0ed71062 100644 --- a/example-app/src/main/java/com/mapconductor/example/navigation/NavigationViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/navigation/NavigationViewModel.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel class NavigationViewModel : ViewModel() { - private val _currentPage = mutableStateOf("flyTo") + private val _currentPage = mutableStateOf("groundImage") val currentPage: State = _currentPage private val _isSidebarExpanded = mutableStateOf(false) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt index 07fd812d..5eccef76 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt @@ -6,7 +6,6 @@ import androidx.compose.ui.Modifier import com.mapconductor.core.groundimage.GroundImage import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapViewState -import com.mapconductor.core.map.OnMapEventHandler import com.mapconductor.example.MapViewContainer @Composable @@ -14,18 +13,16 @@ fun GroundImageMapComponent( mapViewState: MapViewState<*>?, viewModel: GroundImageMapPageViewModel, modifier: Modifier = Modifier, - onMapClick: OnMapEventHandler = {}, onGroundImageClick: OnGroundImageEventHandler = {}, ) { mapViewState?.let { mapViewState -> MapViewContainer( modifier = modifier, state = mapViewState, - onMapClick = onMapClick, onGroundImageClick = onGroundImageClick, ) { // GroundImage - key(viewModel.groundImageState.id) { + key(viewModel.groundImageState.fingerPrint()) { GroundImage(viewModel.groundImageState) } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt index 987bed92..64f044e1 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt @@ -12,24 +12,34 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat -import com.mapconductor.example.R +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.compose.viewModel import com.mapconductor.example.toast.ToastHost import com.mapconductor.example.ui.DemoMapPageScaffold -import com.mapconductor.example.ui.MapViewStatePanel import com.mapconductor.example.ui.MessageCard -import android.content.Context -import android.graphics.drawable.Drawable @Composable fun GroundImageMapPage( groundImageResources: GroundImageResources, - viewModel: GroundImageMapPageViewModel = GroundImageMapPageViewModelImpl(groundImageResources), onToggleSidebar: () -> Unit = {}, ) { + val viewModel: GroundImageMapPageViewModel = + viewModel( + factory = + object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(GroundImageMapPageViewModelImpl::class.java)) { + @Suppress("UNCHECKED_CAST") + return GroundImageMapPageViewModelImpl(groundImageResources) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } + }, + ) + DemoMapPageScaffold( initCameraPosition = viewModel.initCameraPosition, onToggleSidebar = onToggleSidebar, @@ -40,7 +50,6 @@ fun GroundImageMapPage( GroundImageMapComponent( mapViewState = mapViewState.value, viewModel = viewModel, - onMapClick = viewModel::onMapClick, onGroundImageClick = viewModel::onGroundImageClick, ) @@ -61,16 +70,16 @@ fun GroundImageMapPage( Slider( value = viewModel.opacity, onValueChange = { newValue -> - viewModel.setOpacity(newValue) + viewModel.opacity = newValue }, - valueRange = 0.0f..1.0f, // スライダー範囲 - steps = 0, // 中間ステップ数(範囲内を1000分割) + valueRange = 0.0f..1.0f, // スライダー範囲 + steps = 0, // 中間ステップ数(範囲内を1000分割) colors = SliderDefaults.colors( thumbColor = Color.Black, // つまみの色 activeTrackColor = Color.DarkGray, // 値までのトラック inactiveTrackColor = Color.LightGray, // 残りのトラック activeTickColor = Color.Black, // 有効ステップの目盛り - inactiveTickColor = Color.White // 無効ステップの目盛り + inactiveTickColor = Color.White, // 無効ステップの目盛り ) ) } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt index 662dfee2..2ae65db6 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt @@ -1,6 +1,5 @@ package com.mapconductor.example.pages.groundimage -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -25,28 +24,24 @@ interface GroundImageMapPageViewModel { val bounds: GeoRectBounds val imageResources: GroundImageResources val image: Drawable - val opacity: Float + var opacity: Float val groundImageState: GroundImageState fun onMapViewChanged(state: MapViewState<*>) fun cameraReset(listener: MapViewState.MoveCameraCallback? = null) - fun onMapClick(clicked: GeoPoint) - fun onGroundImageClick(clicked: GroundImageEvent) - fun setOpacity(value: Float) - fun showToast(text: String) fun removeToast(toastMessage: ToastMessage) } -class GroundImageMapPageViewModelImpl(override val imageResources: GroundImageResources) : - ViewModel(), +class GroundImageMapPageViewModelImpl( + override val imageResources: GroundImageResources, +) : ViewModel(), GroundImageMapPageViewModel { - override val initCameraPosition = MapCameraPosition( position = @@ -78,44 +73,30 @@ class GroundImageMapPageViewModelImpl(override val imageResources: GroundImageRe private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) override val messages: StateFlow> = _messages.asStateFlow() - override val bounds = GeoRectBounds( // ココが固定だと変化できないので、後日処理を書き換え。 - southWest = GeoPoint.fromLatLong(40.712216, -74.22655), - northEast = GeoPoint.fromLatLong(40.773941, -74.12544) - ) + override val bounds = + GeoRectBounds( // ココが固定だと変化できないので、後日処理を書き換え。 + southWest = GeoPoint.fromLatLong(40.712216, -74.22655), + northEast = GeoPoint.fromLatLong(40.773941, -74.12544), + ) - override fun setOpacity(value: Float) { - val normalizedValue = value.coerceIn(0f, 1f) - _opacity = normalizedValue - _groundImageState.value.opacity = normalizedValue - } - private var _opacity by mutableStateOf(1.0f) - override val opacity - get() = _opacity + override var opacity by mutableStateOf(0.5f) - override val image = imageResources.imageToggle0 + override var image by mutableStateOf(imageResources.image) - private val _groundImageState: MutableState = - mutableStateOf( + override val groundImageState + get() = GroundImageState( id = "groundImage", bounds = bounds, image = image, - ), - ) - - override val groundImageState: GroundImageState - get() = _groundImageState.value - - override fun onMapClick(clicked: GeoPoint) { - showToast("Map clicked at: ${clicked.toUrlValue()}") - } + opacity = opacity, + ) override fun onGroundImageClick(clicked: GroundImageEvent) { - if (clicked.state.image == imageResources.imageToggle0) { - _groundImageState.value.image = imageResources.imageToggle1 - } - else { - _groundImageState.value.image = imageResources.imageToggle0 + if (clicked.state.image == imageResources.image) { + image = imageResources.clickedImage + } else { + image = imageResources.image } showToast("Ground Image clicked.") diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageResources.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageResources.kt index ca3d2f3f..6d356daf 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageResources.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageResources.kt @@ -2,7 +2,7 @@ package com.mapconductor.example.pages.groundimage import android.graphics.drawable.Drawable -data class GroundImageResources ( - val imageToggle0: Drawable, - val imageToggle1: Drawable, +data class GroundImageResources( + val image: Drawable, + val clickedImage: Drawable, ) diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt b/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt index 1c6f8d09..e27a39f4 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt @@ -75,8 +75,14 @@ data class SearchRangeAnalysis( val markersInRange: List, ) -abstract class BaseMapViewController : - MapViewController { +abstract class BaseMapViewController< + ActualCamera, + ActualMarker, + ActualCircle, + ActualPolyline, + ActualPolygon, + ActualGroundImage, +> : MapViewController { abstract val markerRenderer: MarkerRenderer override val markerOverlayManager: MarkerOverlayManager by lazy { @@ -129,7 +135,9 @@ abstract class BaseMapViewController) - protected abstract fun onGroundImageOverlayManagerInitialized(overlayManager: GroundImageOverlayManager) + protected abstract fun onGroundImageOverlayManagerInitialized( + overlayManager: GroundImageOverlayManager, + ) protected abstract fun createMarkerOverlayManager(): MarkerOverlayManager diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt index 1ff605a1..6d3efc7e 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt @@ -4,11 +4,11 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow -import android.os.Parcelable import com.mapconductor.core.StateFlowDelegate import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.GeoRectBounds import android.graphics.drawable.Drawable +import android.os.Parcelable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged @@ -26,22 +26,22 @@ class GroundImageState( var opacity by mutableStateOf(opacity) var extra by mutableStateOf(extra) - fun fingerPrint(): GroundImageFingerPrint = GroundImageFingerPrint( - id = id.hashCode(), - bounds = bounds.hashCode(), - image = image.hashCode(), - opacity = opacity.hashCode(), - extra = extra?.hashCode() ?: 0, - ) + fun fingerPrint(): GroundImageFingerPrint = + GroundImageFingerPrint( + id = id.hashCode(), + bounds = bounds.hashCode(), + image = image.hashCode(), + opacity = opacity.hashCode(), + extra = extra?.hashCode() ?: 0, + ) - fun asFlow(): Flow = - snapshotFlow { fingerPrint() }.distinctUntilChanged() + fun asFlow(): Flow = snapshotFlow { fingerPrint() }.distinctUntilChanged() private fun generateId( bounds: GeoRectBounds, image: Drawable, opacity: Float, - extra: Parcelable? + extra: Parcelable?, ): Int { var result = bounds.hashCode() result = 31 * result + image.hashCode() @@ -50,8 +50,7 @@ class GroundImageState( return result } - override fun equals(other: Any?): Boolean = - (other as? GroundImageState)?.hashCode() == this.hashCode() + override fun equals(other: Any?): Boolean = (other as? GroundImageState)?.hashCode() == this.hashCode() override fun hashCode(): Int = fingerPrint().hashCode() } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt index 9068cf0e..bf96a026 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt @@ -7,12 +7,13 @@ import com.mapconductor.core.MapViewScope import com.mapconductor.core.features.GeoRectBounds import android.graphics.drawable.Drawable import android.os.Parcelable +import kotlinx.coroutines.flow.filter @Composable fun MapViewScope.GroundImage(state: GroundImageState) { - val rememberState = remember { state } + val rememberState = remember(state.fingerPrint()) { state } SideEffect { - groundImageFlow.value += rememberState + groundImageFlow.value = groundImageFlow.value.filter { it.id != state.id } + rememberState } } @@ -24,12 +25,13 @@ fun MapViewScope.GroundImage( id: String? = null, extra: Parcelable? = null, ) { - val state = GroundImageState( - bounds = bounds, - image = image, - opacity = opacity, - id = id, - extra = extra, - ) + val state = + GroundImageState( + bounds = bounds, + image = image, + opacity = opacity, + id = id, + extra = extra, + ) GroundImage(state) } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt index c3bccc44..2704a9d5 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt @@ -20,12 +20,10 @@ interface GroundImageOverlayManager { class GroundImageOverlayManagerImpl( val onAdd: suspend (List) -> List, val onChange: suspend (List>) -> List, - val onRemove: suspend (List>) -> Unit, val onPostProcess: (suspend () -> Unit)? = null, ) : GroundImageOverlayManager { - -val groundImageEntities = mutableMapOf>() + val groundImageEntities = mutableMapOf>() val semaphore = Semaphore(1) override suspend fun addGroundImages(images: List) { @@ -38,13 +36,16 @@ val groundImageEntities = mutableMapOf { - override val entity = GroundImageEntityImpl( - groundImage = prevEntity.groundImage, - state = it - ) - override val prevEntity = prevEntity - }) + updated.add( + object : GroundImageRenderer.UpdateParams { + override val entity = + GroundImageEntityImpl( + groundImage = prevEntity.groundImage, + state = it, + ) + override val prevEntity = prevEntity + }, + ) previous.remove(it.id) } else { added.add(it) @@ -63,10 +64,11 @@ val groundImageEntities = mutableMapOf actualOverlay?.let { val state = added[index] - val entity = GroundImageEntityImpl( - groundImage = it, - state = state - ) + val entity = + GroundImageEntityImpl( + groundImage = it, + state = state, + ) groundImageEntities[state.id] = entity } } @@ -77,10 +79,11 @@ val groundImageEntities = mutableMapOf actualOverlay?.let { val state = updated[index].entity.state - val entity = GroundImageEntityImpl( - groundImage = it, - state = state - ) + val entity = + GroundImageEntityImpl( + groundImage = it, + state = state, + ) groundImageEntities[state.id] = entity } } @@ -99,22 +102,25 @@ val groundImageEntities = mutableMapOf { - override val entity: GroundImageEntity = - GroundImageEntityImpl( - groundImage = prevEntity.groundImage, - state = state - ) - override val prevEntity: GroundImageEntity = prevEntity - }) + updated.add( + object : GroundImageRenderer.UpdateParams { + override val entity: GroundImageEntity = + GroundImageEntityImpl( + groundImage = prevEntity.groundImage, + state = state, + ) + override val prevEntity: GroundImageEntity = prevEntity + }, + ) val actualOverlays: List = onChange(updated) actualOverlays.forEachIndexed { index, actualOverlay -> actualOverlay?.let { - val entity = GroundImageEntityImpl( - groundImage = it, - state = state - ) + val entity = + GroundImageEntityImpl( + groundImage = it, + state = state, + ) groundImageEntities[state.id] = entity } } @@ -133,9 +139,8 @@ val groundImageEntities = mutableMapOf> = groundImageEntities.values.toList() - override fun find(position: IGeoPoint): GroundImageEntity? { - return groundImageEntities.values.find { entity -> + override fun find(position: IGeoPoint): GroundImageEntity? = + groundImageEntities.values.find { entity -> entity.state.bounds.contains(position) } - } } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt index 8920e323..7abb34dc 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt @@ -23,9 +23,9 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import com.mapconductor.core.CollectAndRenderOverlays import com.mapconductor.core.LocalCircleCollector +import com.mapconductor.core.LocalGroundImageCollector import com.mapconductor.core.LocalMarkerCollector import com.mapconductor.core.LocalPolylineCollector -import com.mapconductor.core.LocalGroundImageCollector import com.mapconductor.core.MapViewScope import com.mapconductor.core.ResourceProvider import com.mapconductor.core.controller.MapViewControllerAlias diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt index 948ff660..893dc601 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt @@ -9,7 +9,7 @@ import android.os.Parcelable @Composable fun MapViewScope.Marker(state: MarkerState) { - val rememberMarker = remember(state.fingerPrint()) { state } + val rememberMarker = remember(state.id) { state } SideEffect { markerFlow.value = markerFlow.value.filter { it.id != rememberMarker.id } + rememberMarker } diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt index fbeadf0a..9b6b6977 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt @@ -11,7 +11,7 @@ import com.arcgismaps.mapping.view.UpEvent import com.arcgismaps.mapping.view.extensions.motionEvent import com.mapconductor.arcgis.circle.ArcGISCircleRenderer import com.mapconductor.arcgis.circle.DefaultArcGISCircleRenderer -import com.mapconductor.arcgis.groundoverlay.ArcGISGroundImageRenderer +import com.mapconductor.arcgis.groundimage.ArcGISGroundImageRenderer import com.mapconductor.arcgis.marker.ArcGISMarkerRenderer import com.mapconductor.arcgis.marker.DefaultArcGISMarkerRender import com.mapconductor.arcgis.polygon.ArcGISPolygonRenderer @@ -179,13 +179,16 @@ class ArcGISMapViewController( coroutine = coroutine, ) - override fun onGroundImageOverlayManagerInitialized(overlayManager: GroundImageOverlayManager) { + override fun onGroundImageOverlayManagerInitialized( + overlayManager: GroundImageOverlayManager, + ) { } - override val groundImageRenderer: GroundImageRenderer = ArcGISGroundImageRenderer( - holder = holder, - coroutine = coroutine, - ) + override val groundImageRenderer: GroundImageRenderer = + ArcGISGroundImageRenderer( + holder = holder, + coroutine = coroutine, + ) override fun onCircleOverlayManagerInitialized(overlayManager: CircleOverlayManager) { } @@ -351,6 +354,7 @@ class ArcGISMapViewController( override suspend fun addPolylines(data: List) = polylineOverlayManager.addPolylines(data) override suspend fun updatePolyline(state: PolylineState) = polylineOverlayManager.updatePolyline(state) + override suspend fun addGroundImages(data: List) { TODO("Not yet implemented") } diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundoverlay/ArcGISGroundOverlayRenderer.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundimage/ArcGISGroundImageRenderer.kt similarity index 51% rename from mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundoverlay/ArcGISGroundOverlayRenderer.kt rename to mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundimage/ArcGISGroundImageRenderer.kt index 483d019d..2d92546c 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundoverlay/ArcGISGroundOverlayRenderer.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundimage/ArcGISGroundImageRenderer.kt @@ -1,6 +1,7 @@ -package com.mapconductor.arcgis.groundoverlay +package com.mapconductor.arcgis.groundimage import com.arcgismaps.mapping.view.Graphic +import com.mapconductor.arcgis.ArcGISActualGroundImage import com.mapconductor.arcgis.ArcGISMapViewHolder import com.mapconductor.core.groundimage.AbstractGroundImageRenderer import com.mapconductor.core.groundimage.GroundImageEntity @@ -9,17 +10,16 @@ import com.mapconductor.core.groundimage.GroundImageState import kotlinx.coroutines.CoroutineScope class ArcGISGroundImageRenderer( - override val holder: ArcGISMapViewHolder, - override val coroutine: CoroutineScope - ) : AbstractGroundImageRenderer() { - override suspend fun addGroundImages(newGroundImages: List): List { - return emptyList() - } + override val holder: ArcGISMapViewHolder, + override val coroutine: CoroutineScope, +) : AbstractGroundImageRenderer() { + override suspend fun addGroundImages(newGroundImages: List): List = + emptyList() - override suspend fun changeGroundImages(changes: List>): List { - return emptyList() - } + override suspend fun changeGroundImages( + changes: List>, + ): List = emptyList() - override suspend fun removeGroundImages(removeEntities: List>) { + override suspend fun removeGroundImages(removeEntities: List>) { } } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoRect.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoRect.kt index e212758f..408e7492 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoRect.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoRect.kt @@ -11,13 +11,12 @@ fun GeoRectBounds.toLatLngBounds(): LatLngBounds? { return LatLngBounds( LatLng(sw.latitude, sw.longitude), - LatLng(ne.latitude, ne.longitude) + LatLng(ne.latitude, ne.longitude), ) } -fun LatLngBounds.toGeoRectBounds(): GeoRectBounds { - return GeoRectBounds( +fun LatLngBounds.toGeoRectBounds(): GeoRectBounds = + GeoRectBounds( southWest = GeoPoint(southwest.latitude, southwest.longitude), - northEast = GeoPoint(northeast.latitude, northeast.longitude) + northEast = GeoPoint(northeast.latitude, northeast.longitude), ) -} diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt index 655c04ce..fd2872fa 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt @@ -82,7 +82,8 @@ class GoogleMapViewController( private val polylineRendererFactory: PolylineRendererFactory = DefaultGoogleMapPolylineRenderer(), private val polygonRendererFactory: PolygonRendererFactory = DefaultGoogleMapPolygonRenderer(), private val circleRendererFactory: CircleRendererFactory = DefaultGoogleMapCircleRenderer(), - private val groundImageRendererFactory: GroundImageRendererFactory = DefaultGoogleMapGroundImageRenderer(), + private val groundImageRendererFactory: GroundImageRendererFactory = + DefaultGoogleMapGroundImageRenderer(), ) : BaseMapViewController(), IGoogleMapViewController, OnCameraMoveStartedListener, diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt index 8ae26675..6f22b9b0 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt @@ -22,7 +22,7 @@ class DefaultGoogleMapGroundImageRenderer : GroundImageRendererFactory) -> List, onChange: suspend (List>) -> List, onRemove: suspend (List>) -> Unit, - onPostProcess: (suspend () -> Unit)? + onPostProcess: (suspend () -> Unit)?, ): GroundImageOverlayManager = GroundImageOverlayManagerImpl( onRemove = onRemove, @@ -60,19 +60,27 @@ class GoogleMapGroundImageRenderer( } } - override suspend fun changeGroundImages(changes: List>): List { + override suspend fun changeGroundImages( + changes: List>, + ): List { return withContext(coroutine.coroutineContext) { - return@withContext changes.map { params-> + return@withContext changes.map { params -> val groundOverlay = params.entity.groundImage - val finger = params.entity.state.fingerPrint() + val finger = params.entity.fingerPrint val prevFinger = params.prevEntity.fingerPrint if (finger.bounds != prevFinger.bounds) { - params.entity.state.bounds.toLatLngBounds()?.let{ + params.entity.state.bounds.toLatLngBounds()?.let { groundOverlay.setPositionFromBounds(it) } } groundOverlay.transparency = 1.0f - params.entity.state.opacity - groundOverlay.setImage(BitmapDescriptorFactory.fromBitmap(params.entity.state.image.toBitmap())) + if (finger.image != prevFinger.image) { + val bitmap = + params.entity.state.image + .toBitmap() + val bitmapDesc = BitmapDescriptorFactory.fromBitmap(bitmap) + groundOverlay.setImage(bitmapDesc) + } return@map groundOverlay } } diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt index b4bc157a..fe3b4887 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt @@ -9,8 +9,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner -import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.circle.OnCircleEventHandler +import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapViewBase import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.OnMapEventHandler diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt index 27f4b526..e9dd2f33 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt @@ -83,10 +83,12 @@ class HereMapViewController( baseHexSideLength = 100000, // 100km - 中ズームレベルに適した値 ), private val markerRendererFactory: MarkerRendererFactory = DefaultHereMapMarkerRenderer(), - private val polylineRendererFactory: PolylineRendererFactory = DefaultHereMapPolylineRenderer(), + private val polylineRendererFactory: PolylineRendererFactory = + DefaultHereMapPolylineRenderer(), private val polygonRendererFactory: PolygonRendererFactory = DefaultHereMapPolygonRenderer(), private val circleRendererFactory: CircleRendererFactory = DefaultHereMapCircleRenderer(), - private val groundImageRendererFactory: GroundImageRendererFactory = DefaultHereMapGroundImageRenderer(), + private val groundImageRendererFactory: GroundImageRendererFactory = + DefaultHereMapGroundImageRenderer(), ) : BaseMapViewController< MapCamera.State, HereMapActualMarker, @@ -138,10 +140,12 @@ class HereMapViewController( coroutine = coroutine, ) - override val groundImageRenderer: GroundImageRenderer = HereMapGroundImageRenderer( - holder = holder, - coroutine = coroutine, - ) + override val groundImageRenderer: GroundImageRenderer = + HereMapGroundImageRenderer( + holder = holder, + coroutine = coroutine, + ) + override fun onCircleOverlayManagerInitialized(overlayManager: CircleOverlayManager) { } @@ -154,7 +158,9 @@ class HereMapViewController( override fun onMarkerOverlayManagerInitialized(overlayManager: MarkerOverlayManager) { } - override fun onGroundImageOverlayManagerInitialized(overlayManager: GroundImageOverlayManager) { + override fun onGroundImageOverlayManagerInitialized( + overlayManager: GroundImageOverlayManager, + ) { } override fun createPolylineOverlayManager(): PolylineOverlayManager = diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt index c5d6e3c2..b5da8cca 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt @@ -10,15 +10,13 @@ import com.mapconductor.core.groundimage.GroundImageRendererFactory import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.here.HereMapViewHolder import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.newCoroutineContext -import kotlinx.coroutines.withContext class DefaultHereMapGroundImageRenderer : GroundImageRendererFactory { override fun create( onAdd: suspend (List) -> List, onChange: suspend (List>) -> List, onRemove: suspend (List>) -> Unit, - onPostProcess: (suspend () -> Unit)? + onPostProcess: (suspend () -> Unit)?, ): GroundImageOverlayManager = GroundImageOverlayManagerImpl( onRemove = onRemove, @@ -30,7 +28,7 @@ class DefaultHereMapGroundImageRenderer : GroundImageRendererFactory { class HereMapGroundImageRenderer( override val holder: HereMapViewHolder, - override val coroutine: CoroutineScope + override val coroutine: CoroutineScope, ) : AbstractGroundImageRenderer() { override suspend fun addGroundImages(newGroundImages: List): List { return emptyList() @@ -44,9 +42,9 @@ class HereMapGroundImageRenderer( // } } - override suspend fun changeGroundImages(changes: List>): List { - return emptyList() - } + override suspend fun changeGroundImages( + changes: List>, + ): List = emptyList() override suspend fun removeGroundImages(removeEntities: List>) { } diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt index c84a17a5..748cf1bf 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt @@ -214,13 +214,16 @@ internal class MapboxMapViewController( layer = circleLayer, ) - override fun onGroundImageOverlayManagerInitialized(overlayManager: GroundImageOverlayManager) { + override fun onGroundImageOverlayManagerInitialized( + overlayManager: GroundImageOverlayManager, + ) { } - override val groundImageRenderer: GroundImageRenderer = MapboxGroundImageRenderer( - holder = holder, - coroutine = coroutine, - ) + override val groundImageRenderer: GroundImageRenderer = + MapboxGroundImageRenderer( + holder = holder, + coroutine = coroutine, + ) override fun onCircleOverlayManagerInitialized(overlayManager: CircleOverlayManager) { } @@ -259,6 +262,7 @@ internal class MapboxMapViewController( override suspend fun addPolylines(data: List) = polylineOverlayManager.addPolylines(data) override suspend fun updatePolyline(state: PolylineState) = polylineOverlayManager.updatePolyline(state) + override suspend fun addGroundImages(data: List) { TODO("Not yet implemented") } diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt index 208b2793..1a5b43d9 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt @@ -9,16 +9,13 @@ import com.mapconductor.mapbox.MapboxMapViewHolder import kotlinx.coroutines.CoroutineScope class MapboxGroundImageRenderer( - override val holder: MapboxMapViewHolder, - override val coroutine: CoroutineScope - ) : AbstractGroundImageRenderer() { - override suspend fun addGroundImages(newGroundImages: List): List { - return emptyList() - } + override val holder: MapboxMapViewHolder, + override val coroutine: CoroutineScope, +) : AbstractGroundImageRenderer() { + override suspend fun addGroundImages(newGroundImages: List): List = emptyList() - override suspend fun changeGroundImages(changes: List>): List { - return emptyList() - } + override suspend fun changeGroundImages(changes: List>): List = + emptyList() override suspend fun removeGroundImages(removeEntities: List>) { } From 24a522680b516c690220d8594a36513f2fe7f6d1 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Sun, 17 Aug 2025 22:41:57 +0900 Subject: [PATCH 10/29] Initial implementation of GroundImage (GroundOverlay). NE and SW markers are not yet linked with bounds, but committing as instructed. --- .../groundimage/GroundImageMapComponent.kt | 13 +++++ .../pages/groundimage/GroundImageMapPage.kt | 3 +- .../GroundImageMapPageViewModel.kt | 53 +++++++++++++++++-- .../core/groundimage/GroundImage.kt | 4 +- .../core/groundimage/GroundImageComponent.kt | 23 ++++++++ 5 files changed, 90 insertions(+), 6 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt index 5eccef76..4146ebd7 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt @@ -4,8 +4,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Modifier import com.mapconductor.core.groundimage.GroundImage +import com.mapconductor.core.groundimage.NorthEast import com.mapconductor.core.groundimage.OnGroundImageEventHandler +import com.mapconductor.core.groundimage.SouthWest import com.mapconductor.core.map.MapViewState +import com.mapconductor.core.marker.OnMarkerEventHandler import com.mapconductor.example.MapViewContainer @Composable @@ -14,17 +17,27 @@ fun GroundImageMapComponent( viewModel: GroundImageMapPageViewModel, modifier: Modifier = Modifier, onGroundImageClick: OnGroundImageEventHandler = {}, + onMarkerDrag: OnMarkerEventHandler = {}, ) { mapViewState?.let { mapViewState -> MapViewContainer( modifier = modifier, state = mapViewState, onGroundImageClick = onGroundImageClick, + onMarkerDrag = onMarkerDrag, ) { // GroundImage key(viewModel.groundImageState.fingerPrint()) { GroundImage(viewModel.groundImageState) } + // SouthWest + key(viewModel.southWestMarkerState.fingerPrint()) { + SouthWest(viewModel.southWestMarkerState) + } + // NorthEast + key(viewModel.northEastMarkerState.fingerPrint()) { + NorthEast(viewModel.northEastMarkerState) + } } } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt index 64f044e1..1bfb0f40 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt @@ -51,6 +51,7 @@ fun GroundImageMapPage( mapViewState = mapViewState.value, viewModel = viewModel, onGroundImageClick = viewModel::onGroundImageClick, + onMarkerDrag = viewModel::onMarkerDrag, ) // Message Card @@ -73,7 +74,7 @@ fun GroundImageMapPage( viewModel.opacity = newValue }, valueRange = 0.0f..1.0f, // スライダー範囲 - steps = 0, // 中間ステップ数(範囲内を1000分割) + steps = 0, colors = SliderDefaults.colors( thumbColor = Color.Black, // つまみの色 activeTrackColor = Color.DarkGray, // 値までのトラック diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt index 2ae65db6..a2181360 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt @@ -3,6 +3,7 @@ package com.mapconductor.example.pages.groundimage import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color import androidx.lifecycle.ViewModel import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.GeoRectBounds @@ -10,6 +11,8 @@ import com.mapconductor.core.groundimage.GroundImageEvent import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewState +import com.mapconductor.core.marker.DefaultIcon +import com.mapconductor.core.marker.MarkerState import com.mapconductor.example.toast.ToastMessage import android.graphics.drawable.Drawable import kotlinx.coroutines.flow.MutableStateFlow @@ -22,6 +25,10 @@ interface GroundImageMapPageViewModel { val messages: StateFlow> val bounds: GeoRectBounds + var southWest: GeoPoint + val southWestMarkerState: MarkerState + var northEast: GeoPoint + val northEastMarkerState: MarkerState val imageResources: GroundImageResources val image: Drawable var opacity: Float @@ -33,6 +40,8 @@ interface GroundImageMapPageViewModel { fun onGroundImageClick(clicked: GroundImageEvent) + fun onMarkerDrag(dragged: MarkerState) + fun showToast(text: String) fun removeToast(toastMessage: ToastMessage) @@ -73,10 +82,37 @@ class GroundImageMapPageViewModelImpl( private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) override val messages: StateFlow> = _messages.asStateFlow() - override val bounds = - GeoRectBounds( // ココが固定だと変化できないので、後日処理を書き換え。 - southWest = GeoPoint.fromLatLong(40.712216, -74.22655), - northEast = GeoPoint.fromLatLong(40.773941, -74.12544), + override var southWest by mutableStateOf(GeoPoint.fromLatLong(40.712216, -74.22655)) + override val southWestMarkerState = + MarkerState( + id = "south_west", + position = southWest, + icon = + DefaultIcon( + fillColor = Color.Blue, + strokeColor = Color.White, + label = "SW", + ), + draggable = true, + ) + + override var northEast by mutableStateOf(GeoPoint.fromLatLong(40.773941,-74.12544)) + override val northEastMarkerState = + MarkerState( + id = "north_east", + position = northEast, + icon = + DefaultIcon( + fillColor = Color.Red, + strokeColor = Color.White, + label = "NE", + ), + draggable = true, + ) + + override val bounds: GeoRectBounds = GeoRectBounds( + southWest = southWest, + northEast = northEast, ) override var opacity by mutableStateOf(0.5f) @@ -102,6 +138,15 @@ class GroundImageMapPageViewModelImpl( showToast("Ground Image clicked.") } + override fun onMarkerDrag(dragged: MarkerState) { + if (dragged.id == "south_west") { + southWest = GeoPoint.fromLatLong(dragged.position.latitude, dragged.position.longitude) + } + else { + northEast = GeoPoint.fromLatLong(dragged.position.latitude, dragged.position.longitude) + } + } + override fun showToast(text: String) { this._messages.value = this._messages.value + ToastMessage(text = text) } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt index 6d3efc7e..aa22b4a0 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.snapshotFlow import com.mapconductor.core.StateFlowDelegate import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.GeoRectBounds +import com.mapconductor.core.marker.MarkerState import android.graphics.drawable.Drawable import android.os.Parcelable import kotlinx.coroutines.flow.Flow @@ -21,7 +22,8 @@ class GroundImageState( ) { val id = (id ?: generateId(bounds, image, opacity, extra)).toString() - var bounds by StateFlowDelegate(bounds) +// var bounds by StateFlowDelegate(bounds) + var bounds by mutableStateOf(bounds) var image by mutableStateOf(image) var opacity by mutableStateOf(opacity) var extra by mutableStateOf(extra) diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt index bf96a026..3520faba 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt @@ -3,8 +3,15 @@ package com.mapconductor.core.groundimage import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect 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.core.MapViewScope +import com.mapconductor.core.circle.Circle +import com.mapconductor.core.circle.CircleState import com.mapconductor.core.features.GeoRectBounds +import com.mapconductor.core.features.IGeoPoint +import com.mapconductor.core.marker.MarkerState import android.graphics.drawable.Drawable import android.os.Parcelable import kotlinx.coroutines.flow.filter @@ -17,6 +24,22 @@ fun MapViewScope.GroundImage(state: GroundImageState) { } } +@Composable +fun MapViewScope.SouthWest(state: MarkerState) { + val rememberState = remember(state.fingerPrint()) { state } + SideEffect { + markerFlow.value = markerFlow.value.filter { it.id != state.id } + rememberState + } +} + +@Composable +fun MapViewScope.NorthEast(state: MarkerState) { + val rememberState = remember(state.fingerPrint()) { state } + SideEffect { + markerFlow.value = markerFlow.value.filter { it.id != state.id } + rememberState + } +} + @Composable fun MapViewScope.GroundImage( bounds: GeoRectBounds, From d705245ac907c3d49498f8a5eaff02c2a51b23ff Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Mon, 18 Aug 2025 00:57:45 +0900 Subject: [PATCH 11/29] WIP: GroundImage feature implementation. Known issue: recomposition does not occur, committing current state. --- .../mapconductor/example/MapViewContainer.kt | 1 + .../groundimage/GroundImageMapComponent.kt | 18 ++++----- .../pages/groundimage/GroundImageMapPage.kt | 4 ++ .../GroundImageMapPageViewModel.kt | 37 ++++++++++++------- .../core/groundimage/GroundImage.kt | 4 +- .../core/groundimage/GroundImageComponent.kt | 16 -------- 6 files changed, 39 insertions(+), 41 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt b/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt index 36b09250..e7bf5de9 100644 --- a/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt +++ b/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt @@ -9,6 +9,7 @@ import com.mapconductor.core.circle.OnCircleEventHandler import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.OnMapEventHandler +import com.mapconductor.core.marker.MarkerState import com.mapconductor.core.marker.OnMarkerEventHandler import com.mapconductor.core.polyline.OnPolylineEventHandler import com.mapconductor.googlemaps.GoogleMapViewState diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt index 4146ebd7..17106ab8 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt @@ -4,10 +4,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Modifier import com.mapconductor.core.groundimage.GroundImage -import com.mapconductor.core.groundimage.NorthEast import com.mapconductor.core.groundimage.OnGroundImageEventHandler -import com.mapconductor.core.groundimage.SouthWest import com.mapconductor.core.map.MapViewState +import com.mapconductor.core.marker.Marker +import com.mapconductor.core.marker.MarkerState import com.mapconductor.core.marker.OnMarkerEventHandler import com.mapconductor.example.MapViewContainer @@ -15,6 +15,8 @@ import com.mapconductor.example.MapViewContainer fun GroundImageMapComponent( mapViewState: MapViewState<*>?, viewModel: GroundImageMapPageViewModel, + southWestMarker: MarkerState, + northEastMarker: MarkerState, modifier: Modifier = Modifier, onGroundImageClick: OnGroundImageEventHandler = {}, onMarkerDrag: OnMarkerEventHandler = {}, @@ -30,14 +32,10 @@ fun GroundImageMapComponent( key(viewModel.groundImageState.fingerPrint()) { GroundImage(viewModel.groundImageState) } - // SouthWest - key(viewModel.southWestMarkerState.fingerPrint()) { - SouthWest(viewModel.southWestMarkerState) - } - // NorthEast - key(viewModel.northEastMarkerState.fingerPrint()) { - NorthEast(viewModel.northEastMarkerState) - } + + // BoundsMarkers + Marker(southWestMarker) + Marker(northEastMarker) } } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt index a6fce2c2..c3c744b5 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt @@ -46,8 +46,12 @@ fun GroundImageMapPage( onMapViewStateChanged = viewModel::onMapViewChanged, ) { paddingValues -> val mapViewState = viewModel.mapViewState.collectAsState() + val southWestMarker = viewModel.southWest + val northEastMarker = viewModel.northEast GroundImageMapComponent( + southWestMarker = southWestMarker, + northEastMarker = northEastMarker, mapViewState = mapViewState.value, viewModel = viewModel, onGroundImageClick = viewModel::onGroundImageClick, diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt index a2181360..8774d745 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt @@ -1,5 +1,6 @@ package com.mapconductor.example.pages.groundimage +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -25,10 +26,8 @@ interface GroundImageMapPageViewModel { val messages: StateFlow> val bounds: GeoRectBounds - var southWest: GeoPoint - val southWestMarkerState: MarkerState - var northEast: GeoPoint - val northEastMarkerState: MarkerState + val southWest: MarkerState + val northEast: MarkerState val imageResources: GroundImageResources val image: Drawable var opacity: Float @@ -82,11 +81,11 @@ class GroundImageMapPageViewModelImpl( private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) override val messages: StateFlow> = _messages.asStateFlow() - override var southWest by mutableStateOf(GeoPoint.fromLatLong(40.712216, -74.22655)) - override val southWestMarkerState = + private val _southWest: MutableState = + mutableStateOf( MarkerState( id = "south_west", - position = southWest, + position = GeoPoint.fromLatLong(40.712216, -74.22655), icon = DefaultIcon( fillColor = Color.Blue, @@ -95,12 +94,15 @@ class GroundImageMapPageViewModelImpl( ), draggable = true, ) + ) + override val southWest: MarkerState + get() = _southWest.value - override var northEast by mutableStateOf(GeoPoint.fromLatLong(40.773941,-74.12544)) - override val northEastMarkerState = + private val _northEast: MutableState = + mutableStateOf( MarkerState( id = "north_east", - position = northEast, + position = GeoPoint.fromLatLong(40.773941,-74.12544), icon = DefaultIcon( fillColor = Color.Red, @@ -109,10 +111,13 @@ class GroundImageMapPageViewModelImpl( ), draggable = true, ) + ) + override val northEast: MarkerState + get() = _northEast.value override val bounds: GeoRectBounds = GeoRectBounds( - southWest = southWest, - northEast = northEast, + southWest = GeoPoint.from(southWest.position), + northEast = GeoPoint.from(northEast.position), ) override var opacity by mutableStateOf(0.5f) @@ -140,10 +145,14 @@ class GroundImageMapPageViewModelImpl( override fun onMarkerDrag(dragged: MarkerState) { if (dragged.id == "south_west") { - southWest = GeoPoint.fromLatLong(dragged.position.latitude, dragged.position.longitude) + val southWest = GeoPoint.from(dragged.position) + val northEast = groundImageState.bounds.northEast + groundImageState.bounds = GeoRectBounds(southWest, northEast) } else { - northEast = GeoPoint.fromLatLong(dragged.position.latitude, dragged.position.longitude) + val southWest = groundImageState.bounds.southWest + val northEast = GeoPoint.from(dragged.position) + groundImageState.bounds = GeoRectBounds(southWest, northEast) } } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt index aa22b4a0..767a6de7 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt @@ -37,7 +37,9 @@ class GroundImageState( extra = extra?.hashCode() ?: 0, ) - fun asFlow(): Flow = snapshotFlow { fingerPrint() }.distinctUntilChanged() + fun asFlow(): Flow = snapshotFlow { + fingerPrint() + }.distinctUntilChanged() private fun generateId( bounds: GeoRectBounds, diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt index 3520faba..ff026d47 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt @@ -24,22 +24,6 @@ fun MapViewScope.GroundImage(state: GroundImageState) { } } -@Composable -fun MapViewScope.SouthWest(state: MarkerState) { - val rememberState = remember(state.fingerPrint()) { state } - SideEffect { - markerFlow.value = markerFlow.value.filter { it.id != state.id } + rememberState - } -} - -@Composable -fun MapViewScope.NorthEast(state: MarkerState) { - val rememberState = remember(state.fingerPrint()) { state } - SideEffect { - markerFlow.value = markerFlow.value.filter { it.id != state.id } + rememberState - } -} - @Composable fun MapViewScope.GroundImage( bounds: GeoRectBounds, From 8b878646859b4cf48783059af9a1831677900349 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Mon, 18 Aug 2025 02:33:56 +0900 Subject: [PATCH 12/29] Fix: GroundImage does not update by following markers --- .../mapconductor/example/MapViewContainer.kt | 1 - .../groundimage/GroundImageMapComponent.kt | 10 +- .../GroundImageMapPageViewModel.kt | 91 ++++++++----------- .../core/groundimage/GroundImage.kt | 9 +- .../core/groundimage/GroundImageComponent.kt | 16 +--- 5 files changed, 54 insertions(+), 73 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt b/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt index e7bf5de9..36b09250 100644 --- a/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt +++ b/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt @@ -9,7 +9,6 @@ import com.mapconductor.core.circle.OnCircleEventHandler import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.OnMapEventHandler -import com.mapconductor.core.marker.MarkerState import com.mapconductor.core.marker.OnMarkerEventHandler import com.mapconductor.core.polyline.OnPolylineEventHandler import com.mapconductor.googlemaps.GoogleMapViewState diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt index 17106ab8..42be06dc 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt @@ -29,13 +29,17 @@ fun GroundImageMapComponent( onMarkerDrag = onMarkerDrag, ) { // GroundImage - key(viewModel.groundImageState.fingerPrint()) { + key(viewModel.groundImageState.id) { GroundImage(viewModel.groundImageState) } // BoundsMarkers - Marker(southWestMarker) - Marker(northEastMarker) + key(southWestMarker.id) { + Marker(southWestMarker) + } + key(northEastMarker.id) { + Marker(northEastMarker) + } } } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt index 8774d745..b4aacf0a 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt @@ -1,6 +1,5 @@ package com.mapconductor.example.pages.groundimage -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -25,7 +24,6 @@ interface GroundImageMapPageViewModel { val mapViewState: StateFlow?> val messages: StateFlow> - val bounds: GeoRectBounds val southWest: MarkerState val northEast: MarkerState val imageResources: GroundImageResources @@ -35,8 +33,6 @@ interface GroundImageMapPageViewModel { fun onMapViewChanged(state: MapViewState<*>) - fun cameraReset(listener: MapViewState.MoveCameraCallback? = null) - fun onGroundImageClick(clicked: GroundImageEvent) fun onMarkerDrag(dragged: MarkerState) @@ -70,60 +66,54 @@ class GroundImageMapPageViewModelImpl( this._mapViewState.value = state } - override fun cameraReset(listener: MapViewState.MoveCameraCallback?) { - this.mapViewState.value?.moveCameraTo( - cameraPosition = initCameraPosition, - durationMs = 3000, - listener = listener, - ) - } - private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) override val messages: StateFlow> = _messages.asStateFlow() - private val _southWest: MutableState = - mutableStateOf( - MarkerState( - id = "south_west", - position = GeoPoint.fromLatLong(40.712216, -74.22655), - icon = - DefaultIcon( - fillColor = Color.Blue, - strokeColor = Color.White, - label = "SW", - ), - draggable = true, - ) - ) - override val southWest: MarkerState - get() = _southWest.value - - private val _northEast: MutableState = - mutableStateOf( - MarkerState( - id = "north_east", - position = GeoPoint.fromLatLong(40.773941,-74.12544), - icon = - DefaultIcon( - fillColor = Color.Red, - strokeColor = Color.White, - label = "NE", - ), - draggable = true, - ) + override val southWest: MarkerState = + MarkerState( + id = "south_west", + position = + GeoPoint( + latitude = 40.712216, + longitude = -74.22655, + ), + icon = + DefaultIcon( + fillColor = Color.Blue, + strokeColor = Color.White, + label = "SW", + ), + draggable = true, ) - override val northEast: MarkerState - get() = _northEast.value - override val bounds: GeoRectBounds = GeoRectBounds( - southWest = GeoPoint.from(southWest.position), - northEast = GeoPoint.from(northEast.position), + override val northEast: MarkerState = + MarkerState( + id = "north_east", + position = + GeoPoint( + latitude = 40.773941, + longitude = -74.12544, + ), + icon = + DefaultIcon( + fillColor = Color.Red, + strokeColor = Color.White, + label = "NE", + ), + draggable = true, ) override var opacity by mutableStateOf(0.5f) override var image by mutableStateOf(imageResources.image) + private var bounds by mutableStateOf( + GeoRectBounds( + southWest = GeoPoint.from(southWest.position), + northEast = GeoPoint.from(northEast.position), + ), + ) + override val groundImageState get() = GroundImageState( @@ -147,12 +137,11 @@ class GroundImageMapPageViewModelImpl( if (dragged.id == "south_west") { val southWest = GeoPoint.from(dragged.position) val northEast = groundImageState.bounds.northEast - groundImageState.bounds = GeoRectBounds(southWest, northEast) - } - else { + bounds = GeoRectBounds(southWest, northEast) + } else { val southWest = groundImageState.bounds.southWest val northEast = GeoPoint.from(dragged.position) - groundImageState.bounds = GeoRectBounds(southWest, northEast) + bounds = GeoRectBounds(southWest, northEast) } } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt index 767a6de7..0f7068f3 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImage.kt @@ -4,10 +4,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow -import com.mapconductor.core.StateFlowDelegate import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.GeoRectBounds -import com.mapconductor.core.marker.MarkerState import android.graphics.drawable.Drawable import android.os.Parcelable import kotlinx.coroutines.flow.Flow @@ -37,9 +35,10 @@ class GroundImageState( extra = extra?.hashCode() ?: 0, ) - fun asFlow(): Flow = snapshotFlow { - fingerPrint() - }.distinctUntilChanged() + fun asFlow(): Flow = + snapshotFlow { + fingerPrint() + }.distinctUntilChanged() private fun generateId( bounds: GeoRectBounds, diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt index ff026d47..06c84bb6 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageComponent.kt @@ -1,26 +1,16 @@ package com.mapconductor.core.groundimage import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.remember -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp +import androidx.compose.runtime.LaunchedEffect import com.mapconductor.core.MapViewScope -import com.mapconductor.core.circle.Circle -import com.mapconductor.core.circle.CircleState import com.mapconductor.core.features.GeoRectBounds -import com.mapconductor.core.features.IGeoPoint -import com.mapconductor.core.marker.MarkerState import android.graphics.drawable.Drawable import android.os.Parcelable -import kotlinx.coroutines.flow.filter @Composable fun MapViewScope.GroundImage(state: GroundImageState) { - val rememberState = remember(state.fingerPrint()) { state } - SideEffect { - groundImageFlow.value = groundImageFlow.value.filter { it.id != state.id } + rememberState + LaunchedEffect(state.fingerPrint()) { + groundImageFlow.value = groundImageFlow.value.filter { it.id != state.id } + state } } From 5ae84721c77053b4cb36c7374c59d66fdd5ef76b Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Mon, 18 Aug 2025 03:01:10 +0900 Subject: [PATCH 13/29] Fix: GeoRectBounds.extend is incorrect --- .../GroundImageMapPageViewModel.kt | 11 +-- .../core/features/GeoRectBounds.kt | 84 +++++++++++++------ 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt index b4aacf0a..aaa47173 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt @@ -134,14 +134,9 @@ class GroundImageMapPageViewModelImpl( } override fun onMarkerDrag(dragged: MarkerState) { - if (dragged.id == "south_west") { - val southWest = GeoPoint.from(dragged.position) - val northEast = groundImageState.bounds.northEast - bounds = GeoRectBounds(southWest, northEast) - } else { - val southWest = groundImageState.bounds.southWest - val northEast = GeoPoint.from(dragged.position) - bounds = GeoRectBounds(southWest, northEast) + bounds = GeoRectBounds().also { + it.extend(southWest.position) + it.extend(northEast.position) } } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt b/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt index 4be64fab..2acf3d87 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt @@ -11,38 +11,68 @@ data class GeoRectBounds( get() = southWest == null || northEast == null fun extend(point: IGeoPoint) { - val target = GeoPoint.from(point) - - if (isEmpty) { - southWest = target - northEast = target - return - } - - val sw = southWest!! - val ne = northEast!! - - val south = min(sw.latitude, target.latitude) - val north = max(ne.latitude, target.latitude) - - var west = sw.longitude - var east = ne.longitude + val position = GeoPoint.from(point) + + when { + // まだ何もない:両方に同じ点を入れて初期化 + southWest == null && northEast == null -> { + southWest = position + northEast = position + return + } - val lon = target.longitude + // southWest だけある:既存点と position の2点から SW/NE を決める + southWest != null && northEast == null -> { + val sw = southWest!! + val south = minOf(sw.latitude, position.latitude) + val north = maxOf(sw.latitude, position.latitude) + // 1点ずつなので子午線跨ぎの判定は不要。単純に min/max でOK + val west = minOf(sw.longitude, position.longitude) + val east = maxOf(sw.longitude, position.longitude) + + southWest = GeoPoint(south, west) + northEast = GeoPoint(north, east) + return + } - if (!containsLongitude(lon, west, east)) { - val distToEast = distanceEast(lon, east) - val distToWest = distanceWest(west, lon) + // northEast だけある:既存点と position の2点から SW/NE を決める + southWest == null && northEast != null -> { + val ne = northEast!! + val south = minOf(ne.latitude, position.latitude) + val north = maxOf(ne.latitude, position.latitude) + val west = minOf(ne.longitude, position.longitude) + val east = maxOf(ne.longitude, position.longitude) + + southWest = GeoPoint(south, west) + northEast = GeoPoint(north, east) + return + } - if (distToEast < distToWest) { - east = lon - } else { - west = lon + else -> { + // どちらもある:従来ロジック(子午線跨ぎ考慮)+ 緯度/経度の参照を修正 + val south = minOf(position.latitude, southWest!!.latitude) + val north = maxOf(position.latitude, northEast!!.latitude) + + var west = southWest!!.longitude + var east = northEast!!.longitude + + if (west > 0 && east < 0) { + // すでに経度が + と - に分かれている=日付変更線跨ぎの矩形 + if (position.longitude > 0) { + west = minOf(position.longitude, west) + } else { + east = maxOf(position.longitude, east) + } + } else { + // 通常ケース:単純に min/max + west = minOf(position.longitude, southWest!!.longitude) + east = maxOf(position.longitude, northEast!!.longitude) + } + + southWest = GeoPoint(south, west) + northEast = GeoPoint(north, east) } } - - southWest = GeoPoint(south, west) - northEast = GeoPoint(north, east) } private fun distanceEast( From 82daee10644168ec48d88eece7498ba769a5ee14 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Mon, 18 Aug 2025 03:27:42 +0900 Subject: [PATCH 14/29] Update: Marker labels based on the relative positions --- .../groundimage/GroundImageMapComponent.kt | 12 +- .../pages/groundimage/GroundImageMapPage.kt | 4 - .../GroundImageMapPageViewModel.kt | 119 ++++++++++++------ .../core/features/GeoRectBounds.kt | 11 +- 4 files changed, 90 insertions(+), 56 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt index 42be06dc..d85e6182 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt @@ -7,7 +7,6 @@ import com.mapconductor.core.groundimage.GroundImage import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapViewState import com.mapconductor.core.marker.Marker -import com.mapconductor.core.marker.MarkerState import com.mapconductor.core.marker.OnMarkerEventHandler import com.mapconductor.example.MapViewContainer @@ -15,8 +14,6 @@ import com.mapconductor.example.MapViewContainer fun GroundImageMapComponent( mapViewState: MapViewState<*>?, viewModel: GroundImageMapPageViewModel, - southWestMarker: MarkerState, - northEastMarker: MarkerState, modifier: Modifier = Modifier, onGroundImageClick: OnGroundImageEventHandler = {}, onMarkerDrag: OnMarkerEventHandler = {}, @@ -34,11 +31,10 @@ fun GroundImageMapComponent( } // BoundsMarkers - key(southWestMarker.id) { - Marker(southWestMarker) - } - key(northEastMarker.id) { - Marker(northEastMarker) + viewModel.markers.forEach { marker -> + key(marker.fingerPrint()) { + Marker(marker) + } } } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt index c3c744b5..a6fce2c2 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPage.kt @@ -46,12 +46,8 @@ fun GroundImageMapPage( onMapViewStateChanged = viewModel::onMapViewChanged, ) { paddingValues -> val mapViewState = viewModel.mapViewState.collectAsState() - val southWestMarker = viewModel.southWest - val northEastMarker = viewModel.northEast GroundImageMapComponent( - southWestMarker = southWestMarker, - northEastMarker = northEastMarker, mapViewState = mapViewState.value, viewModel = viewModel, onGroundImageClick = viewModel::onGroundImageClick, diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt index aaa47173..0be2a546 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt @@ -15,6 +15,7 @@ import com.mapconductor.core.marker.DefaultIcon import com.mapconductor.core.marker.MarkerState import com.mapconductor.example.toast.ToastMessage import android.graphics.drawable.Drawable +import android.util.Log import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -24,8 +25,7 @@ interface GroundImageMapPageViewModel { val mapViewState: StateFlow?> val messages: StateFlow> - val southWest: MarkerState - val northEast: MarkerState + val markers: List val imageResources: GroundImageResources val image: Drawable var opacity: Float @@ -69,39 +69,76 @@ class GroundImageMapPageViewModelImpl( private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) override val messages: StateFlow> = _messages.asStateFlow() - override val southWest: MarkerState = - MarkerState( - id = "south_west", - position = - GeoPoint( - latitude = 40.712216, - longitude = -74.22655, - ), - icon = - DefaultIcon( - fillColor = Color.Blue, - strokeColor = Color.White, - label = "SW", - ), - draggable = true, - ) + private var _southWestPosition by mutableStateOf( + GeoPoint( + latitude = 40.712216, + longitude = -74.22655, + ), + ) - override val northEast: MarkerState = - MarkerState( - id = "north_east", - position = - GeoPoint( - latitude = 40.773941, - longitude = -74.12544, + private var _northEastPosition by mutableStateOf( + GeoPoint( + latitude = 40.773941, + longitude = -74.12544, + ), + ) + + private fun calculateMarkerLabels(): Pair { + val swLat = _southWestPosition.latitude + val swLng = _southWestPosition.longitude + val neLat = _northEastPosition.latitude + val neLng = _northEastPosition.longitude + + val southWestLabel = + when { + swLat <= neLat && swLng <= neLng -> "SW" // Normal + swLat <= neLat && swLng > neLng -> "SE" // East-West flipped + swLat > neLat && swLng <= neLng -> "NW" // North-South flipped + else -> "NE" // Both flipped + } + + val northEastLabel = + when { + neLat >= swLat && neLng >= swLng -> "NE" // Normal + neLat >= swLat && neLng < swLng -> "NW" // East-West flipped + neLat < swLat && neLng >= swLng -> "SE" // North-South flipped + else -> "SW" // Both flipped + } + + return Pair(southWestLabel, northEastLabel) + } + + override val markers: List + get() { + val (swLabel, neLabel) = calculateMarkerLabels() + Log.d("debug", "$swLabel, $neLabel") + return listOf( + MarkerState( + id = "south_west", + position = _southWestPosition, + icon = + DefaultIcon( + fillColor = Color.Blue, + strokeColor = Color.White, + label = swLabel, + labelTextColor = Color.White, + ), + draggable = true, ), - icon = - DefaultIcon( - fillColor = Color.Red, - strokeColor = Color.White, - label = "NE", + MarkerState( + id = "north_east", + position = _northEastPosition, + icon = + DefaultIcon( + fillColor = Color.Red, + strokeColor = Color.White, + label = neLabel, + labelTextColor = Color.White, + ), + draggable = true, ), - draggable = true, - ) + ) + } override var opacity by mutableStateOf(0.5f) @@ -109,8 +146,8 @@ class GroundImageMapPageViewModelImpl( private var bounds by mutableStateOf( GeoRectBounds( - southWest = GeoPoint.from(southWest.position), - northEast = GeoPoint.from(northEast.position), + southWest = GeoPoint.from(_southWestPosition), + northEast = GeoPoint.from(_northEastPosition), ), ) @@ -134,10 +171,18 @@ class GroundImageMapPageViewModelImpl( } override fun onMarkerDrag(dragged: MarkerState) { - bounds = GeoRectBounds().also { - it.extend(southWest.position) - it.extend(northEast.position) + // Update the internal position based on which marker was dragged + when (dragged.id) { + "south_west" -> _southWestPosition = GeoPoint.from(dragged.position) + "north_east" -> _northEastPosition = GeoPoint.from(dragged.position) } + + // Update bounds using the new positions + bounds = + GeoRectBounds().also { + it.extend(markers[0].position) + it.extend(markers[1].position) + } } override fun showToast(text: String) { diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt b/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt index 2acf3d87..74b61ca6 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt @@ -1,8 +1,5 @@ package com.mapconductor.core.features -import kotlin.math.max -import kotlin.math.min - data class GeoRectBounds( var southWest: GeoPoint? = null, var northEast: GeoPoint? = null, @@ -27,8 +24,8 @@ data class GeoRectBounds( val south = minOf(sw.latitude, position.latitude) val north = maxOf(sw.latitude, position.latitude) // 1点ずつなので子午線跨ぎの判定は不要。単純に min/max でOK - val west = minOf(sw.longitude, position.longitude) - val east = maxOf(sw.longitude, position.longitude) + val west = minOf(sw.longitude, position.longitude) + val east = maxOf(sw.longitude, position.longitude) southWest = GeoPoint(south, west) northEast = GeoPoint(north, east) @@ -40,8 +37,8 @@ data class GeoRectBounds( val ne = northEast!! val south = minOf(ne.latitude, position.latitude) val north = maxOf(ne.latitude, position.latitude) - val west = minOf(ne.longitude, position.longitude) - val east = maxOf(ne.longitude, position.longitude) + val west = minOf(ne.longitude, position.longitude) + val east = maxOf(ne.longitude, position.longitude) southWest = GeoPoint(south, west) northEast = GeoPoint(north, east) From 98f03aec14aa12bcebf5cf1e3d0a2abcb5f9fb26 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Thu, 21 Aug 2025 01:47:51 +0900 Subject: [PATCH 15/29] Prepared initial implementation setup of MapDesignType for Google Maps API. --- .../com/mapconductor/example/DemoAppScreen.kt | 13 +++ ...ewModel.kt => GroundImagePageViewModel.kt} | 0 .../pages/mapDesign/MapDesignMapComponent.kt | 22 +++++ .../pages/mapDesign/MapDesignMapPage.kt | 92 +++++++++++++++++++ .../pages/mapDesign/MapDesignPageViewModel.kt | 74 +++++++++++++++ .../com/mapconductor/core/map/MapViewState.kt | 8 +- .../mapconductor/arcgis/ArcGisMapViewState.kt | 13 ++- .../googlemaps/GoogleMapViewController.kt | 12 +++ .../googlemaps/GoogleMapViewState.kt | 16 +++- .../java/com/mapconductor/here/HereMapView.kt | 4 +- .../com/mapconductor/here/HereMapViewState.kt | 10 +- .../mapconductor/mapbox/MapboxViewState.kt | 10 +- 12 files changed, 255 insertions(+), 19 deletions(-) rename example-app/src/main/java/com/mapconductor/example/pages/groundimage/{GroundImageMapPageViewModel.kt => GroundImagePageViewModel.kt} (100%) create mode 100644 example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt create mode 100644 example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt create mode 100644 example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt diff --git a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt index 75a445bd..cac5e82c 100644 --- a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt +++ b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt @@ -3,6 +3,7 @@ package com.mapconductor.example import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Build import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Home @@ -22,6 +23,7 @@ import com.mapconductor.example.pages.groundimage.GroundImageMapPage import com.mapconductor.example.pages.groundimage.GroundImageResources import com.mapconductor.example.pages.map.flyto.FlyToMapIcons import com.mapconductor.example.pages.map.flyto.FlyToMapPage +import com.mapconductor.example.pages.mapDesign.MapDesignMapPage import com.mapconductor.example.pages.polyline.PolylineMapPage import com.mapconductor.example.pages.stores.StoreMapPage import com.mapconductor.example.ui.sidebar.Sidebar @@ -85,6 +87,12 @@ fun DemoAppScreen() { icon = Icons.Default.PlayArrow, route = "polyline", ), + SidebarItem( + id = "mapDesign", + title = "MapDesign Demo", + icon = Icons.Default.Build, + route = "mapDesign", + ), // SidebarItem( // id = "examples", // title = "Map Examples", @@ -131,6 +139,11 @@ fun DemoAppScreen() { onToggleSidebar = navigationViewModel::toggleSidebar, ) } + "mapDesign" -> { + MapDesignMapPage( + onToggleSidebar = navigationViewModel::toggleSidebar, + ) + } "settings" -> { // Placeholder for settings page Box( diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImagePageViewModel.kt similarity index 100% rename from example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapPageViewModel.kt rename to example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImagePageViewModel.kt diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt new file mode 100644 index 00000000..a2b052da --- /dev/null +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt @@ -0,0 +1,22 @@ +package com.mapconductor.example.pages.mapDesign + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.mapconductor.core.map.MapViewState +import com.mapconductor.core.map.OnMapEventHandler +import com.mapconductor.example.MapViewContainer + +@Composable +fun MapDesignMapComponent( + mapViewState: MapViewState<*>?, + modifier: Modifier = Modifier, + onMapClick: OnMapEventHandler = {}, +) { + mapViewState?.let { state -> + MapViewContainer( + modifier = modifier, + state = state, + onMapClick = onMapClick, + ) {} + } +} diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt new file mode 100644 index 00000000..96d1d943 --- /dev/null +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt @@ -0,0 +1,92 @@ +package com.mapconductor.example.pages.mapDesign + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import com.mapconductor.example.ui.DemoMapPageScaffold + +@Composable +fun MapDesignMapPage( + viewModel: MapDesignPageViewModel = MapDesignPageViewModelImpl(), + onToggleSidebar: () -> Unit = {}, +) { + DemoMapPageScaffold( + initCameraPosition = viewModel.initCameraPosition, + onToggleSidebar = onToggleSidebar, + onMapViewStateChanged = viewModel::onMapViewChanged, + ) { paddingValues -> + val mapViewState = viewModel.mapViewState.collectAsState() + + MapDesignMapComponent( + mapViewState = mapViewState.value, + onMapClick = viewModel::onMapClick, + ) + +// // Control Panel +// MessageCard( +// modifier = +// Modifier +// .align(Alignment.BottomStart) +// .padding( +// bottom = paddingValues.calculateBottomPadding() + 16.dp, +// start = paddingValues.calculateStartPadding(LayoutDirection.Ltr) + 16.dp, +// end = paddingValues.calculateEndPadding(LayoutDirection.Ltr) + 16.dp, +// ), +// title = "Select Map Design...", +// maxHeight = 400.dp, +// ) { +// Column( +// modifier = Modifier.fillMaxSize(), +// verticalArrangement = Arrangement.spacedBy(8.dp), +// ) { +// Row( +// modifier = Modifier.fillMaxWidth(), +// horizontalArrangement = Arrangement.spacedBy(8.dp), +// ) { +// Button( +// modifier = Modifier.weight(1f), +// onClick = { viewModel.flyToSydney() }, +// ) { +// Text("Normal") +// } +// +// Button( +// modifier = Modifier.weight(1f), +// onClick = { viewModel.flyToSydney() }, +// ) { +// Text("Satellite") +// } +// } +// Row( +// modifier = Modifier.fillMaxWidth(), +// horizontalArrangement = Arrangement.spacedBy(8.dp), +// ) { +// Button( +// modifier = Modifier.weight(1f), +// onClick = { viewModel.flyToHonolulu() }, +// ) { +// Text("Hybrid") +// } +// +// Button( +// modifier = Modifier.weight(1f), +// onClick = { viewModel.flyToTokyo() }, +// ) { +// Text("Terrain") +// } +// } +// +// Row( +// modifier = Modifier.fillMaxWidth(), +// horizontalArrangement = Arrangement.spacedBy(8.dp), +// ) { +// Button( +// modifier = Modifier.weight(1f), +// onClick = { viewModel.flyToLondon() }, +// ) { +// Text("None") +// } +// } +// } +// } + } +} diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt new file mode 100644 index 00000000..8cb1f738 --- /dev/null +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt @@ -0,0 +1,74 @@ +package com.mapconductor.example.pages.mapDesign + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.mapconductor.core.features.GeoPoint +import com.mapconductor.core.map.MapCameraPosition +import com.mapconductor.core.map.MapViewState +import com.mapconductor.core.marker.MarkerState +import com.mapconductor.core.polyline.PolylineState +import com.mapconductor.example.toast.ToastMessage +import com.mapconductor.googlemaps.GoogleMapDesign +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +interface MapDesignPageViewModel { + val initCameraPosition: MapCameraPosition + val mapViewState: StateFlow?> + val messages: StateFlow> + + var design: Int + fun onMapViewChanged(state: MapViewState<*>) + + fun onMapClick(clicked: GeoPoint) + + fun showToast(text: String) + + fun removeToast(toastMessage: ToastMessage) +} + +class MapDesignPageViewModelImpl(): + ViewModel(), + MapDesignPageViewModel { + + override val initCameraPosition = + MapCameraPosition( + position = + GeoPoint.fromLatLong( + latitude = 21.382314, + longitude = -157.933097, + ), + zoom = 12.0, + bearing = 0.0, + tilt = 0.0, + paddings = null, + ) + + private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) + override val messages: StateFlow> = _messages.asStateFlow() + + private val _mapViewState = MutableStateFlow?>(null) + override val mapViewState: StateFlow?> = _mapViewState.asStateFlow() + + override var design by mutableStateOf(1) + + override fun onMapViewChanged(state: MapViewState<*>) { +// state.mapDesignType = GoogleMapDesign.Satellite + this._mapViewState.value = state + } + + override fun onMapClick(clicked: GeoPoint) { + showToast("Map clicked at: ${clicked.toUrlValue()}") + } + + override fun showToast(text: String) { + this._messages.value = this._messages.value + ToastMessage(text = text) + } + + override fun removeToast(toastMessage: ToastMessage) { + this._messages.value = this._messages.value.filter { it != toastMessage } + } +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt index 0bcf093b..e22ccd79 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt @@ -17,7 +17,7 @@ enum class InitState { Failed, } -interface MapViewState { +interface MapViewState { interface MoveCameraCallback { fun onComplete(result: Boolean) } @@ -26,12 +26,16 @@ interface MapViewState { val initCameraPosition: MapCameraPosition val isInitialized: StateFlow val cameraPosition: StateFlow - val mapDesignType: MapDesignType + var mapDesignType: ActualMapDesignType fun initAsync(init: suspend () -> Boolean) fun resetInitState() + fun changeMapDesignType( + value: ActualMapDesignType + ) + fun moveCameraTo( cameraPosition: MapCameraPosition, durationMs: Long = 0, diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt index 92633b09..0beca032 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt @@ -9,6 +9,7 @@ import com.mapconductor.core.map.BaseMapViewSaver import com.mapconductor.core.map.IMapCameraPosition import com.mapconductor.core.map.InitState import com.mapconductor.core.map.MapCameraPosition +import com.mapconductor.core.map.MapDesignType import com.mapconductor.core.map.MapPaddings import com.mapconductor.core.map.MapPaddingsImpl import com.mapconductor.core.map.MapViewState @@ -22,14 +23,12 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -interface IArcGISMapViewState : MapViewState - class ArcGISMapViewState( override val id: String, override val initCameraPosition: MapCameraPosition, - override val mapDesignType: ArcGISDesign, -) : MapViewStateImpl(), - IArcGISMapViewState { + override var mapDesignType: ArcGISDesign, +) : MapViewStateImpl() + { // Map padding private val _padding = MutableStateFlow(MapPaddingsImpl.Zeros) val padding: StateFlow = _padding.asStateFlow() @@ -40,6 +39,10 @@ class ArcGISMapViewState( private val _cameraPosition = MutableStateFlow(initCameraPosition) override val cameraPosition: StateFlow = _cameraPosition.asStateFlow() + override fun changeMapDesignType(value: ArcGISDesign) { + TODO("Not yet implemented") + } + override fun moveCameraTo( cameraPosition: MapCameraPosition, durationMs: Long, diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt index 24be6362..53a75239 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt @@ -57,6 +57,10 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch interface IGoogleMapViewController : MapViewController { + fun changeMapDesign( + value: Int + ) + fun moveCamera( dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback? = null, @@ -189,6 +193,14 @@ class GoogleMapViewController( holder.map.setOnMarkerDragListener(this) } + override fun changeMapDesign( + value: Int + ){ + coroutine.launch { + holder.map.mapType = value + } + } + override fun moveCamera( position: MapCameraPosition, listener: MapViewState.MoveCameraCallback?, diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewState.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewState.kt index 5b94238c..ebd9fb1b 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewState.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewState.kt @@ -12,19 +12,20 @@ import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapPaddingsImpl import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.MapViewStateImpl +import com.mapconductor.core.state.StateOrValue import java.util.UUID import android.os.Bundle import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -interface IGoogleMapViewState : MapViewState +interface IGoogleMapViewState : MapViewState class GoogleMapViewState( override val id: String, - override val mapDesignType: GoogleMapDesignType, + override var mapDesignType: GoogleMapDesign, override val initCameraPosition: MapCameraPosition, -) : MapViewStateImpl(), +) : MapViewStateImpl(), IGoogleMapViewState { // Map padding private val _padding = MutableStateFlow(MapPaddingsImpl.Zeros) @@ -36,6 +37,13 @@ class GoogleMapViewState( internal var controller: IGoogleMapViewController? = null + override fun changeMapDesignType( + value: GoogleMapDesign, + ){ + this.mapDesignType = value + this.controller?.changeMapDesign(value.getValue()) + } + override fun moveCameraTo( position: GeoPoint, durationMs: Long, @@ -130,7 +138,7 @@ class GoogleMapViewSaver : BaseMapViewSaver() { @Composable fun rememberGoogleMapViewState( - mapDesign: GoogleMapDesignType = GoogleMapDesign.Normal, + mapDesign: GoogleMapDesign = GoogleMapDesign.Normal, cameraPosition: IMapCameraPosition = MapCameraPosition.Default, ): GoogleMapViewState { val stateId by rememberSaveable { diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt index 9ca73d3e..7cdf8e8d 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt @@ -58,7 +58,7 @@ fun HereMapView( val mapInitOptions = HereMapViewInitOptions( - scheme = state.mapDesignType.id, + scheme = state.mapDesignType.getValue(), ) val controller = @@ -86,7 +86,7 @@ fun HereMapView( (state as? HereMapViewState)?.controller = controller - controller.holder.mapView.mapScene.loadScene(state.mapDesignType.id) { mapError -> + controller.holder.mapView.mapScene.loadScene(state.mapDesignType.getValue()) { mapError -> if (mapError != null) { throw Throwable("Loading map failed: mapError: " + mapError.name) } diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewState.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewState.kt index 1600d14e..dcf72897 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewState.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewState.kt @@ -24,13 +24,13 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -interface IHereMapViewState : MapViewState +interface IHereMapViewState : MapViewState class HereMapViewState( override val id: String, - override val mapDesignType: HereMapDesignType, + override var mapDesignType: HereMapDesignType, override val initCameraPosition: MapCameraPosition = MapCameraPosition.Default, -) : MapViewStateImpl(), +) : MapViewStateImpl(), IHereMapViewState { internal var controller: IHereMapViewController? = null @@ -38,6 +38,10 @@ class HereMapViewState( private val _cameraPosition = MutableStateFlow(initCameraPosition) override val cameraPosition: StateFlow = _cameraPosition.asStateFlow() + override fun changeMapDesignType(value: HereMapDesignType) { + TODO("Not yet implemented") + } + override fun moveCameraTo( position: GeoPoint, durationMs: Long, diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt index 4a893721..b47a5fc8 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt @@ -18,13 +18,13 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -interface IMapboxMapViewState : MapViewState +interface IMapboxMapViewState : MapViewState class MapboxMapViewState( override val id: String, - override val mapDesignType: MapboxDesignType, + override var mapDesignType: MapboxDesignType, override val initCameraPosition: MapCameraPosition = MapCameraPosition.Default, -) : MapViewStateImpl(), +) : MapViewStateImpl(), IMapboxMapViewState { internal var controller: IMapboxMapViewController? = null @@ -32,6 +32,10 @@ class MapboxMapViewState( private val _cameraPosition = MutableStateFlow(initCameraPosition) override val cameraPosition: StateFlow = _cameraPosition.asStateFlow() + override fun changeMapDesignType(value: MapboxDesignType) { + TODO("Not yet implemented") + } + override fun moveCameraTo( position: GeoPoint, durationMs: Long, From 5a2bde3f3643f80592ddefcb8bd18207d0617cdc Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Thu, 21 Aug 2025 01:56:03 +0900 Subject: [PATCH 16/29] Prepared initial implementation setup of MapDesignType for Google Maps API. --- .../com/mapconductor/example/DemoAppScreen.kt | 2 +- .../pages/circle/CircleMapComponent.kt | 4 +- .../groundimage/GroundImageMapComponent.kt | 9 +- .../groundimage/GroundImagePageViewModel.kt | 26 ++-- .../com/mapconductor/core/OverlayProvider.kt | 16 +- .../mapconductor/core/circle/CircleCompose.kt | 8 +- .../core/controller/MapViewController.kt | 83 ++++++---- .../core/groundimage/GroundImageController.kt | 141 +++++++++++++++++ .../core/groundimage/GroundImageOverlay.kt | 16 ++ .../groundimage/GroundImageOverlayManager.kt | 146 ------------------ .../core/groundimage/GroundImageRenderer.kt | 38 ----- .../com/mapconductor/core/map/MapViewBase.kt | 13 +- .../mapconductor/core/marker/MarkerCompose.kt | 8 +- .../core/polyline/PolylineComponent.kt | 8 +- .../arcgis/ArcGISMapViewController.kt | 29 ---- .../mapconductor/arcgis/ArcGISTypeAlias.kt | 1 - .../groundimage/ArcGISGroundImageRenderer.kt | 25 --- .../googlemaps/GoogleMapTypeAlias.kt | 13 ++ .../GoogleMapViewControllerStore.kt | 13 ++ .../GoogleMapGroundImageRenderer.kt | 59 +++---- .../com/mapconductor/here/HereMapTypeAlias.kt | 2 - .../here/HereMapViewController.kt | 40 +---- .../groundimage/HereGroundImageRenderer.kt | 51 ------ .../mapbox/MapboxMapViewController.kt | 29 ---- .../mapconductor/mapbox/MapboxTypeAlias.kt | 1 - .../groundImage/MapboxGroundImageRenderer.kt | 22 --- 26 files changed, 294 insertions(+), 509 deletions(-) create mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageController.kt create mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlay.kt delete mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt delete mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageRenderer.kt delete mode 100644 mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundimage/ArcGISGroundImageRenderer.kt create mode 100644 mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapTypeAlias.kt delete mode 100644 mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt delete mode 100644 mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt diff --git a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt index cac5e82c..0eb14b27 100644 --- a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt +++ b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt @@ -77,7 +77,7 @@ fun DemoAppScreen() { ), SidebarItem( id = "groundImage", - title = "GroundImage Demo", + title = "GroundImage", icon = Icons.Default.Favorite, route = "groundImage", ), diff --git a/example-app/src/main/java/com/mapconductor/example/pages/circle/CircleMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/circle/CircleMapComponent.kt index 56cb301a..7d983ae6 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/circle/CircleMapComponent.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/circle/CircleMapComponent.kt @@ -31,9 +31,7 @@ fun CircleMapComponent( onMarkerDrag = onMarkerDrag, ) { // Circle - key(viewModel.circleState.id) { - Circle(viewModel.circleState) - } + Circle(viewModel.circleState) // Center marker (not draggable) key(viewModel.centerMarker.id) { diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt index d85e6182..7d1e2620 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImageMapComponent.kt @@ -1,7 +1,6 @@ package com.mapconductor.example.pages.groundimage import androidx.compose.runtime.Composable -import androidx.compose.runtime.key import androidx.compose.ui.Modifier import com.mapconductor.core.groundimage.GroundImage import com.mapconductor.core.groundimage.OnGroundImageEventHandler @@ -26,15 +25,11 @@ fun GroundImageMapComponent( onMarkerDrag = onMarkerDrag, ) { // GroundImage - key(viewModel.groundImageState.id) { - GroundImage(viewModel.groundImageState) - } + GroundImage(viewModel.groundImageState) // BoundsMarkers viewModel.markers.forEach { marker -> - key(marker.fingerPrint()) { - Marker(marker) - } + Marker(marker) } } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImagePageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImagePageViewModel.kt index 0be2a546..4047338c 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImagePageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/groundimage/GroundImagePageViewModel.kt @@ -15,7 +15,6 @@ import com.mapconductor.core.marker.DefaultIcon import com.mapconductor.core.marker.MarkerState import com.mapconductor.example.toast.ToastMessage import android.graphics.drawable.Drawable -import android.util.Log import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -69,14 +68,14 @@ class GroundImageMapPageViewModelImpl( private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) override val messages: StateFlow> = _messages.asStateFlow() - private var _southWestPosition by mutableStateOf( + private var southWestPosition by mutableStateOf( GeoPoint( latitude = 40.712216, longitude = -74.22655, ), ) - private var _northEastPosition by mutableStateOf( + private var northEastPosition by mutableStateOf( GeoPoint( latitude = 40.773941, longitude = -74.12544, @@ -84,10 +83,10 @@ class GroundImageMapPageViewModelImpl( ) private fun calculateMarkerLabels(): Pair { - val swLat = _southWestPosition.latitude - val swLng = _southWestPosition.longitude - val neLat = _northEastPosition.latitude - val neLng = _northEastPosition.longitude + val swLat = southWestPosition.latitude + val swLng = southWestPosition.longitude + val neLat = northEastPosition.latitude + val neLng = northEastPosition.longitude val southWestLabel = when { @@ -111,11 +110,10 @@ class GroundImageMapPageViewModelImpl( override val markers: List get() { val (swLabel, neLabel) = calculateMarkerLabels() - Log.d("debug", "$swLabel, $neLabel") return listOf( MarkerState( id = "south_west", - position = _southWestPosition, + position = southWestPosition, icon = DefaultIcon( fillColor = Color.Blue, @@ -127,7 +125,7 @@ class GroundImageMapPageViewModelImpl( ), MarkerState( id = "north_east", - position = _northEastPosition, + position = northEastPosition, icon = DefaultIcon( fillColor = Color.Red, @@ -146,8 +144,8 @@ class GroundImageMapPageViewModelImpl( private var bounds by mutableStateOf( GeoRectBounds( - southWest = GeoPoint.from(_southWestPosition), - northEast = GeoPoint.from(_northEastPosition), + southWest = GeoPoint.from(southWestPosition), + northEast = GeoPoint.from(northEastPosition), ), ) @@ -173,8 +171,8 @@ class GroundImageMapPageViewModelImpl( override fun onMarkerDrag(dragged: MarkerState) { // Update the internal position based on which marker was dragged when (dragged.id) { - "south_west" -> _southWestPosition = GeoPoint.from(dragged.position) - "north_east" -> _northEastPosition = GeoPoint.from(dragged.position) + "south_west" -> southWestPosition = GeoPoint.from(dragged.position) + "north_east" -> northEastPosition = GeoPoint.from(dragged.position) } // Update bounds using the new positions diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt b/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt index 917b804b..dc351e37 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.compositionLocalOf import com.mapconductor.core.circle.CircleState import com.mapconductor.core.controller.MapViewControllerAlias +import com.mapconductor.core.groundimage.GroundImageOverlay import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.info.InfoBubbleEntry import com.mapconductor.core.map.MapOverlay @@ -116,17 +117,6 @@ class PolylineOverlay( } } -class GroundImageOverlay( - override val flow: StateFlow>, -) : MapOverlay { - override suspend fun render( - data: List, - controller: MapViewControllerAlias, - ) { - controller.addGroundImages(data) - } -} - val LocalPolylineCollector = compositionLocalOf>> { error("Polyline must be under the ") @@ -170,7 +160,9 @@ fun CollectAndRenderOverlays( LaunchedEffect(Unit) { typedOverlay.flow.collect { - typedOverlay.render(it, controller) + if (it.isNotEmpty()) { + typedOverlay.render(it, controller) + } } } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/circle/CircleCompose.kt b/mapconductor-core/src/main/java/com/mapconductor/core/circle/CircleCompose.kt index 30ed1ce4..ec5c710d 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/circle/CircleCompose.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/circle/CircleCompose.kt @@ -1,8 +1,7 @@ package com.mapconductor.core.circle import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.remember +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -12,9 +11,8 @@ import android.os.Parcelable @Composable fun MapViewScope.Circle(state: CircleState) { - val rememberState = remember(state.fingerPrint()) { state } - SideEffect { - circleFlow.value = circleFlow.value.filter { it.id != rememberState.id } + rememberState + LaunchedEffect(state.fingerPrint()) { + circleFlow.value = circleFlow.value.filter { it.id != state.id } + state } } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt b/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt index 06c5f73a..6fccbc27 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/controller/MapViewController.kt @@ -4,11 +4,8 @@ import com.mapconductor.core.circle.CircleOverlayManager import com.mapconductor.core.circle.CircleRenderer import com.mapconductor.core.circle.CircleState import com.mapconductor.core.circle.OnCircleEventHandler +import com.mapconductor.core.features.IGeoPoint import com.mapconductor.core.geocell.HexGeocell -import com.mapconductor.core.groundimage.GroundImageOverlayManager -import com.mapconductor.core.groundimage.GroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageState -import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapViewHolder import com.mapconductor.core.map.OnCameraMoveHandler import com.mapconductor.core.map.OnMapEventHandler @@ -24,7 +21,7 @@ import com.mapconductor.core.polyline.PolylineRenderer import com.mapconductor.core.polyline.PolylineState import kotlinx.coroutines.CoroutineScope -interface MapViewController { +interface MapViewController { val holder: MapViewHolder<*, *> val coroutine: CoroutineScope val markerOverlayManager: MarkerOverlayManager @@ -32,7 +29,6 @@ interface MapViewController val circleOverlayManager: CircleOverlayManager val polygonOverlayManager: PolygonOverlayManager - val groundImageOverlayManager: GroundImageOverlayManager suspend fun addMarkers(data: List) @@ -42,10 +38,6 @@ interface MapViewController) - - suspend fun updateGroundImage(state: GroundImageState) - suspend fun addCircles(data: List) suspend fun updateCircle(state: CircleState) @@ -73,13 +65,57 @@ interface MapViewController + +interface OverlayRenderer { + interface Changes { + val current: EntityType + val prev: EntityType + } + + suspend fun onAdd(data: List): List + + suspend fun onChange(data: List>): List + + suspend fun onRemove(data: List) + + suspend fun onPostProcess() +} + +interface OverlayController { + val zIndex: Int + + // val overlayManager: OverlayManager + val renderer: OverlayRenderer + + suspend fun add(data: List) + + suspend fun update(state: StateType) + + suspend fun clear() - fun setOnGroundImageClickListener(listener: OnGroundImageEventHandler?) + var clickListener: ((EventType) -> Unit)? + + fun find(position: IGeoPoint): EntityType? +} + +interface OverlayManager { + suspend fun add(states: List) + + suspend fun update(state: StateType) + + suspend fun clear() + + fun getById(id: String): StateType? + + fun allEntities(): List + + fun find(position: IGeoPoint): EntityType? } -typealias MapViewControllerAlias = MapViewController<*, *, *, *, *> -abstract class BaseMapViewController : - MapViewController { +abstract class BaseMapViewController : + MapViewController { abstract val markerRenderer: MarkerRenderer override val markerOverlayManager: MarkerOverlayManager by lazy { @@ -106,14 +142,6 @@ abstract class BaseMapViewController - - override val groundImageOverlayManager: GroundImageOverlayManager by lazy { - createGroundImageOverlayManager().also { overlayManager -> - groundImageRenderer.init(overlayManager) - onGroundImageOverlayManagerInitialized(overlayManager) - } - } abstract val circleRenderer: CircleRenderer @@ -132,10 +160,6 @@ abstract class BaseMapViewController) - protected abstract fun onGroundImageOverlayManagerInitialized( - overlayManager: GroundImageOverlayManager, - ) - protected abstract fun createMarkerOverlayManager(): MarkerOverlayManager protected abstract fun createPolylineOverlayManager(): PolylineOverlayManager @@ -144,8 +168,6 @@ abstract class BaseMapViewController - protected abstract fun createGroundImageOverlayManager(): GroundImageOverlayManager - protected var cameraMoveCallback: OnCameraMoveHandler? = null protected var mapClickCallback: OnMapEventHandler? = null protected var mapLongClickCallback: OnMapEventHandler? = null @@ -155,7 +177,6 @@ abstract class BaseMapViewController { + suspend fun compositionGroundImages(data: List) + + suspend fun updateGroundImage(state: GroundImageState) +} + +class GroundImageController( + override val renderer: OverlayRenderer>, + override var clickListener: OnGroundImageEventHandler? = null, +) : OverlayController< + ActualGroundImage, + GroundImageState, + GroundImageEntity, + GroundImageEvent, + > { + override val zIndex: Int = 2 + val entities = mutableMapOf>() + val semaphore = Semaphore(1) + + override suspend fun add(data: List) { + semaphore.withPermit { + val previous = entities.keys.toMutableSet() + val added = mutableListOf() + val updated = mutableListOf>>() + val removed = mutableListOf>() + + data.forEach { + if (previous.contains(it.id)) { + val prevEntity = entities[it.id]!! + updated.add( + object : OverlayRenderer.Changes> { + override val current = + GroundImageEntityImpl( + groundImage = prevEntity.groundImage, + state = it, + ) + override val prev = prevEntity + }, + ) + previous.remove(it.id) + } else { + added.add(it) + previous.remove(it.id) + } + } + + previous.forEach { remainId -> + entities.remove(remainId)?.let { removedEntity -> + removed.add(removedEntity) + } + } + + if (added.isNotEmpty()) { + val actualOverlays = renderer.onAdd(added) + actualOverlays.forEachIndexed { index, actualOverlay -> + actualOverlay?.let { + val state = added[index] + val entity = + GroundImageEntityImpl( + groundImage = it, + state = state, + ) + entities[state.id] = entity + } + } + } + + if (updated.isNotEmpty()) { + val actualOverlays = renderer.onChange(updated.toList()) + actualOverlays.forEachIndexed { index, actualOverlay -> + actualOverlay?.let { + val state = updated[index].current.state + val entity = + GroundImageEntityImpl( + groundImage = it, + state = state, + ) + entities[state.id] = entity + } + } + } + + if (removed.isNotEmpty()) { + renderer.onRemove(removed) + } + + renderer.onPostProcess() + } + } + + override suspend fun update(state: GroundImageState) { + semaphore.withPermit { + val updated = mutableListOf>>() + val prevEntity = entities[state.id]!! + updated.add( + object : OverlayRenderer.Changes> { + override val current: GroundImageEntity = + GroundImageEntityImpl( + groundImage = prevEntity.groundImage, + state = state, + ) + override val prev: GroundImageEntity = prevEntity + }, + ) + + val actualOverlays: List = renderer.onChange(updated) + actualOverlays.forEachIndexed { index, actualOverlay -> + actualOverlay?.let { + val entity = + GroundImageEntityImpl( + groundImage = it, + state = state, + ) + entities[state.id] = entity + } + } + } + } + + override suspend fun clear() { + semaphore.withPermit { + renderer.onRemove(entities.values.toList()) + entities.clear() + } + } + + override fun find(position: IGeoPoint): GroundImageEntity? { + // TODO: Improve this implementation later + return entities.values.find { entity -> + entity.state.bounds.contains(position) + } + } +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlay.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlay.kt new file mode 100644 index 00000000..5ed59d24 --- /dev/null +++ b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlay.kt @@ -0,0 +1,16 @@ +package com.mapconductor.core.groundimage + +import com.mapconductor.core.controller.MapViewControllerAlias +import com.mapconductor.core.map.MapOverlay +import kotlinx.coroutines.flow.StateFlow + +class GroundImageOverlay( + override val flow: StateFlow>, +) : MapOverlay { + override suspend fun render( + data: List, + controller: MapViewControllerAlias, + ) { + (controller as? GroundImageCapable<*>)?.compositionGroundImages(data) + } +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt deleted file mode 100644 index 2704a9d5..00000000 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageOverlayManager.kt +++ /dev/null @@ -1,146 +0,0 @@ -package com.mapconductor.core.groundimage - -import com.mapconductor.core.features.IGeoPoint -import kotlinx.coroutines.sync.Semaphore - -interface GroundImageOverlayManager { - suspend fun addGroundImages(overlays: List) - - suspend fun updateGroundImage(overlay: GroundImageState) - - suspend fun clearGroundImages() - - fun getGroundImageState(id: String): GroundImageState? - - fun getAllEntities(): List> - - fun find(position: IGeoPoint): GroundImageEntity? -} - -class GroundImageOverlayManagerImpl( - val onAdd: suspend (List) -> List, - val onChange: suspend (List>) -> List, - val onRemove: suspend (List>) -> Unit, - val onPostProcess: (suspend () -> Unit)? = null, -) : GroundImageOverlayManager { - val groundImageEntities = mutableMapOf>() - val semaphore = Semaphore(1) - - override suspend fun addGroundImages(images: List) { - semaphore.acquire() - val previous = groundImageEntities.keys.toMutableSet() - val added = mutableListOf() - val updated = mutableListOf>() - val removed = mutableListOf>() - - images.forEach { - if (previous.contains(it.id)) { - val prevEntity = groundImageEntities[it.id]!! - updated.add( - object : GroundImageRenderer.UpdateParams { - override val entity = - GroundImageEntityImpl( - groundImage = prevEntity.groundImage, - state = it, - ) - override val prevEntity = prevEntity - }, - ) - previous.remove(it.id) - } else { - added.add(it) - previous.remove(it.id) - } - } - - previous.forEach { remainId -> - groundImageEntities.remove(remainId)?.let { removedEntity -> - removed.add(removedEntity) - } - } - - if (added.isNotEmpty()) { - val actualOverlays = onAdd(added) - actualOverlays.forEachIndexed { index, actualOverlay -> - actualOverlay?.let { - val state = added[index] - val entity = - GroundImageEntityImpl( - groundImage = it, - state = state, - ) - groundImageEntities[state.id] = entity - } - } - } - - if (updated.isNotEmpty()) { - val actualOverlays = onChange(updated.toList()) - actualOverlays.forEachIndexed { index, actualOverlay -> - actualOverlay?.let { - val state = updated[index].entity.state - val entity = - GroundImageEntityImpl( - groundImage = it, - state = state, - ) - groundImageEntities[state.id] = entity - } - } - } - - if (removed.isNotEmpty()) { - onRemove(removed) - } - - onPostProcess?.invoke() - semaphore.release() - } - - override suspend fun updateGroundImage(state: GroundImageState) { - val updated = mutableListOf>() - - semaphore.acquire() - val prevEntity = groundImageEntities[state.id]!! - updated.add( - object : GroundImageRenderer.UpdateParams { - override val entity: GroundImageEntity = - GroundImageEntityImpl( - groundImage = prevEntity.groundImage, - state = state, - ) - override val prevEntity: GroundImageEntity = prevEntity - }, - ) - - val actualOverlays: List = onChange(updated) - actualOverlays.forEachIndexed { index, actualOverlay -> - actualOverlay?.let { - val entity = - GroundImageEntityImpl( - groundImage = it, - state = state, - ) - groundImageEntities[state.id] = entity - } - } - semaphore.release() - } - - override suspend fun clearGroundImages() { - semaphore.acquire() - val entities = groundImageEntities.values.toList() - onRemove(entities) - groundImageEntities.clear() - semaphore.release() - } - - override fun getGroundImageState(id: String): GroundImageState? = groundImageEntities[id]?.state - - override fun getAllEntities(): List> = groundImageEntities.values.toList() - - override fun find(position: IGeoPoint): GroundImageEntity? = - groundImageEntities.values.find { entity -> - entity.state.bounds.contains(position) - } -} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageRenderer.kt b/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageRenderer.kt deleted file mode 100644 index d123fe47..00000000 --- a/mapconductor-core/src/main/java/com/mapconductor/core/groundimage/GroundImageRenderer.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.mapconductor.core.groundimage - -import com.mapconductor.core.map.MapViewHolder -import kotlinx.coroutines.CoroutineScope - -interface GroundImageRendererFactory { - fun create( - onAdd: suspend (List) -> List, - onChange: suspend (List>) -> List, - onRemove: suspend (List>) -> Unit, - onPostProcess: (suspend () -> Unit)? = null, - ): GroundImageOverlayManager -} - -interface GroundImageRenderer { - interface UpdateParams { - val entity: GroundImageEntity - val prevEntity: GroundImageEntity - } - - fun init(groundImageOverlayManager: GroundImageOverlayManager) - - suspend fun addGroundImages(newGroundImages: List): List - - suspend fun removeGroundImages(removeEntities: List>) - - suspend fun changeGroundImages(changes: List>): List -} - -abstract class AbstractGroundImageRenderer : GroundImageRenderer { - protected lateinit var groundImageOverlayManager: GroundImageOverlayManager - abstract val holder: MapViewHolder<*, *> - abstract val coroutine: CoroutineScope - - override fun init(groundImageOverlayManager: GroundImageOverlayManager) { - this.groundImageOverlayManager = groundImageOverlayManager - } -} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt index 8251735b..ee89b4b4 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt @@ -30,6 +30,7 @@ import com.mapconductor.core.MapViewScope import com.mapconductor.core.ResourceProvider import com.mapconductor.core.controller.MapViewControllerAlias import com.mapconductor.core.features.GeoPoint +import com.mapconductor.core.groundimage.GroundImageCapable import com.mapconductor.core.info.InfoBubbleOverlay import com.mapconductor.core.info.LocalInfoBubbleCollector import com.mapconductor.core.marker.DefaultIcon @@ -117,11 +118,13 @@ fun < } } } - val groundImage = scope.groundImageFlow.collectAsState() - groundImage.value.forEach { groundImageState -> - LaunchedEffect(groundImageState.id) { - groundImageState.asFlow().debounce(Settings.Default.composeEventDebounce).collectLatest { - controller.updateGroundImage(groundImageState) + (controller as? GroundImageCapable<*>)?.let { + val groundImage = scope.groundImageFlow.collectAsState() + groundImage.value.forEach { groundImageState -> + LaunchedEffect(groundImageState.id) { + groundImageState.asFlow().debounce(Settings.Default.composeEventDebounce).collectLatest { + controller.updateGroundImage(groundImageState) + } } } } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt index 893dc601..ddc22005 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt @@ -1,17 +1,15 @@ package com.mapconductor.core.marker import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.remember +import androidx.compose.runtime.LaunchedEffect import com.mapconductor.core.MapViewScope import com.mapconductor.core.features.IGeoPoint import android.os.Parcelable @Composable fun MapViewScope.Marker(state: MarkerState) { - val rememberMarker = remember(state.id) { state } - SideEffect { - markerFlow.value = markerFlow.value.filter { it.id != rememberMarker.id } + rememberMarker + LaunchedEffect(state.fingerPrint()) { + markerFlow.value = markerFlow.value.filter { it.id != state.id } + state } } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/polyline/PolylineComponent.kt b/mapconductor-core/src/main/java/com/mapconductor/core/polyline/PolylineComponent.kt index 9fcecf80..d3e8bef5 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/polyline/PolylineComponent.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/polyline/PolylineComponent.kt @@ -1,8 +1,7 @@ package com.mapconductor.core.polyline import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.remember +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -12,9 +11,8 @@ import android.os.Parcelable @Composable fun MapViewScope.Polyline(state: PolylineState) { - val rememberState = remember(state.fingerPrint()) { state } - SideEffect { - polylineFlow.value = polylineFlow.value.filter { it.id != rememberState.id } + rememberState + LaunchedEffect(state.fingerPrint()) { + polylineFlow.value = polylineFlow.value.filter { it.id != state.id } + state } } diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt index da0a7ab3..ec34c923 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt @@ -10,7 +10,6 @@ import com.arcgismaps.mapping.view.UpEvent import com.arcgismaps.mapping.view.extensions.motionEvent import com.mapconductor.arcgis.circle.ArcGISCircleRenderer import com.mapconductor.arcgis.circle.DefaultArcGISCircleRenderer -import com.mapconductor.arcgis.groundimage.ArcGISGroundImageRenderer import com.mapconductor.arcgis.marker.ArcGISMarkerRenderer import com.mapconductor.arcgis.marker.DefaultArcGISMarkerRender import com.mapconductor.arcgis.polygon.ArcGISPolygonRenderer @@ -26,9 +25,6 @@ import com.mapconductor.core.circle.CircleState import com.mapconductor.core.controller.BaseMapViewController import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.geocell.HexGeocell -import com.mapconductor.core.groundimage.GroundImageOverlayManager -import com.mapconductor.core.groundimage.GroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewState import com.mapconductor.core.marker.MarkerOverlayManager @@ -55,7 +51,6 @@ interface IArcGISMapViewController : ArcGISActualCircle, ArcGISActualPolyline, ArcGISActualPolygon, - ArcGISActualGroundImage, > { fun moveCamera( dstPosition: MapCameraPosition, @@ -110,7 +105,6 @@ class ArcGISMapViewController( ArcGISActualCircle, ArcGISActualPolyline, ArcGISActualPolygon, - ArcGISActualGroundImage, >(), IArcGISMapViewController { override val markerRenderer: MarkerRenderer = @@ -166,10 +160,6 @@ class ArcGISMapViewController( onRemove = circleRenderer::removeCircles, ) - override fun createGroundImageOverlayManager(): GroundImageOverlayManager { - TODO("Not yet implemented") - } - override val circleRenderer: CircleRenderer = ArcGISCircleRenderer( circleLayer = circleLayer, @@ -177,17 +167,6 @@ class ArcGISMapViewController( coroutine = coroutine, ) - override fun onGroundImageOverlayManagerInitialized( - overlayManager: GroundImageOverlayManager, - ) { - } - - override val groundImageRenderer: GroundImageRenderer = - ArcGISGroundImageRenderer( - holder = holder, - coroutine = coroutine, - ) - override fun onCircleOverlayManagerInitialized(overlayManager: CircleOverlayManager) { } @@ -356,14 +335,6 @@ class ArcGISMapViewController( override suspend fun updatePolyline(state: PolylineState) = polylineOverlayManager.updatePolyline(state) - override suspend fun addGroundImages(data: List) { - TODO("Not yet implemented") - } - - override suspend fun updateGroundImage(state: GroundImageState) { - TODO("Not yet implemented") - } - override suspend fun addCircles(data: List) = circleOverlayManager.addCircles(data) override suspend fun updateCircle(state: CircleState) = circleOverlayManager.updateCircle(state) diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISTypeAlias.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISTypeAlias.kt index 4a216b4e..a6f35e5d 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISTypeAlias.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISTypeAlias.kt @@ -6,4 +6,3 @@ typealias ArcGISActualMarker = Graphic typealias ArcGISActualCircle = Graphic typealias ArcGISActualPolyline = Graphic typealias ArcGISActualPolygon = Graphic -typealias ArcGISActualGroundImage = Graphic diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundimage/ArcGISGroundImageRenderer.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundimage/ArcGISGroundImageRenderer.kt deleted file mode 100644 index 2d92546c..00000000 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/groundimage/ArcGISGroundImageRenderer.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.mapconductor.arcgis.groundimage - -import com.arcgismaps.mapping.view.Graphic -import com.mapconductor.arcgis.ArcGISActualGroundImage -import com.mapconductor.arcgis.ArcGISMapViewHolder -import com.mapconductor.core.groundimage.AbstractGroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageEntity -import com.mapconductor.core.groundimage.GroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageState -import kotlinx.coroutines.CoroutineScope - -class ArcGISGroundImageRenderer( - override val holder: ArcGISMapViewHolder, - override val coroutine: CoroutineScope, -) : AbstractGroundImageRenderer() { - override suspend fun addGroundImages(newGroundImages: List): List = - emptyList() - - override suspend fun changeGroundImages( - changes: List>, - ): List = emptyList() - - override suspend fun removeGroundImages(removeEntities: List>) { - } -} diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapTypeAlias.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapTypeAlias.kt new file mode 100644 index 00000000..68769060 --- /dev/null +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapTypeAlias.kt @@ -0,0 +1,13 @@ +package com.mapconductor.googlemaps + +import com.google.android.gms.maps.model.Circle +import com.google.android.gms.maps.model.GroundOverlay +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.Polygon +import com.google.android.gms.maps.model.Polyline + +typealias ActualGoogleMapMarker = Marker +typealias ActualGoogleMapCircle = Circle +typealias ActualGoogleMapPolyline = Polyline +typealias ActualGoogleMapPolygon = Polygon +typealias ActualGoogleMapGroundImage = GroundOverlay diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewControllerStore.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewControllerStore.kt index 6253853f..ced55963 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewControllerStore.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewControllerStore.kt @@ -3,8 +3,10 @@ package com.mapconductor.googlemaps import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.MapView +import com.mapconductor.core.groundimage.GroundImageController import com.mapconductor.core.map.MapViewHolder import com.mapconductor.core.map.StaticHolder +import com.mapconductor.googlemaps.groundimage.GoogleMapGroundImageRenderer import android.app.Activity import android.content.Context import android.content.ContextWrapper @@ -28,8 +30,19 @@ object GoogleMapViewControllerStore : StaticHolder() { options = options, ) + val groundImageRenderer = + GoogleMapGroundImageRenderer( + holder = holder, + ) + + val groundImageController = + GroundImageController( + renderer = groundImageRenderer, + ) + val controller = GoogleMapViewController( + groundImageController = groundImageController, holder = holder, ) this.set(id, controller) diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt index 6f22b9b0..4d831a7b 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/groundimage/GoogleMapGroundImageRenderer.kt @@ -4,41 +4,24 @@ import androidx.core.graphics.drawable.toBitmap import com.google.android.gms.maps.model.BitmapDescriptorFactory import com.google.android.gms.maps.model.GroundOverlay import com.google.android.gms.maps.model.GroundOverlayOptions -import com.mapconductor.core.groundimage.AbstractGroundImageRenderer +import com.mapconductor.core.controller.OverlayRenderer import com.mapconductor.core.groundimage.GroundImageEntity -import com.mapconductor.core.groundimage.GroundImageOverlayManager -import com.mapconductor.core.groundimage.GroundImageOverlayManagerImpl -import com.mapconductor.core.groundimage.GroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageRendererFactory import com.mapconductor.core.groundimage.GroundImageState +import com.mapconductor.googlemaps.ActualGoogleMapGroundImage import com.mapconductor.googlemaps.GoogleMapViewHolder import com.mapconductor.googlemaps.toLatLngBounds import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -class DefaultGoogleMapGroundImageRenderer : GroundImageRendererFactory { - override fun create( - onAdd: suspend (List) -> List, - onChange: suspend (List>) -> List, - onRemove: suspend (List>) -> Unit, - onPostProcess: (suspend () -> Unit)?, - ): GroundImageOverlayManager = - GroundImageOverlayManagerImpl( - onRemove = onRemove, - onAdd = onAdd, - onChange = onChange, - onPostProcess = onPostProcess, - ) -} - class GoogleMapGroundImageRenderer( - override val holder: GoogleMapViewHolder, - override val coroutine: CoroutineScope, -) : AbstractGroundImageRenderer() { - override suspend fun addGroundImages(newImages: List): List { + val holder: GoogleMapViewHolder, + val coroutine: CoroutineScope = CoroutineScope(Dispatchers.Main), +) : OverlayRenderer> { + override suspend fun onAdd(data: List): List { return withContext(coroutine.coroutineContext) { - return@withContext newImages.map { state -> + return@withContext data.map { state -> val bounds = state.bounds.toLatLngBounds() ?: return@withContext emptyList() val image = BitmapDescriptorFactory.fromBitmap(state.image.toBitmap()) val opacity = state.opacity @@ -54,29 +37,33 @@ class GoogleMapGroundImageRenderer( } } - override suspend fun removeGroundImages(removeEntities: List>) { + override suspend fun onRemove(data: List>) { coroutine.launch { - removeEntities.forEach { params -> params.groundImage.remove() } + data.forEach { params -> params.groundImage.remove() } } } - override suspend fun changeGroundImages( - changes: List>, + override suspend fun onPostProcess() { + // Do nothing here + } + + override suspend fun onChange( + data: List>>, ): List { return withContext(coroutine.coroutineContext) { - return@withContext changes.map { params -> - val groundOverlay = params.entity.groundImage - val finger = params.entity.fingerPrint - val prevFinger = params.prevEntity.fingerPrint + return@withContext data.map { params -> + val groundOverlay = params.current.groundImage + val finger = params.current.fingerPrint + val prevFinger = params.prev.fingerPrint if (finger.bounds != prevFinger.bounds) { - params.entity.state.bounds.toLatLngBounds()?.let { + params.current.state.bounds.toLatLngBounds()?.let { groundOverlay.setPositionFromBounds(it) } } - groundOverlay.transparency = 1.0f - params.entity.state.opacity + groundOverlay.transparency = 1.0f - params.current.state.opacity if (finger.image != prevFinger.image) { val bitmap = - params.entity.state.image + params.current.state.image .toBitmap() val bitmapDesc = BitmapDescriptorFactory.fromBitmap(bitmap) groundOverlay.setImage(bitmapDesc) diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapTypeAlias.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapTypeAlias.kt index 7151aecb..dc757271 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapTypeAlias.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapTypeAlias.kt @@ -1,6 +1,5 @@ package com.mapconductor.here -import com.here.sdk.mapview.MapImage import com.here.sdk.mapview.MapMarker import com.here.sdk.mapview.MapPolygon import com.here.sdk.mapview.MapPolyline @@ -9,4 +8,3 @@ typealias HereMapActualMarker = MapMarker typealias HereMapActualCircle = MapPolygon typealias HereMapActualPolyline = MapPolyline typealias HereMapActualPolygon = MapPolygon -typealias HereMapActualGroundImage = MapImage diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt index 3555651b..62e434ca 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt @@ -10,7 +10,6 @@ import com.here.sdk.mapview.MapCamera import com.here.sdk.mapview.MapCameraAnimationFactory import com.here.sdk.mapview.MapCameraListener import com.here.sdk.mapview.MapCameraUpdateFactory -import com.here.sdk.mapview.MapImage import com.here.sdk.mapview.MapMarker import com.here.sdk.mapview.MapMeasure import com.here.sdk.mapview.MapPolygon @@ -27,10 +26,6 @@ import com.mapconductor.core.controller.BaseMapViewController import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.geocell.HexGeocell -import com.mapconductor.core.groundimage.GroundImageOverlayManager -import com.mapconductor.core.groundimage.GroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageRendererFactory -import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewHolder import com.mapconductor.core.map.MapViewState.MoveCameraCallback @@ -48,8 +43,6 @@ import com.mapconductor.core.polyline.PolylineState import com.mapconductor.core.projection.WebMercator import com.mapconductor.here.circle.DefaultHereMapCircleRenderer import com.mapconductor.here.circle.HereMapCircleRenderer -import com.mapconductor.here.groundimage.DefaultHereMapGroundImageRenderer -import com.mapconductor.here.groundimage.HereMapGroundImageRenderer import com.mapconductor.here.marker.DefaultHereMapMarkerRenderer import com.mapconductor.here.marker.HereMapMarkerRenderer import com.mapconductor.here.polygon.DefaultHereMapPolygonRenderer @@ -61,7 +54,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -interface IHereMapViewController : MapViewController { +interface IHereMapViewController : MapViewController { fun moveCamera( dstPosition: MapCameraPosition, listener: MoveCameraCallback? = null, @@ -87,14 +80,11 @@ class HereMapViewController( DefaultHereMapPolylineRenderer(), private val polygonRendererFactory: PolygonRendererFactory = DefaultHereMapPolygonRenderer(), private val circleRendererFactory: CircleRendererFactory = DefaultHereMapCircleRenderer(), - private val groundImageRendererFactory: GroundImageRendererFactory = - DefaultHereMapGroundImageRenderer(), ) : BaseMapViewController< HereMapActualMarker, HereMapActualCircle, HereMapActualPolyline, HereMapActualPolygon, - HereMapActualGroundImage, >(), IHereMapViewController, MapCameraListener, @@ -139,12 +129,6 @@ class HereMapViewController( coroutine = coroutine, ) - override val groundImageRenderer: GroundImageRenderer = - HereMapGroundImageRenderer( - holder = holder, - coroutine = coroutine, - ) - override fun onCircleOverlayManagerInitialized(overlayManager: CircleOverlayManager) { } @@ -157,11 +141,6 @@ class HereMapViewController( override fun onMarkerOverlayManagerInitialized(overlayManager: MarkerOverlayManager) { } - override fun onGroundImageOverlayManagerInitialized( - overlayManager: GroundImageOverlayManager, - ) { - } - override fun createPolylineOverlayManager(): PolylineOverlayManager = polylineRendererFactory.create( onAdd = polylineRenderer::addPolylines, @@ -183,15 +162,6 @@ class HereMapViewController( onRemove = circleRenderer::removeCircles, ) - override fun createGroundImageOverlayManager(): GroundImageOverlayManager { - groundImageRendererFactory.create( - onAdd = groundImageRenderer::addGroundImages, - onChange = groundImageRenderer::changeGroundImages, - onRemove = groundImageRenderer::removeGroundImages, - ) - TODO("Not yet implemented") - } - override suspend fun clearOverlays() { markerOverlayManager.clearOverlays() polylineOverlayManager.clearOverlays() @@ -211,14 +181,6 @@ class HereMapViewController( override suspend fun updatePolyline(state: PolylineState) = polylineOverlayManager.updatePolyline(state) - override suspend fun addGroundImages(data: List) { - TODO("Not yet implemented") - } - - override suspend fun updateGroundImage(state: GroundImageState) { - TODO("Not yet implemented") - } - init { setupListeners() markerRenderer.init(markerOverlayManager) diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt deleted file mode 100644 index b5da8cca..00000000 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/groundimage/HereGroundImageRenderer.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.mapconductor.here.groundimage - -import com.here.sdk.mapview.MapImage -import com.mapconductor.core.groundimage.AbstractGroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageEntity -import com.mapconductor.core.groundimage.GroundImageOverlayManager -import com.mapconductor.core.groundimage.GroundImageOverlayManagerImpl -import com.mapconductor.core.groundimage.GroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageRendererFactory -import com.mapconductor.core.groundimage.GroundImageState -import com.mapconductor.here.HereMapViewHolder -import kotlinx.coroutines.CoroutineScope - -class DefaultHereMapGroundImageRenderer : GroundImageRendererFactory { - override fun create( - onAdd: suspend (List) -> List, - onChange: suspend (List>) -> List, - onRemove: suspend (List>) -> Unit, - onPostProcess: (suspend () -> Unit)?, - ): GroundImageOverlayManager = - GroundImageOverlayManagerImpl( - onRemove = onRemove, - onAdd = onAdd, - onChange = onChange, - onPostProcess = onPostProcess, - ) -} - -class HereMapGroundImageRenderer( - override val holder: HereMapViewHolder, - override val coroutine: CoroutineScope, -) : AbstractGroundImageRenderer() { - override suspend fun addGroundImages(newGroundImages: List): List { - return emptyList() -// return withContext(coroutine.coroutineContext){ -// return@withContext newGroundImages.map { state -> -// val bounds = state.bounds ?: return@withContext emptyList() -// val image = state.image -// val alpha = state.alpha -// val options = state.extra -// } -// } - } - - override suspend fun changeGroundImages( - changes: List>, - ): List = emptyList() - - override suspend fun removeGroundImages(removeEntities: List>) { - } -} diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt index d4fb175f..bae037c1 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt @@ -30,9 +30,6 @@ import com.mapconductor.core.circle.CircleState import com.mapconductor.core.controller.BaseMapViewController import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.geocell.HexGeocell -import com.mapconductor.core.groundimage.GroundImageOverlayManager -import com.mapconductor.core.groundimage.GroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewState import com.mapconductor.core.marker.MarkerOverlayManager @@ -47,7 +44,6 @@ import com.mapconductor.core.projection.WebMercator import com.mapconductor.mapbox.circle.DefaultMapboxCircleRenderer import com.mapconductor.mapbox.circle.MapboxCircleLayer import com.mapconductor.mapbox.circle.MapboxCircleRenderer -import com.mapconductor.mapbox.groundImage.MapboxGroundImageRenderer import com.mapconductor.mapbox.marker.DefaultMapboxMarkerRenderer import com.mapconductor.mapbox.marker.MapboxMarkerRenderer import com.mapconductor.mapbox.marker.MarkerDragLayer @@ -69,7 +65,6 @@ interface IMapboxMapViewController : MapboxActualCircle, MapboxActualPolyline, MapboxActualPolygon, - MapboxActualGroundImage, > { fun moveCamera( dstPosition: MapCameraPosition, @@ -127,7 +122,6 @@ internal class MapboxMapViewController( MapboxActualCircle, MapboxActualPolyline, MapboxActualPolygon, - MapboxActualGroundImage, >(), IMapboxMapViewController, CameraChangedCallback, @@ -176,10 +170,6 @@ internal class MapboxMapViewController( onPostProcess = circleRenderer::redraw, ) - override fun createGroundImageOverlayManager(): GroundImageOverlayManager { - TODO("Not yet implemented") - } - override fun onMarkerOverlayManagerInitialized(overlayManager: MarkerOverlayManager) { holder.map.getStyle { style -> style.addSource(circleLayer.source) @@ -213,17 +203,6 @@ internal class MapboxMapViewController( layer = circleLayer, ) - override fun onGroundImageOverlayManagerInitialized( - overlayManager: GroundImageOverlayManager, - ) { - } - - override val groundImageRenderer: GroundImageRenderer = - MapboxGroundImageRenderer( - holder = holder, - coroutine = coroutine, - ) - override fun onCircleOverlayManagerInitialized(overlayManager: CircleOverlayManager) { } @@ -262,14 +241,6 @@ internal class MapboxMapViewController( override suspend fun updatePolyline(state: PolylineState) = polylineOverlayManager.updatePolyline(state) - override suspend fun addGroundImages(data: List) { - TODO("Not yet implemented") - } - - override suspend fun updateGroundImage(state: GroundImageState) { - TODO("Not yet implemented") - } - override suspend fun addCircles(data: List) = circleOverlayManager.addCircles(data) override suspend fun updateCircle(state: CircleState) = circleOverlayManager.updateCircle(state) diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxTypeAlias.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxTypeAlias.kt index b4065d74..a8199ba7 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxTypeAlias.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxTypeAlias.kt @@ -6,4 +6,3 @@ typealias MapboxActualMarker = Feature typealias MapboxActualCircle = Feature typealias MapboxActualPolyline = List typealias MapboxActualPolygon = Feature -typealias MapboxActualGroundImage = Feature diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt deleted file mode 100644 index 1a5b43d9..00000000 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/groundImage/MapboxGroundImageRenderer.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.mapconductor.mapbox.groundImage - -import com.mapbox.geojson.Feature -import com.mapconductor.core.groundimage.AbstractGroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageEntity -import com.mapconductor.core.groundimage.GroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageState -import com.mapconductor.mapbox.MapboxMapViewHolder -import kotlinx.coroutines.CoroutineScope - -class MapboxGroundImageRenderer( - override val holder: MapboxMapViewHolder, - override val coroutine: CoroutineScope, -) : AbstractGroundImageRenderer() { - override suspend fun addGroundImages(newGroundImages: List): List = emptyList() - - override suspend fun changeGroundImages(changes: List>): List = - emptyList() - - override suspend fun removeGroundImages(removeEntities: List>) { - } -} From 9c5c7d22a7f2ca33326ac3ba1b7b3a54bbb0e27b Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Thu, 21 Aug 2025 23:54:12 +0900 Subject: [PATCH 17/29] Resolve conflicts and add MapDesign demo with Google Maps SDK. --- .../pages/mapDesign/MapDesignMapPage.kt | 147 ++++++++++-------- .../pages/mapDesign/MapDesignPageViewModel.kt | 1 - .../googlemaps/GoogleMapViewController.kt | 59 +++---- 3 files changed, 104 insertions(+), 103 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt index 96d1d943..120baf9c 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt @@ -1,8 +1,25 @@ package com.mapconductor.example.pages.mapDesign +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp import com.mapconductor.example.ui.DemoMapPageScaffold +import com.mapconductor.example.ui.MessageCard +import androidx.compose.material3.Button +import com.mapconductor.core.map.MapViewState +import com.mapconductor.googlemaps.GoogleMapDesign @Composable fun MapDesignMapPage( @@ -21,72 +38,68 @@ fun MapDesignMapPage( onMapClick = viewModel::onMapClick, ) -// // Control Panel -// MessageCard( -// modifier = -// Modifier -// .align(Alignment.BottomStart) -// .padding( -// bottom = paddingValues.calculateBottomPadding() + 16.dp, -// start = paddingValues.calculateStartPadding(LayoutDirection.Ltr) + 16.dp, -// end = paddingValues.calculateEndPadding(LayoutDirection.Ltr) + 16.dp, -// ), -// title = "Select Map Design...", -// maxHeight = 400.dp, -// ) { -// Column( -// modifier = Modifier.fillMaxSize(), -// verticalArrangement = Arrangement.spacedBy(8.dp), -// ) { -// Row( -// modifier = Modifier.fillMaxWidth(), -// horizontalArrangement = Arrangement.spacedBy(8.dp), -// ) { -// Button( -// modifier = Modifier.weight(1f), -// onClick = { viewModel.flyToSydney() }, -// ) { -// Text("Normal") -// } -// -// Button( -// modifier = Modifier.weight(1f), -// onClick = { viewModel.flyToSydney() }, -// ) { -// Text("Satellite") -// } -// } -// Row( -// modifier = Modifier.fillMaxWidth(), -// horizontalArrangement = Arrangement.spacedBy(8.dp), -// ) { -// Button( -// modifier = Modifier.weight(1f), -// onClick = { viewModel.flyToHonolulu() }, -// ) { -// Text("Hybrid") -// } -// -// Button( -// modifier = Modifier.weight(1f), -// onClick = { viewModel.flyToTokyo() }, -// ) { -// Text("Terrain") -// } -// } -// -// Row( -// modifier = Modifier.fillMaxWidth(), -// horizontalArrangement = Arrangement.spacedBy(8.dp), -// ) { -// Button( -// modifier = Modifier.weight(1f), -// onClick = { viewModel.flyToLondon() }, -// ) { -// Text("None") -// } -// } -// } -// } + // Message Card + MessageCard( + modifier = + Modifier + .align(Alignment.BottomStart) + .padding( + bottom = paddingValues.calculateBottomPadding() + 16.dp, + start = paddingValues.calculateStartPadding(LayoutDirection.Ltr) + 16.dp, + end = paddingValues.calculateEndPadding(LayoutDirection.Ltr) + 16.dp, + ), + title = "Select Map Design...", + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp), + ){ + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Button( + modifier = Modifier.weight(1f), + onClick = { (viewModel.mapViewState.value as? MapViewState) + ?.changeMapDesignType(GoogleMapDesign.Normal) }, + ) { + Text("Normal") + } + Button( + modifier = Modifier.weight(1f), + onClick = { (viewModel.mapViewState.value as? MapViewState) + ?.changeMapDesignType(GoogleMapDesign.Satellite) }, + ) { + Text("Satellite") + } + Button( + modifier = Modifier.weight(1f), + onClick = { (viewModel.mapViewState.value as? MapViewState) + ?.changeMapDesignType(GoogleMapDesign.Hybrid) }, + ) { + Text("Hybrid") + } + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Button( + modifier = Modifier.weight(1f), + onClick = { (viewModel.mapViewState.value as? MapViewState) + ?.changeMapDesignType(GoogleMapDesign.Terrain) }, + ) { + Text("Terrain") + } + Button( + modifier = Modifier.weight(1f), + onClick = { (viewModel.mapViewState.value as? MapViewState) + ?.changeMapDesignType(GoogleMapDesign.None) }, + ) { + Text("None") + } + } + } + } } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt index 8cb1f738..0e0a930e 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt @@ -56,7 +56,6 @@ class MapDesignPageViewModelImpl(): override var design by mutableStateOf(1) override fun onMapViewChanged(state: MapViewState<*>) { -// state.mapDesignType = GoogleMapDesign.Satellite this._mapViewState.value = state } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt index 53a75239..c103e27c 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt @@ -10,7 +10,6 @@ import com.google.android.gms.maps.GoogleMap.OnMapClickListener import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener import com.google.android.gms.maps.model.Circle -import com.google.android.gms.maps.model.GroundOverlay import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.Marker import com.google.android.gms.maps.model.Polygon @@ -23,11 +22,11 @@ import com.mapconductor.core.circle.CircleState import com.mapconductor.core.controller.BaseMapViewController import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.geocell.HexGeocell +import com.mapconductor.core.groundimage.GroundImageCapable +import com.mapconductor.core.groundimage.GroundImageController import com.mapconductor.core.groundimage.GroundImageEvent -import com.mapconductor.core.groundimage.GroundImageOverlayManager -import com.mapconductor.core.groundimage.GroundImageRenderer -import com.mapconductor.core.groundimage.GroundImageRendererFactory import com.mapconductor.core.groundimage.GroundImageState +import com.mapconductor.core.groundimage.OnGroundImageEventHandler import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapViewState import com.mapconductor.core.marker.MarkerOverlayManager @@ -44,8 +43,6 @@ import com.mapconductor.core.polyline.PolylineState import com.mapconductor.core.projection.WebMercator import com.mapconductor.googlemaps.circle.DefaultGoogleMapCircleRenderer import com.mapconductor.googlemaps.circle.GoogleMapCircleRenderer -import com.mapconductor.googlemaps.groundimage.DefaultGoogleMapGroundImageRenderer -import com.mapconductor.googlemaps.groundimage.GoogleMapGroundImageRenderer import com.mapconductor.googlemaps.marker.DefaultGoogleMapMarkerRenderer import com.mapconductor.googlemaps.marker.GoogleMapMarkerRenderer import com.mapconductor.googlemaps.polygon.DefaultGoogleMapPolygonRenderer @@ -56,7 +53,9 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -interface IGoogleMapViewController : MapViewController { +interface IGoogleMapViewController : + MapViewController, + GroundImageCapable { fun changeMapDesign( value: Int ) @@ -85,9 +84,13 @@ class GoogleMapViewController( private val polylineRendererFactory: PolylineRendererFactory = DefaultGoogleMapPolylineRenderer(), private val polygonRendererFactory: PolygonRendererFactory = DefaultGoogleMapPolygonRenderer(), private val circleRendererFactory: CircleRendererFactory = DefaultGoogleMapCircleRenderer(), - private val groundImageRendererFactory: GroundImageRendererFactory = - DefaultGoogleMapGroundImageRenderer(), -) : BaseMapViewController(), + private val groundImageController: GroundImageController, +) : BaseMapViewController< + ActualGoogleMapMarker, + ActualGoogleMapCircle, + ActualGoogleMapPolyline, + ActualGoogleMapPolygon, + >(), IGoogleMapViewController, OnCameraMoveStartedListener, OnCameraMoveCanceledListener, @@ -155,9 +158,6 @@ class GoogleMapViewController( coroutine = coroutine, ) - override fun onGroundImageOverlayManagerInitialized(overlayManager: GroundImageOverlayManager) { - } - override fun createCircleOverlayManager(): CircleOverlayManager = circleRendererFactory.create( onAdd = circleRenderer::addCircles, @@ -165,19 +165,6 @@ class GoogleMapViewController( onRemove = circleRenderer::removeCircles, ) - override val groundImageRenderer: GroundImageRenderer = - GoogleMapGroundImageRenderer( - holder = holder, - coroutine = coroutine, - ) - - override fun createGroundImageOverlayManager(): GroundImageOverlayManager = - groundImageRendererFactory.create( - onAdd = groundImageRenderer::addGroundImages, - onChange = groundImageRenderer::changeGroundImages, - onRemove = groundImageRenderer::removeGroundImages, - ) - init { setupListeners() markerRenderer.init(markerOverlayManager) @@ -254,10 +241,6 @@ class GoogleMapViewController( override suspend fun updatePolyline(state: PolylineState) = polylineOverlayManager.updatePolyline(state) - override suspend fun addGroundImages(data: List) = groundImageOverlayManager.addGroundImages(data) - - override suspend fun updateGroundImage(state: GroundImageState) = groundImageOverlayManager.updateGroundImage(state) - override fun onCameraMove() { cameraMoveCallback?.let { val mapCameraPosition = holder.map.cameraPosition.toMapCameraPosition() @@ -311,16 +294,14 @@ class GoogleMapViewController( return } - groundImageOverlayManager.find(touchPosition)?.let { entity -> + groundImageController.find(touchPosition)?.let { entity -> val event = GroundImageEvent( state = entity.state, position = touchPosition, ) - groundImageClickCallback?.let { - coroutine.launch { - it.invoke(event) - } + coroutine.launch { + groundImageController.clickListener?.invoke(event) } return } @@ -363,4 +344,12 @@ class GoogleMapViewController( markerDragStartCallback?.invoke(state) } } + + override suspend fun compositionGroundImages(data: List) = groundImageController.add(data) + + override suspend fun updateGroundImage(state: GroundImageState) = groundImageController.update(state) + + fun setOnGroundImageClickListener(listener: OnGroundImageEventHandler?) { + this.groundImageController.clickListener = listener + } } From b6f67efa9303de6ab70cc58bd9b276caf40ddaf8 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Sat, 23 Aug 2025 01:37:15 +0900 Subject: [PATCH 18/29] Add MapDesign change demo for Google Maps API and initial Here Map support with list display (WIP). --- .../pages/mapDesign/MapDesignMapPage.kt | 197 ++++++++++++++---- .../pages/mapDesign/MapDesignPageViewModel.kt | 44 ++++ 2 files changed, 197 insertions(+), 44 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt index 120baf9c..093864a3 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt @@ -18,14 +18,32 @@ import androidx.compose.ui.unit.dp import com.mapconductor.example.ui.DemoMapPageScaffold import com.mapconductor.example.ui.MessageCard import androidx.compose.material3.Button +import com.mapbox.maps.extension.style.expressions.dsl.generated.mod +import com.mapconductor.arcgis.ArcGISDesign +import com.mapconductor.arcgis.ArcGISMapViewState +import com.mapconductor.core.map.MapDesignType import com.mapconductor.core.map.MapViewState import com.mapconductor.googlemaps.GoogleMapDesign +import com.mapconductor.googlemaps.GoogleMapDesignType +import com.mapconductor.googlemaps.GoogleMapViewState +import com.mapconductor.here.HereMapDesign +import com.mapconductor.here.HereMapDesignType +import com.mapconductor.here.HereMapViewState +import com.mapconductor.mapbox.MapboxMapDesign +import com.mapconductor.mapbox.MapboxMapViewState + +data class DesignList( + val text: String, + val design: MapDesignType +) @Composable fun MapDesignMapPage( viewModel: MapDesignPageViewModel = MapDesignPageViewModelImpl(), onToggleSidebar: () -> Unit = {}, ) { + val buttons = viewModel.buttons.collectAsState() + DemoMapPageScaffold( initCameraPosition = viewModel.initCameraPosition, onToggleSidebar = onToggleSidebar, @@ -33,6 +51,31 @@ fun MapDesignMapPage( ) { paddingValues -> val mapViewState = viewModel.mapViewState.collectAsState() + fun callChangeMapDesignType(state: MapViewState<*>,designType: MapDesignType<*>) { + when (state) { + is GoogleMapViewState -> { + (designType as? GoogleMapDesign)?.let { + state.changeMapDesignType(it) + } + } +// is HereMapViewState -> { +// (designType as? MapViewState)?.let { +// state.changeMapDesignType(it) +// } +// } +// is MapboxMapViewState -> { +// (designType as? MapViewState)?.let { +// state.changeMapDesignType(it) +// } +// } +// is ArcGISMapViewState -> { +// (designType as? MapViewState)?.let { +// state.changeMapDesignType(it) +// } +// } + } + } + MapDesignMapComponent( mapViewState = mapViewState.value, onMapClick = viewModel::onMapClick, @@ -53,53 +96,119 @@ fun MapDesignMapPage( Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(8.dp), - ){ - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Button( - modifier = Modifier.weight(1f), - onClick = { (viewModel.mapViewState.value as? MapViewState) - ?.changeMapDesignType(GoogleMapDesign.Normal) }, - ) { - Text("Normal") - } - Button( - modifier = Modifier.weight(1f), - onClick = { (viewModel.mapViewState.value as? MapViewState) - ?.changeMapDesignType(GoogleMapDesign.Satellite) }, - ) { - Text("Satellite") - } - Button( - modifier = Modifier.weight(1f), - onClick = { (viewModel.mapViewState.value as? MapViewState) - ?.changeMapDesignType(GoogleMapDesign.Hybrid) }, - ) { - Text("Hybrid") - } - } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Button( - modifier = Modifier.weight(1f), - onClick = { (viewModel.mapViewState.value as? MapViewState) - ?.changeMapDesignType(GoogleMapDesign.Terrain) }, - ) { - Text("Terrain") - } - Button( - modifier = Modifier.weight(1f), - onClick = { (viewModel.mapViewState.value as? MapViewState) - ?.changeMapDesignType(GoogleMapDesign.None) }, - ) { - Text("None") + ) { + Row { + buttons.value.forEach { + Button( + modifier = Modifier.weight(1f), + onClick = { + callChangeMapDesignType(mapViewState.value!!, it.designType) + } + ){ + Text(it.label) + } } } } +// Column( +// modifier = Modifier.fillMaxSize(), +// verticalArrangement = Arrangement.spacedBy(8.dp), +// ) { +// when (mapViewState) { +// is GoogleMapViewState -> { +// Row( +// modifier = Modifier.fillMaxWidth(), +// horizontalArrangement = Arrangement.spacedBy(8.dp), +// ) { +// Button( +// modifier = Modifier.weight(1f), +// onClick = { +// (viewModel.mapViewState.value as? MapViewState) +// ?.changeMapDesignType(GoogleMapDesign.Normal) +// }, +// ) { +// Text("Normal") +// } +// Button( +// modifier = Modifier.weight(1f), +// onClick = { +// (viewModel.mapViewState.value as? MapViewState) +// ?.changeMapDesignType(GoogleMapDesign.Satellite) +// }, +// ) { +// Text("Satellite") +// } +// Button( +// modifier = Modifier.weight(1f), +// onClick = { +// (viewModel.mapViewState.value as? MapViewState) +// ?.changeMapDesignType(GoogleMapDesign.Hybrid) +// }, +// ) { +// Text("Hybrid") +// } +// } +// Row( +// modifier = Modifier.fillMaxWidth(), +// horizontalArrangement = Arrangement.spacedBy(8.dp), +// ) { +// Button( +// modifier = Modifier.weight(1f), +// onClick = { +// (viewModel.mapViewState.value as? MapViewState) +// ?.changeMapDesignType(GoogleMapDesign.Terrain) +// }, +// ) { +// Text("Terrain") +// } +// Button( +// modifier = Modifier.weight(1f), +// onClick = { +// (viewModel.mapViewState.value as? MapViewState) +// ?.changeMapDesignType(GoogleMapDesign.None) +// }, +// ) { +// Text("None") +// } +// } +// } +// +// is HereMapViewState -> { +// Row( +// modifier = Modifier.fillMaxWidth(), +// horizontalArrangement = Arrangement.spacedBy(8.dp), +// ) { +// Button( +// modifier = Modifier.weight(1f), +// onClick = { +// (viewModel.mapViewState.value as? MapViewState) +// ?.changeMapDesignType(HereMapDesign.NormalDay) +// }, +// ) { +// Text("NormalDay") +// } +// Button( +// modifier = Modifier.weight(1f), +// onClick = { +// (viewModel.mapViewState.value as? MapViewState) +// ?.changeMapDesignType(HereMapDesign.NormalNigh) +// }, +// ) { +// Text("NormalNigh") +// } +// Button( +// modifier = Modifier.weight(1f), +// onClick = { +// (viewModel.mapViewState.value as? MapViewState) +// ?.changeMapDesignType(HereMapDesign.Satellite) +// }, +// ) { +// Text("Satellite") +// } +// } +// } +// } +// } } } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt index 0e0a930e..2512cef0 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt @@ -1,26 +1,41 @@ package com.mapconductor.example.pages.mapDesign import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.map.MapCameraPosition +import com.mapconductor.core.map.MapDesignType import com.mapconductor.core.map.MapViewState import com.mapconductor.core.marker.MarkerState import com.mapconductor.core.polyline.PolylineState import com.mapconductor.example.toast.ToastMessage import com.mapconductor.googlemaps.GoogleMapDesign +import com.mapconductor.googlemaps.GoogleMapDesignType +import com.mapconductor.googlemaps.GoogleMapViewState +import com.mapconductor.here.HereMapDesign +import com.mapconductor.here.HereMapViewState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asStateFlow +data class MapDesignButton( + val label: String, + val designType: MapDesignType<*> +) + + interface MapDesignPageViewModel { val initCameraPosition: MapCameraPosition val mapViewState: StateFlow?> val messages: StateFlow> var design: Int + + val buttons: StateFlow> fun onMapViewChanged(state: MapViewState<*>) fun onMapClick(clicked: GeoPoint) @@ -53,10 +68,39 @@ class MapDesignPageViewModelImpl(): private val _mapViewState = MutableStateFlow?>(null) override val mapViewState: StateFlow?> = _mapViewState.asStateFlow() + private val _buttons: MutableStateFlow> = MutableStateFlow(emptyList()) + override val buttons: StateFlow> = _buttons.asStateFlow() + override var design by mutableStateOf(1) override fun onMapViewChanged(state: MapViewState<*>) { this._mapViewState.value = state + when(state) { + is GoogleMapViewState -> { + _buttons.value = listOf( + MapDesignButton( + label = "Normal", + designType = GoogleMapDesign.Normal, + ), + MapDesignButton( + label = "Satellite", + designType = GoogleMapDesign.Satellite, + ), + ) + } + is HereMapViewState -> { + _buttons.value = listOf( + MapDesignButton( + label = "NormalDay", + designType = HereMapDesign.NormalDay, + ), + MapDesignButton( + label = "NormalNigh", + designType = HereMapDesign.NormalNigh, + ), + ) + } + } } override fun onMapClick(clicked: GeoPoint) { From 9183548eeaa030c15271dcf0ab464c4eaa68a099 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Sat, 23 Aug 2025 22:31:40 +0900 Subject: [PATCH 19/29] feat(here): add MapDesign support for HereMap with runtime style switching. --- .../example/pages/mapDesign/MapDesignMapPage.kt | 10 +++++----- .../com/mapconductor/here/HereMapViewController.kt | 13 +++++++++++++ .../java/com/mapconductor/here/HereMapViewState.kt | 7 +++++-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt index 093864a3..3bf2f15c 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt @@ -58,11 +58,11 @@ fun MapDesignMapPage( state.changeMapDesignType(it) } } -// is HereMapViewState -> { -// (designType as? MapViewState)?.let { -// state.changeMapDesignType(it) -// } -// } + is HereMapViewState -> { + (designType as? HereMapDesign)?.let { + state.changeMapDesignType(it) + } + } // is MapboxMapViewState -> { // (designType as? MapViewState)?.let { // state.changeMapDesignType(it) diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt index 62e434ca..14fe1520 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewController.kt @@ -15,6 +15,7 @@ import com.here.sdk.mapview.MapMeasure import com.here.sdk.mapview.MapPolygon import com.here.sdk.mapview.MapPolyline import com.here.sdk.mapview.MapScene +import com.here.sdk.mapview.MapScheme import com.here.sdk.mapview.MapView import com.here.time.Duration import com.mapconductor.core.ResourceProvider @@ -55,6 +56,10 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch interface IHereMapViewController : MapViewController { + fun changeMapDesign( + value: MapScheme + ) + fun moveCamera( dstPosition: MapCameraPosition, listener: MoveCameraCallback? = null, @@ -195,6 +200,14 @@ class HereMapViewController( holder.mapView.gestures.longPressListener = this } + override fun changeMapDesign( + value: MapScheme + ){ + coroutine.launch { + holder.mapView.mapScene.loadScene(value){} + } + } + override fun moveCamera( dstPosition: MapCameraPosition, listener: MoveCameraCallback?, diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewState.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewState.kt index dcf72897..dab6aed3 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewState.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewState.kt @@ -38,8 +38,11 @@ class HereMapViewState( private val _cameraPosition = MutableStateFlow(initCameraPosition) override val cameraPosition: StateFlow = _cameraPosition.asStateFlow() - override fun changeMapDesignType(value: HereMapDesignType) { - TODO("Not yet implemented") + override fun changeMapDesignType( + value: HereMapDesignType + ) { + this.mapDesignType = value + this.controller?.changeMapDesign(value.getValue()) } override fun moveCameraTo( From 88e104d8ee8f8fc5aacca3e3e231ca1c35cf4f88 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Sat, 23 Aug 2025 23:11:07 +0900 Subject: [PATCH 20/29] feat(Mapbox): add MapDesign support for Mapbox with runtime style switching. --- .../pages/mapDesign/MapDesignMapPage.kt | 10 +++++----- .../pages/mapDesign/MapDesignPageViewModel.kt | 18 ++++++++++++++++-- .../mapbox/MapboxMapViewController.kt | 13 +++++++++++++ .../com/mapconductor/mapbox/MapboxViewState.kt | 3 ++- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt index 3bf2f15c..c4150993 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt @@ -63,11 +63,11 @@ fun MapDesignMapPage( state.changeMapDesignType(it) } } -// is MapboxMapViewState -> { -// (designType as? MapViewState)?.let { -// state.changeMapDesignType(it) -// } -// } + is MapboxMapViewState -> { + (designType as? MapboxMapDesign)?.let { + state.changeMapDesignType(it) + } + } // is ArcGISMapViewState -> { // (designType as? MapViewState)?.let { // state.changeMapDesignType(it) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt index 2512cef0..533baed1 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt @@ -17,6 +17,8 @@ import com.mapconductor.googlemaps.GoogleMapDesignType import com.mapconductor.googlemaps.GoogleMapViewState import com.mapconductor.here.HereMapDesign import com.mapconductor.here.HereMapViewState +import com.mapconductor.mapbox.MapboxMapDesign +import com.mapconductor.mapbox.MapboxMapViewState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asFlow @@ -77,7 +79,7 @@ class MapDesignPageViewModelImpl(): this._mapViewState.value = state when(state) { is GoogleMapViewState -> { - _buttons.value = listOf( + _buttons.value = listOf( MapDesignButton( label = "Normal", designType = GoogleMapDesign.Normal, @@ -89,7 +91,7 @@ class MapDesignPageViewModelImpl(): ) } is HereMapViewState -> { - _buttons.value = listOf( + _buttons.value = listOf( MapDesignButton( label = "NormalDay", designType = HereMapDesign.NormalDay, @@ -100,6 +102,18 @@ class MapDesignPageViewModelImpl(): ), ) } + is MapboxMapViewState -> { + _buttons.value = listOf( + MapDesignButton( + label = "standard", + designType = MapboxMapDesign.Standard, + ), + MapDesignButton( + label = "standard-satellite", + designType = MapboxMapDesign.StandardSatellite, + ), + ) + } } } diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt index bae037c1..6c61525f 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt @@ -66,6 +66,11 @@ interface IMapboxMapViewController : MapboxActualPolyline, MapboxActualPolygon, > { + + fun changeMapDesign( + value: String + ) + fun moveCamera( dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback? = null, @@ -260,6 +265,14 @@ internal class MapboxMapViewController( } } + override fun changeMapDesign( + value: String + ){ + coroutine.launch { + holder.mapView.mapboxMap.loadStyle(value) {} + } + } + override fun moveCamera( dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback?, diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt index b47a5fc8..fc796c23 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt @@ -33,7 +33,8 @@ class MapboxMapViewState( override val cameraPosition: StateFlow = _cameraPosition.asStateFlow() override fun changeMapDesignType(value: MapboxDesignType) { - TODO("Not yet implemented") + this.mapDesignType = value + this.controller?.changeMapDesign(value.getValue()) } override fun moveCameraTo( From fa0898ee6c6b60e7c3a988da5f2a8b410642cb72 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Sun, 24 Aug 2025 00:12:27 +0900 Subject: [PATCH 21/29] feat(ArcGIS): add MapDesign support for ArcGIS with runtime style switching. --- .../pages/mapDesign/MapDesignMapPage.kt | 10 +++++----- .../pages/mapDesign/MapDesignPageViewModel.kt | 14 ++++++++++++++ .../arcgis/ArcGISMapViewController.kt | 19 +++++++++++++++++++ .../mapconductor/arcgis/ArcGisMapViewState.kt | 3 ++- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt index c4150993..b496de54 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt @@ -68,11 +68,11 @@ fun MapDesignMapPage( state.changeMapDesignType(it) } } -// is ArcGISMapViewState -> { -// (designType as? MapViewState)?.let { -// state.changeMapDesignType(it) -// } -// } + is ArcGISMapViewState -> { + (designType as? ArcGISDesign)?.let { + state.changeMapDesignType(it) + } + } } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt index 533baed1..e368e819 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt @@ -5,6 +5,8 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import com.mapconductor.arcgis.ArcGISDesign +import com.mapconductor.arcgis.ArcGISMapViewState import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapDesignType @@ -114,6 +116,18 @@ class MapDesignPageViewModelImpl(): ), ) } + is ArcGISMapViewState -> { + _buttons.value = listOf( + MapDesignButton( + label = "Streets", + designType = ArcGISDesign.Streets, + ), + MapDesignButton( + label = "Imagery", + designType = ArcGISDesign.Imagery, + ), + ) + } } } diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt index ec34c923..f982460d 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt @@ -44,6 +44,7 @@ import android.view.MotionEvent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import com.arcgismaps.mapping.Basemap interface IArcGISMapViewController : MapViewController< @@ -52,6 +53,10 @@ interface IArcGISMapViewController : ArcGISActualPolyline, ArcGISActualPolygon, > { + fun changeMapDesign( + value: String + ) + fun moveCamera( dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback? = null, @@ -339,6 +344,20 @@ class ArcGISMapViewController( override suspend fun updateCircle(state: CircleState) = circleOverlayManager.updateCircle(state) + override fun changeMapDesign( + value: String + ){ + coroutine.launch { + holder.map.scene!!.setBasemap( + Basemap( + ArcGISDesign.toBasemapStyle( + ArcGISDesign(value) + ) + ) + ) + } + } + override fun moveCamera( dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback?, diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt index 0beca032..63e58f6e 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt @@ -40,7 +40,8 @@ class ArcGISMapViewState( override val cameraPosition: StateFlow = _cameraPosition.asStateFlow() override fun changeMapDesignType(value: ArcGISDesign) { - TODO("Not yet implemented") + this.mapDesignType = value + this.controller?.changeMapDesign(value.getValue()) } override fun moveCameraTo( From 6892edda819a37a841ab3b144533cc7faeefd760 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Sun, 24 Aug 2025 22:36:12 +0900 Subject: [PATCH 22/29] Refactor MapDesign selection UI: replace button controls with dropdown list. --- .../pages/mapDesign/MapDesignMapPage.kt | 197 +++++++----------- .../example/ui/DemoMapPageScaffold.kt | 15 -- 2 files changed, 72 insertions(+), 140 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt index b496de54..eb4515fb 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -18,25 +17,29 @@ import androidx.compose.ui.unit.dp import com.mapconductor.example.ui.DemoMapPageScaffold import com.mapconductor.example.ui.MessageCard import androidx.compose.material3.Button -import com.mapbox.maps.extension.style.expressions.dsl.generated.mod import com.mapconductor.arcgis.ArcGISDesign import com.mapconductor.arcgis.ArcGISMapViewState import com.mapconductor.core.map.MapDesignType import com.mapconductor.core.map.MapViewState import com.mapconductor.googlemaps.GoogleMapDesign -import com.mapconductor.googlemaps.GoogleMapDesignType import com.mapconductor.googlemaps.GoogleMapViewState import com.mapconductor.here.HereMapDesign -import com.mapconductor.here.HereMapDesignType import com.mapconductor.here.HereMapViewState import com.mapconductor.mapbox.MapboxMapDesign import com.mapconductor.mapbox.MapboxMapViewState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.TextField +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable data class DesignList( val text: String, val design: MapDesignType ) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun MapDesignMapPage( viewModel: MapDesignPageViewModel = MapDesignPageViewModelImpl(), @@ -53,26 +56,30 @@ fun MapDesignMapPage( fun callChangeMapDesignType(state: MapViewState<*>,designType: MapDesignType<*>) { when (state) { - is GoogleMapViewState -> { - (designType as? GoogleMapDesign)?.let { - state.changeMapDesignType(it) - } - } - is HereMapViewState -> { - (designType as? HereMapDesign)?.let { - state.changeMapDesignType(it) - } - } - is MapboxMapViewState -> { - (designType as? MapboxMapDesign)?.let { - state.changeMapDesignType(it) - } - } - is ArcGISMapViewState -> { - (designType as? ArcGISDesign)?.let { - state.changeMapDesignType(it) - } - } + is GoogleMapViewState -> (designType as? GoogleMapDesign)?.let { state.changeMapDesignType(it) } + is HereMapViewState -> (designType as? HereMapDesign )?.let { state.changeMapDesignType(it) } + is MapboxMapViewState -> (designType as? MapboxMapDesign)?.let { state.changeMapDesignType(it) } + is ArcGISMapViewState -> (designType as? ArcGISDesign )?.let { state.changeMapDesignType(it) } +// is GoogleMapViewState -> { +// (designType as? GoogleMapDesign)?.let { +// state.changeMapDesignType(it) +// } +// } +// is HereMapViewState -> { +// (designType as? HereMapDesign)?.let { +// state.changeMapDesignType(it) +// } +// } +// is MapboxMapViewState -> { +// (designType as? MapboxMapDesign)?.let { +// state.changeMapDesignType(it) +// } +// } +// is ArcGISMapViewState -> { +// (designType as? ArcGISDesign)?.let { +// state.changeMapDesignType(it) +// } +// } } } @@ -93,118 +100,58 @@ fun MapDesignMapPage( ), title = "Select Map Design...", ) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(8.dp), + // --- ここからドロップダウン --- + var expanded by remember { mutableStateOf(false) } + val items = buttons.value + var selectedLabel by rememberSaveable { mutableStateOf(items.firstOrNull()?.label ?: "") } + + // mapViewState が null の間は選択させない + val enabled = mapViewState.value != null && items.isNotEmpty() + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { if (enabled) expanded = !expanded } ) { - Row { - buttons.value.forEach { - Button( - modifier = Modifier.weight(1f), + TextField( + modifier = Modifier.menuAnchor(), // ExposedDropdownMenuBoxScope + value = selectedLabel, + onValueChange = {}, + readOnly = true, + enabled = enabled, + label = { Text("Map design") }, + ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + items.forEach { item -> + DropdownMenuItem( + text = { Text(item.label) }, onClick = { - callChangeMapDesignType(mapViewState.value!!, it.designType) + selectedLabel = item.label + expanded = false + mapViewState.value?.let { state -> + callChangeMapDesignType(state, item.designType) + } } - ){ - Text(it.label) - } + ) } } } + // --- ここまでドロップダウン --- // Column( // modifier = Modifier.fillMaxSize(), // verticalArrangement = Arrangement.spacedBy(8.dp), // ) { -// when (mapViewState) { -// is GoogleMapViewState -> { -// Row( -// modifier = Modifier.fillMaxWidth(), -// horizontalArrangement = Arrangement.spacedBy(8.dp), -// ) { -// Button( -// modifier = Modifier.weight(1f), -// onClick = { -// (viewModel.mapViewState.value as? MapViewState) -// ?.changeMapDesignType(GoogleMapDesign.Normal) -// }, -// ) { -// Text("Normal") -// } -// Button( -// modifier = Modifier.weight(1f), -// onClick = { -// (viewModel.mapViewState.value as? MapViewState) -// ?.changeMapDesignType(GoogleMapDesign.Satellite) -// }, -// ) { -// Text("Satellite") -// } -// Button( -// modifier = Modifier.weight(1f), -// onClick = { -// (viewModel.mapViewState.value as? MapViewState) -// ?.changeMapDesignType(GoogleMapDesign.Hybrid) -// }, -// ) { -// Text("Hybrid") -// } -// } -// Row( -// modifier = Modifier.fillMaxWidth(), -// horizontalArrangement = Arrangement.spacedBy(8.dp), -// ) { -// Button( -// modifier = Modifier.weight(1f), -// onClick = { -// (viewModel.mapViewState.value as? MapViewState) -// ?.changeMapDesignType(GoogleMapDesign.Terrain) -// }, -// ) { -// Text("Terrain") -// } -// Button( -// modifier = Modifier.weight(1f), -// onClick = { -// (viewModel.mapViewState.value as? MapViewState) -// ?.changeMapDesignType(GoogleMapDesign.None) -// }, -// ) { -// Text("None") -// } -// } -// } -// -// is HereMapViewState -> { -// Row( -// modifier = Modifier.fillMaxWidth(), -// horizontalArrangement = Arrangement.spacedBy(8.dp), -// ) { -// Button( -// modifier = Modifier.weight(1f), -// onClick = { -// (viewModel.mapViewState.value as? MapViewState) -// ?.changeMapDesignType(HereMapDesign.NormalDay) -// }, -// ) { -// Text("NormalDay") -// } -// Button( -// modifier = Modifier.weight(1f), -// onClick = { -// (viewModel.mapViewState.value as? MapViewState) -// ?.changeMapDesignType(HereMapDesign.NormalNigh) -// }, -// ) { -// Text("NormalNigh") -// } -// Button( -// modifier = Modifier.weight(1f), -// onClick = { -// (viewModel.mapViewState.value as? MapViewState) -// ?.changeMapDesignType(HereMapDesign.Satellite) -// }, -// ) { -// Text("Satellite") +// Row { +// buttons.value.forEach { +// Button( +// modifier = Modifier.weight(1f), +// onClick = { +// callChangeMapDesignType(mapViewState.value!!, it.designType) // } +// ){ +// Text(it.label) // } // } // } diff --git a/example-app/src/main/java/com/mapconductor/example/ui/DemoMapPageScaffold.kt b/example-app/src/main/java/com/mapconductor/example/ui/DemoMapPageScaffold.kt index 0e48e220..ce59dcbd 100644 --- a/example-app/src/main/java/com/mapconductor/example/ui/DemoMapPageScaffold.kt +++ b/example-app/src/main/java/com/mapconductor/example/ui/DemoMapPageScaffold.kt @@ -154,21 +154,6 @@ fun DemoMapPageScaffold( ) } } - -// // Message Card -// MessageCard( -// modifier = -// Modifier -// .align(Alignment.BottomStart) -// .padding( -// bottom = paddingValues.calculateBottomPadding() + 16.dp, -// start = paddingValues.calculateStartPadding(LayoutDirection.Ltr) + 16.dp, -// end = paddingValues.calculateEndPadding(LayoutDirection.Ltr) + 16.dp, -// ), -// title = "Messages", -// ) { -// MapViewStatePanel(selectedMapViewState.mapCameraPosition.collectAsState().value) -// } } } } From bcdf42f2d71013ff1edfffd2738ce584850e953b Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Sun, 24 Aug 2025 22:54:28 +0900 Subject: [PATCH 23/29] Refactor: update variable names to match dropdown list usage. --- .../pages/mapDesign/MapDesignMapPage.kt | 126 +++++++----------- .../pages/mapDesign/MapDesignPageViewModel.kt | 55 ++++---- 2 files changed, 74 insertions(+), 107 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt index eb4515fb..42aff920 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt @@ -1,11 +1,7 @@ package com.mapconductor.example.pages.mapDesign -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -16,7 +12,6 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.mapconductor.example.ui.DemoMapPageScaffold import com.mapconductor.example.ui.MessageCard -import androidx.compose.material3.Button import com.mapconductor.arcgis.ArcGISDesign import com.mapconductor.arcgis.ArcGISMapViewState import com.mapconductor.core.map.MapDesignType @@ -33,11 +28,8 @@ import androidx.compose.material3.TextField import androidx.compose.material3.DropdownMenuItem import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable - -data class DesignList( - val text: String, - val design: MapDesignType -) +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.key @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -45,7 +37,7 @@ fun MapDesignMapPage( viewModel: MapDesignPageViewModel = MapDesignPageViewModelImpl(), onToggleSidebar: () -> Unit = {}, ) { - val buttons = viewModel.buttons.collectAsState() + val optionsState = viewModel.options.collectAsState() DemoMapPageScaffold( initCameraPosition = viewModel.initCameraPosition, @@ -60,26 +52,6 @@ fun MapDesignMapPage( is HereMapViewState -> (designType as? HereMapDesign )?.let { state.changeMapDesignType(it) } is MapboxMapViewState -> (designType as? MapboxMapDesign)?.let { state.changeMapDesignType(it) } is ArcGISMapViewState -> (designType as? ArcGISDesign )?.let { state.changeMapDesignType(it) } -// is GoogleMapViewState -> { -// (designType as? GoogleMapDesign)?.let { -// state.changeMapDesignType(it) -// } -// } -// is HereMapViewState -> { -// (designType as? HereMapDesign)?.let { -// state.changeMapDesignType(it) -// } -// } -// is MapboxMapViewState -> { -// (designType as? MapboxMapDesign)?.let { -// state.changeMapDesignType(it) -// } -// } -// is ArcGISMapViewState -> { -// (designType as? ArcGISDesign)?.let { -// state.changeMapDesignType(it) -// } -// } } } @@ -100,62 +72,62 @@ fun MapDesignMapPage( ), title = "Select Map Design...", ) { - // --- ここからドロップダウン --- var expanded by remember { mutableStateOf(false) } - val items = buttons.value - var selectedLabel by rememberSaveable { mutableStateOf(items.firstOrNull()?.label ?: "") } + val items = optionsState.value + + // SDKキー(Google/Here/Mapbox/ArcGISで切替) + val sdkKey = when (mapViewState.value) { + is GoogleMapViewState -> "google" + is HereMapViewState -> "here" + is MapboxMapViewState -> "mapbox" + is ArcGISMapViewState -> "arcgis" + else -> "none" + } + + var selectedLabel by rememberSaveable(sdkKey) { // SDKごとに独立して保存 + mutableStateOf(items.firstOrNull()?.label ?: "") + } + + // ★ options(候補リスト)が変わったら選択と展開状態をリセット + LaunchedEffect(items) { + selectedLabel = items.firstOrNull()?.label ?: "" + expanded = false + } - // mapViewState が null の間は選択させない val enabled = mapViewState.value != null && items.isNotEmpty() - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { if (enabled) expanded = !expanded } - ) { - TextField( - modifier = Modifier.menuAnchor(), // ExposedDropdownMenuBoxScope - value = selectedLabel, - onValueChange = {}, - readOnly = true, - enabled = enabled, - label = { Text("Map design") }, - ) - ExposedDropdownMenu( + key(sdkKey) { // ★ SDK切替でサブツリーごと作り直し + ExposedDropdownMenuBox( expanded = expanded, - onDismissRequest = { expanded = false } + onExpandedChange = { if (enabled) expanded = !expanded } ) { - items.forEach { item -> - DropdownMenuItem( - text = { Text(item.label) }, - onClick = { - selectedLabel = item.label - expanded = false - mapViewState.value?.let { state -> - callChangeMapDesignType(state, item.designType) + TextField( + modifier = Modifier.menuAnchor(), + value = selectedLabel, + onValueChange = {}, + readOnly = true, + enabled = enabled, + label = { Text("Map design") }, + ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + items.forEach { item -> + DropdownMenuItem( + text = { Text(item.label) }, + onClick = { + selectedLabel = item.label + expanded = false + mapViewState.value?.let { state -> + callChangeMapDesignType(state, item.design) + } } - } - ) + ) + } } } } - // --- ここまでドロップダウン --- -// Column( -// modifier = Modifier.fillMaxSize(), -// verticalArrangement = Arrangement.spacedBy(8.dp), -// ) { -// Row { -// buttons.value.forEach { -// Button( -// modifier = Modifier.weight(1f), -// onClick = { -// callChangeMapDesignType(mapViewState.value!!, it.designType) -// } -// ){ -// Text(it.label) -// } -// } -// } -// } } } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt index e368e819..c5b458c2 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt @@ -1,7 +1,6 @@ package com.mapconductor.example.pages.mapDesign import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel @@ -11,11 +10,8 @@ import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapDesignType import com.mapconductor.core.map.MapViewState -import com.mapconductor.core.marker.MarkerState -import com.mapconductor.core.polyline.PolylineState import com.mapconductor.example.toast.ToastMessage import com.mapconductor.googlemaps.GoogleMapDesign -import com.mapconductor.googlemaps.GoogleMapDesignType import com.mapconductor.googlemaps.GoogleMapViewState import com.mapconductor.here.HereMapDesign import com.mapconductor.here.HereMapViewState @@ -23,12 +19,11 @@ import com.mapconductor.mapbox.MapboxMapDesign import com.mapconductor.mapbox.MapboxMapViewState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asStateFlow -data class MapDesignButton( +data class MapDesignOptions( val label: String, - val designType: MapDesignType<*> + val design: MapDesignType<*> ) @@ -39,7 +34,7 @@ interface MapDesignPageViewModel { var design: Int - val buttons: StateFlow> + val options: StateFlow> fun onMapViewChanged(state: MapViewState<*>) fun onMapClick(clicked: GeoPoint) @@ -72,8 +67,8 @@ class MapDesignPageViewModelImpl(): private val _mapViewState = MutableStateFlow?>(null) override val mapViewState: StateFlow?> = _mapViewState.asStateFlow() - private val _buttons: MutableStateFlow> = MutableStateFlow(emptyList()) - override val buttons: StateFlow> = _buttons.asStateFlow() + private val _options: MutableStateFlow> = MutableStateFlow(emptyList()) + override val options: StateFlow> = _options.asStateFlow() override var design by mutableStateOf(1) @@ -81,50 +76,50 @@ class MapDesignPageViewModelImpl(): this._mapViewState.value = state when(state) { is GoogleMapViewState -> { - _buttons.value = listOf( - MapDesignButton( + _options.value = listOf( + MapDesignOptions( label = "Normal", - designType = GoogleMapDesign.Normal, + design = GoogleMapDesign.Normal, ), - MapDesignButton( + MapDesignOptions( label = "Satellite", - designType = GoogleMapDesign.Satellite, + design = GoogleMapDesign.Satellite, ), ) } is HereMapViewState -> { - _buttons.value = listOf( - MapDesignButton( + _options.value = listOf( + MapDesignOptions( label = "NormalDay", - designType = HereMapDesign.NormalDay, + design = HereMapDesign.NormalDay, ), - MapDesignButton( + MapDesignOptions( label = "NormalNigh", - designType = HereMapDesign.NormalNigh, + design = HereMapDesign.NormalNigh, ), ) } is MapboxMapViewState -> { - _buttons.value = listOf( - MapDesignButton( + _options.value = listOf( + MapDesignOptions( label = "standard", - designType = MapboxMapDesign.Standard, + design = MapboxMapDesign.Standard, ), - MapDesignButton( + MapDesignOptions( label = "standard-satellite", - designType = MapboxMapDesign.StandardSatellite, + design = MapboxMapDesign.StandardSatellite, ), ) } is ArcGISMapViewState -> { - _buttons.value = listOf( - MapDesignButton( + _options.value = listOf( + MapDesignOptions( label = "Streets", - designType = ArcGISDesign.Streets, + design = ArcGISDesign.Streets, ), - MapDesignButton( + MapDesignOptions( label = "Imagery", - designType = ArcGISDesign.Imagery, + design = ArcGISDesign.Imagery, ), ) } From d0f01313f33c536b916c70ee77e5b24037b74194 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Sun, 24 Aug 2025 23:22:55 +0900 Subject: [PATCH 24/29] Update MapDesign list handling to show all available designs instead of a sample subset. --- .../pages/mapDesign/MapDesignPageViewModel.kt | 328 +++++++++++++++++- 1 file changed, 326 insertions(+), 2 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt index c5b458c2..65ac27b3 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt @@ -85,6 +85,18 @@ class MapDesignPageViewModelImpl(): label = "Satellite", design = GoogleMapDesign.Satellite, ), + MapDesignOptions( + label = "Hybrid", + design = GoogleMapDesign.Hybrid, + ), + MapDesignOptions( + label = "Terrain", + design = GoogleMapDesign.Terrain, + ), + MapDesignOptions( + label = "None", + design = GoogleMapDesign.None, + ), ) } is HereMapViewState -> { @@ -97,18 +109,98 @@ class MapDesignPageViewModelImpl(): label = "NormalNigh", design = HereMapDesign.NormalNigh, ), + MapDesignOptions( + label = "Satellite", + design = HereMapDesign.Satellite, + ), + MapDesignOptions( + label = "HybridDay", + design = HereMapDesign.HybridDay, + ), + MapDesignOptions( + label = "HybridNight", + design = HereMapDesign.HybridNight, + ), + MapDesignOptions( + label = "LiteDay", + design = HereMapDesign.LiteDay, + ), + MapDesignOptions( + label = "LiteNight", + design = HereMapDesign.LiteNight, + ), + MapDesignOptions( + label = "LiteHybridDay", + design = HereMapDesign.LiteHybridDay, + ), + MapDesignOptions( + label = "LiteHybridNight", + design = HereMapDesign.LiteHybridNight, + ), + MapDesignOptions( + label = "LogisticsDay", + design = HereMapDesign.LogisticsDay, + ), + MapDesignOptions( + label = "LogisticsNight", + design = HereMapDesign.LogisticsNight, + ), + MapDesignOptions( + label = "LogisticsHybridDay", + design = HereMapDesign.LogisticsHybridDay, + ), + MapDesignOptions( + label = "RoadNetworkDay", + design = HereMapDesign.RoadNetworkDay, + ), + MapDesignOptions( + label = "RoadNetworkNight", + design = HereMapDesign.RoadNetworkNight, + ), ) } is MapboxMapViewState -> { _options.value = listOf( MapDesignOptions( - label = "standard", + label = "Standard", design = MapboxMapDesign.Standard, ), MapDesignOptions( - label = "standard-satellite", + label = "StandardSatellite", design = MapboxMapDesign.StandardSatellite, ), + MapDesignOptions( + label = "Streets", + design = MapboxMapDesign.Streets, + ), + MapDesignOptions( + label = "Outdoors", + design = MapboxMapDesign.Outdoors, + ), + MapDesignOptions( + label = "Light", + design = MapboxMapDesign.Light, + ), + MapDesignOptions( + label = "Dark", + design = MapboxMapDesign.Dark, + ), + MapDesignOptions( + label = "Satellite", + design = MapboxMapDesign.Satellite, + ), + MapDesignOptions( + label = "SatelliteStreets", + design = MapboxMapDesign.SatelliteStreets, + ), + MapDesignOptions( + label = "NavigationDay", + design = MapboxMapDesign.NavigationDay, + ), + MapDesignOptions( + label = "NavigationNight", + design = MapboxMapDesign.NavigationNight, + ), ) } is ArcGISMapViewState -> { @@ -121,6 +213,238 @@ class MapDesignPageViewModelImpl(): label = "Imagery", design = ArcGISDesign.Imagery, ), + MapDesignOptions( + label = "ImageryStandard", + design = ArcGISDesign.ImageryStandard, + ), + MapDesignOptions( + label = "ImageryLabels", + design = ArcGISDesign.ImageryLabels, + ), + MapDesignOptions( + label = "LightGray", + design = ArcGISDesign.LightGray, + ), + MapDesignOptions( + label = "LightGrayBase", + design = ArcGISDesign.LightGrayBase, + ), + MapDesignOptions( + label = "LightGrayLabels", + design = ArcGISDesign.LightGrayLabels, + ), + MapDesignOptions( + label = "DarkGray", + design = ArcGISDesign.DarkGray, + ), + MapDesignOptions( + label = "DarkGrayBase", + design = ArcGISDesign.DarkGrayBase, + ), + MapDesignOptions( + label = "DarkGrayLabels", + design = ArcGISDesign.DarkGrayLabels, + ), + MapDesignOptions( + label = "Navigation", + design = ArcGISDesign.Navigation, + ), + MapDesignOptions( + label = "NavigationNight", + design = ArcGISDesign.NavigationNight, + ), + MapDesignOptions( + label = "StreetsNight", + design = ArcGISDesign.StreetsNight, + ), + MapDesignOptions( + label = "StreetsRelief", + design = ArcGISDesign.StreetsRelief, + ), + MapDesignOptions( + label = "Topographic", + design = ArcGISDesign.Topographic, + ), + MapDesignOptions( + label = "Oceans", + design = ArcGISDesign.Oceans, + ), + MapDesignOptions( + label = "OceansBase", + design = ArcGISDesign.OceansBase, + ), + MapDesignOptions( + label = "OceansLabels", + design = ArcGISDesign.OceansLabels, + ), + MapDesignOptions( + label = "Terrain", + design = ArcGISDesign.Terrain, + ), + MapDesignOptions( + label = "TerrainBase", + design = ArcGISDesign.TerrainBase, + ), + MapDesignOptions( + label = "TerrainDetail", + design = ArcGISDesign.TerrainDetail, + ), + MapDesignOptions( + label = "Community", + design = ArcGISDesign.Community, + ), + MapDesignOptions( + label = "ChartedTerritory", + design = ArcGISDesign.ChartedTerritory, + ), + MapDesignOptions( + label = "ColoredPencil", + design = ArcGISDesign.ColoredPencil, + ), + MapDesignOptions( + label = "Nova", + design = ArcGISDesign.Nova, + ), + MapDesignOptions( + label = "ModernAntique", + design = ArcGISDesign.ModernAntique, + ), + MapDesignOptions( + label = "Midcentury", + design = ArcGISDesign.Midcentury, + ), + MapDesignOptions( + label = "Newspaper", + design = ArcGISDesign.Newspaper, + ), + MapDesignOptions( + label = "HillshadeLight", + design = ArcGISDesign.HillshadeLight, + ), + MapDesignOptions( + label = "HillshadeDark", + design = ArcGISDesign.HillshadeDark, + ), + MapDesignOptions( + label = "StreetsReliefBase", + design = ArcGISDesign.StreetsReliefBase, + ), + MapDesignOptions( + label = "TopographicBase", + design = ArcGISDesign.TopographicBase, + ), + MapDesignOptions( + label = "ChartedTerritoryBase", + design = ArcGISDesign.ChartedTerritoryBase, + ), + MapDesignOptions( + label = "ModernAntiqueBase", + design = ArcGISDesign.ModernAntiqueBase, + ), + MapDesignOptions( + label = "HumanGeography", + design = ArcGISDesign.HumanGeography, + ), + MapDesignOptions( + label = "HumanGeographyBase", + design = ArcGISDesign.HumanGeographyBase, + ), + MapDesignOptions( + label = "HumanGeographyDetail", + design = ArcGISDesign.HumanGeographyDetail, + ), + MapDesignOptions( + label = "HumanGeographyLabels", + design = ArcGISDesign.HumanGeographyLabels, + ), + MapDesignOptions( + label = "HumanGeographyDark", + design = ArcGISDesign.HumanGeographyDark, + ), + MapDesignOptions( + label = "HumanGeographyDarkBase", + design = ArcGISDesign.HumanGeographyDarkBase, + ), + MapDesignOptions( + label = "HumanGeographyDarkDetail", + design = ArcGISDesign.HumanGeographyDarkDetail, + ), + MapDesignOptions( + label = "HumanGeographyDarkLabels", + design = ArcGISDesign.HumanGeographyDarkLabels, + ), + MapDesignOptions( + label = "Outdoor", + design = ArcGISDesign.Outdoor, + ), + MapDesignOptions( + label = "OsmStandard", + design = ArcGISDesign.OsmStandard, + ), + MapDesignOptions( + label = "OsmStandardRelief", + design = ArcGISDesign.OsmStandardRelief, + ), + MapDesignOptions( + label = "OsmStandardReliefBase", + design = ArcGISDesign.OsmStandardReliefBase, + ), + MapDesignOptions( + label = "OsmStreets", + design = ArcGISDesign.OsmStreets, + ), + MapDesignOptions( + label = "OsmStreetsRelief", + design = ArcGISDesign.OsmStreetsRelief, + ), + MapDesignOptions( + label = "OsmLightGray", + design = ArcGISDesign.OsmLightGray, + ), + MapDesignOptions( + label = "OsmLightGrayBase", + design = ArcGISDesign.OsmLightGrayBase, + ), + MapDesignOptions( + label = "OsmLightGrayLabels", + design = ArcGISDesign.OsmLightGrayLabels, + ), + MapDesignOptions( + label = "OsmDarkGray", + design = ArcGISDesign.OsmDarkGray, + ), + MapDesignOptions( + label = "OsmDarkGrayBase", + design = ArcGISDesign.OsmDarkGrayBase, + ), + MapDesignOptions( + label = "OsmDarkGrayLabels", + design = ArcGISDesign.OsmDarkGrayLabels, + ), + MapDesignOptions( + label = "OsmStreetsReliefBase", + design = ArcGISDesign.OsmStreetsReliefBase, + ), + MapDesignOptions( + label = "OsmBlueprint", + design = ArcGISDesign.OsmBlueprint, + ), + MapDesignOptions( + label = "OsmHybrid", + design = ArcGISDesign.OsmHybrid, + ), + MapDesignOptions( + label = "OsmHybridDetail", + design = ArcGISDesign.OsmHybridDetail, + ), + MapDesignOptions( + label = "OsmNavigation", + design = ArcGISDesign.OsmNavigation, + ), + MapDesignOptions( + label = "OsmNavigationDark", + design = ArcGISDesign.OsmNavigationDark, + ), ) } } From 5075d88b2ef7787888a35a40ee103fc79eb95194 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Mon, 25 Aug 2025 00:42:06 +0900 Subject: [PATCH 25/29] Refactor: move design data definition out of onMapViewChanged() and decouple from list display. --- .../pages/mapDesign/MapDesignPageViewModel.kt | 377 +----------------- .../arcgis/ArcGISMapDesignType.kt | 72 +++- .../googlemaps/GoogleMapDesign.kt | 8 + .../com/mapconductor/here/HereMapDesign.kt | 17 + .../mapconductor/mapbox/MapboxMapDesign.kt | 20 + 5 files changed, 127 insertions(+), 367 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt index 65ac27b3..ed968f7f 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt @@ -26,7 +26,6 @@ data class MapDesignOptions( val design: MapDesignType<*> ) - interface MapDesignPageViewModel { val initCameraPosition: MapCameraPosition val mapViewState: StateFlow?> @@ -76,376 +75,24 @@ class MapDesignPageViewModelImpl(): this._mapViewState.value = state when(state) { is GoogleMapViewState -> { - _options.value = listOf( - MapDesignOptions( - label = "Normal", - design = GoogleMapDesign.Normal, - ), - MapDesignOptions( - label = "Satellite", - design = GoogleMapDesign.Satellite, - ), - MapDesignOptions( - label = "Hybrid", - design = GoogleMapDesign.Hybrid, - ), - MapDesignOptions( - label = "Terrain", - design = GoogleMapDesign.Terrain, - ), - MapDesignOptions( - label = "None", - design = GoogleMapDesign.None, - ), - ) + _options.value = GoogleMapDesign.all().map { (label, design) -> + MapDesignOptions(label = label, design = design) + } } is HereMapViewState -> { - _options.value = listOf( - MapDesignOptions( - label = "NormalDay", - design = HereMapDesign.NormalDay, - ), - MapDesignOptions( - label = "NormalNigh", - design = HereMapDesign.NormalNigh, - ), - MapDesignOptions( - label = "Satellite", - design = HereMapDesign.Satellite, - ), - MapDesignOptions( - label = "HybridDay", - design = HereMapDesign.HybridDay, - ), - MapDesignOptions( - label = "HybridNight", - design = HereMapDesign.HybridNight, - ), - MapDesignOptions( - label = "LiteDay", - design = HereMapDesign.LiteDay, - ), - MapDesignOptions( - label = "LiteNight", - design = HereMapDesign.LiteNight, - ), - MapDesignOptions( - label = "LiteHybridDay", - design = HereMapDesign.LiteHybridDay, - ), - MapDesignOptions( - label = "LiteHybridNight", - design = HereMapDesign.LiteHybridNight, - ), - MapDesignOptions( - label = "LogisticsDay", - design = HereMapDesign.LogisticsDay, - ), - MapDesignOptions( - label = "LogisticsNight", - design = HereMapDesign.LogisticsNight, - ), - MapDesignOptions( - label = "LogisticsHybridDay", - design = HereMapDesign.LogisticsHybridDay, - ), - MapDesignOptions( - label = "RoadNetworkDay", - design = HereMapDesign.RoadNetworkDay, - ), - MapDesignOptions( - label = "RoadNetworkNight", - design = HereMapDesign.RoadNetworkNight, - ), - ) + _options.value = HereMapDesign.all().map { (label, design) -> + MapDesignOptions(label = label, design = design) + } } is MapboxMapViewState -> { - _options.value = listOf( - MapDesignOptions( - label = "Standard", - design = MapboxMapDesign.Standard, - ), - MapDesignOptions( - label = "StandardSatellite", - design = MapboxMapDesign.StandardSatellite, - ), - MapDesignOptions( - label = "Streets", - design = MapboxMapDesign.Streets, - ), - MapDesignOptions( - label = "Outdoors", - design = MapboxMapDesign.Outdoors, - ), - MapDesignOptions( - label = "Light", - design = MapboxMapDesign.Light, - ), - MapDesignOptions( - label = "Dark", - design = MapboxMapDesign.Dark, - ), - MapDesignOptions( - label = "Satellite", - design = MapboxMapDesign.Satellite, - ), - MapDesignOptions( - label = "SatelliteStreets", - design = MapboxMapDesign.SatelliteStreets, - ), - MapDesignOptions( - label = "NavigationDay", - design = MapboxMapDesign.NavigationDay, - ), - MapDesignOptions( - label = "NavigationNight", - design = MapboxMapDesign.NavigationNight, - ), - ) + _options.value = MapboxMapDesign.all().map { (label, design) -> + MapDesignOptions(label = label, design = design) + } } is ArcGISMapViewState -> { - _options.value = listOf( - MapDesignOptions( - label = "Streets", - design = ArcGISDesign.Streets, - ), - MapDesignOptions( - label = "Imagery", - design = ArcGISDesign.Imagery, - ), - MapDesignOptions( - label = "ImageryStandard", - design = ArcGISDesign.ImageryStandard, - ), - MapDesignOptions( - label = "ImageryLabels", - design = ArcGISDesign.ImageryLabels, - ), - MapDesignOptions( - label = "LightGray", - design = ArcGISDesign.LightGray, - ), - MapDesignOptions( - label = "LightGrayBase", - design = ArcGISDesign.LightGrayBase, - ), - MapDesignOptions( - label = "LightGrayLabels", - design = ArcGISDesign.LightGrayLabels, - ), - MapDesignOptions( - label = "DarkGray", - design = ArcGISDesign.DarkGray, - ), - MapDesignOptions( - label = "DarkGrayBase", - design = ArcGISDesign.DarkGrayBase, - ), - MapDesignOptions( - label = "DarkGrayLabels", - design = ArcGISDesign.DarkGrayLabels, - ), - MapDesignOptions( - label = "Navigation", - design = ArcGISDesign.Navigation, - ), - MapDesignOptions( - label = "NavigationNight", - design = ArcGISDesign.NavigationNight, - ), - MapDesignOptions( - label = "StreetsNight", - design = ArcGISDesign.StreetsNight, - ), - MapDesignOptions( - label = "StreetsRelief", - design = ArcGISDesign.StreetsRelief, - ), - MapDesignOptions( - label = "Topographic", - design = ArcGISDesign.Topographic, - ), - MapDesignOptions( - label = "Oceans", - design = ArcGISDesign.Oceans, - ), - MapDesignOptions( - label = "OceansBase", - design = ArcGISDesign.OceansBase, - ), - MapDesignOptions( - label = "OceansLabels", - design = ArcGISDesign.OceansLabels, - ), - MapDesignOptions( - label = "Terrain", - design = ArcGISDesign.Terrain, - ), - MapDesignOptions( - label = "TerrainBase", - design = ArcGISDesign.TerrainBase, - ), - MapDesignOptions( - label = "TerrainDetail", - design = ArcGISDesign.TerrainDetail, - ), - MapDesignOptions( - label = "Community", - design = ArcGISDesign.Community, - ), - MapDesignOptions( - label = "ChartedTerritory", - design = ArcGISDesign.ChartedTerritory, - ), - MapDesignOptions( - label = "ColoredPencil", - design = ArcGISDesign.ColoredPencil, - ), - MapDesignOptions( - label = "Nova", - design = ArcGISDesign.Nova, - ), - MapDesignOptions( - label = "ModernAntique", - design = ArcGISDesign.ModernAntique, - ), - MapDesignOptions( - label = "Midcentury", - design = ArcGISDesign.Midcentury, - ), - MapDesignOptions( - label = "Newspaper", - design = ArcGISDesign.Newspaper, - ), - MapDesignOptions( - label = "HillshadeLight", - design = ArcGISDesign.HillshadeLight, - ), - MapDesignOptions( - label = "HillshadeDark", - design = ArcGISDesign.HillshadeDark, - ), - MapDesignOptions( - label = "StreetsReliefBase", - design = ArcGISDesign.StreetsReliefBase, - ), - MapDesignOptions( - label = "TopographicBase", - design = ArcGISDesign.TopographicBase, - ), - MapDesignOptions( - label = "ChartedTerritoryBase", - design = ArcGISDesign.ChartedTerritoryBase, - ), - MapDesignOptions( - label = "ModernAntiqueBase", - design = ArcGISDesign.ModernAntiqueBase, - ), - MapDesignOptions( - label = "HumanGeography", - design = ArcGISDesign.HumanGeography, - ), - MapDesignOptions( - label = "HumanGeographyBase", - design = ArcGISDesign.HumanGeographyBase, - ), - MapDesignOptions( - label = "HumanGeographyDetail", - design = ArcGISDesign.HumanGeographyDetail, - ), - MapDesignOptions( - label = "HumanGeographyLabels", - design = ArcGISDesign.HumanGeographyLabels, - ), - MapDesignOptions( - label = "HumanGeographyDark", - design = ArcGISDesign.HumanGeographyDark, - ), - MapDesignOptions( - label = "HumanGeographyDarkBase", - design = ArcGISDesign.HumanGeographyDarkBase, - ), - MapDesignOptions( - label = "HumanGeographyDarkDetail", - design = ArcGISDesign.HumanGeographyDarkDetail, - ), - MapDesignOptions( - label = "HumanGeographyDarkLabels", - design = ArcGISDesign.HumanGeographyDarkLabels, - ), - MapDesignOptions( - label = "Outdoor", - design = ArcGISDesign.Outdoor, - ), - MapDesignOptions( - label = "OsmStandard", - design = ArcGISDesign.OsmStandard, - ), - MapDesignOptions( - label = "OsmStandardRelief", - design = ArcGISDesign.OsmStandardRelief, - ), - MapDesignOptions( - label = "OsmStandardReliefBase", - design = ArcGISDesign.OsmStandardReliefBase, - ), - MapDesignOptions( - label = "OsmStreets", - design = ArcGISDesign.OsmStreets, - ), - MapDesignOptions( - label = "OsmStreetsRelief", - design = ArcGISDesign.OsmStreetsRelief, - ), - MapDesignOptions( - label = "OsmLightGray", - design = ArcGISDesign.OsmLightGray, - ), - MapDesignOptions( - label = "OsmLightGrayBase", - design = ArcGISDesign.OsmLightGrayBase, - ), - MapDesignOptions( - label = "OsmLightGrayLabels", - design = ArcGISDesign.OsmLightGrayLabels, - ), - MapDesignOptions( - label = "OsmDarkGray", - design = ArcGISDesign.OsmDarkGray, - ), - MapDesignOptions( - label = "OsmDarkGrayBase", - design = ArcGISDesign.OsmDarkGrayBase, - ), - MapDesignOptions( - label = "OsmDarkGrayLabels", - design = ArcGISDesign.OsmDarkGrayLabels, - ), - MapDesignOptions( - label = "OsmStreetsReliefBase", - design = ArcGISDesign.OsmStreetsReliefBase, - ), - MapDesignOptions( - label = "OsmBlueprint", - design = ArcGISDesign.OsmBlueprint, - ), - MapDesignOptions( - label = "OsmHybrid", - design = ArcGISDesign.OsmHybrid, - ), - MapDesignOptions( - label = "OsmHybridDetail", - design = ArcGISDesign.OsmHybridDetail, - ), - MapDesignOptions( - label = "OsmNavigation", - design = ArcGISDesign.OsmNavigation, - ), - MapDesignOptions( - label = "OsmNavigationDark", - design = ArcGISDesign.OsmNavigationDark, - ), - ) + _options.value = ArcGISDesign.all().map { (label, design) -> + MapDesignOptions(label = label, design = design) + } } } } diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt index 8909c017..24124195 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt @@ -30,7 +30,7 @@ data class ArcGISDesign( val Navigation = ArcGISDesign("arc_gis_navigation") val NavigationNight = ArcGISDesign("arc_gis_navigation_night") val StreetsNight = ArcGISDesign("arc_gis_streets_night") - val StreetsRelief = ArcGISDesign("arc_gis_Streets_relief") + val StreetsRelief = ArcGISDesign("arc_gis_Streets_relief") // "arc_gis_streets_relief"の誤記? val Topographic = ArcGISDesign("arc_gis_topographic") val Oceans = ArcGISDesign("arc_gis_oceans") val OceansBase = ArcGISDesign("arc_gis_oceans_base") @@ -53,7 +53,7 @@ data class ArcGISDesign( val ModernAntiqueBase = ArcGISDesign("arc_gis_modern_antique_base") val HumanGeography = ArcGISDesign("arc_gis_human_geography") val HumanGeographyBase = ArcGISDesign("arc_gis_human_geography_base") - val HumanGeographyDetail = ArcGISDesign("arc_gis_humanGeography_detail") + val HumanGeographyDetail = ArcGISDesign("arc_gis_humanGeography_detail") // "arc_gis_human_geography_detail"の誤記? val HumanGeographyLabels = ArcGISDesign("arc_gis_human_geography_labels") val HumanGeographyDark = ArcGISDesign("arc_gis_human_geography_dark") val HumanGeographyDarkBase = ArcGISDesign("arc_gis_human_geography_dark_base") @@ -210,5 +210,73 @@ data class ArcGISDesign( OsmNavigationDark.id -> BasemapStyle.OsmNavigationDark else -> throw Throwable("unknown design id: \"$designType.id\"") } + + fun all(): List> = listOf( + // ArcGIS ベースマップ + "Streets" to Streets, + "Imagery" to Imagery, + "ImageryStandard" to ImageryStandard, + "ImageryLabels" to ImageryLabels, + "LightGray" to LightGray, + "LightGrayBase" to LightGrayBase, + "LightGrayLabels" to LightGrayLabels, + "DarkGray" to DarkGray, + "DarkGrayBase" to DarkGrayBase, + "DarkGrayLabels" to DarkGrayLabels, + "Navigation" to Navigation, + "NavigationNight" to NavigationNight, + "StreetsNight" to StreetsNight, + "StreetsRelief" to StreetsRelief, + "Topographic" to Topographic, + "Oceans" to Oceans, + "OceansBase" to OceansBase, + "OceansLabels" to OceansLabels, + "Terrain" to Terrain, + "TerrainBase" to TerrainBase, + "TerrainDetail" to TerrainDetail, + "Community" to Community, + "ChartedTerritory" to ChartedTerritory, + "ColoredPencil" to ColoredPencil, + "Nova" to Nova, + "ModernAntique" to ModernAntique, + "Midcentury" to Midcentury, + "Newspaper" to Newspaper, + "HillshadeLight" to HillshadeLight, + "HillshadeDark" to HillshadeDark, + "StreetsReliefBase" to StreetsReliefBase, + "TopographicBase" to TopographicBase, + "ChartedTerritoryBase" to ChartedTerritoryBase, + "ModernAntiqueBase" to ModernAntiqueBase, + + // Human Geography + "HumanGeography" to HumanGeography, + "HumanGeographyBase" to HumanGeographyBase, + "HumanGeographyDetail" to HumanGeographyDetail, + "HumanGeographyLabels" to HumanGeographyLabels, + "HumanGeographyDark" to HumanGeographyDark, + "HumanGeographyDarkBase" to HumanGeographyDarkBase, + "HumanGeographyDarkDetail" to HumanGeographyDarkDetail, + "HumanGeographyDarkLabels" to HumanGeographyDarkLabels, + + // Outdoor / OSM 系 + "Outdoor" to Outdoor, + "OsmStandard" to OsmStandard, + "OsmStandardRelief" to OsmStandardRelief, + "OsmStandardReliefBase" to OsmStandardReliefBase, + "OsmStreets" to OsmStreets, + "OsmStreetsRelief" to OsmStreetsRelief, + "OsmLightGray" to OsmLightGray, + "OsmLightGrayBase" to OsmLightGrayBase, + "OsmLightGrayLabels" to OsmLightGrayLabels, + "OsmDarkGray" to OsmDarkGray, + "OsmDarkGrayBase" to OsmDarkGrayBase, + "OsmDarkGrayLabels" to OsmDarkGrayLabels, + "OsmStreetsReliefBase" to OsmStreetsReliefBase, + "OsmBlueprint" to OsmBlueprint, + "OsmHybrid" to OsmHybrid, + "OsmHybridDetail" to OsmHybridDetail, + "OsmNavigation" to OsmNavigation, + "OsmNavigationDark" to OsmNavigationDark, + ) } } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt index 873a17cf..98b162af 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt @@ -33,5 +33,13 @@ sealed class GoogleMapDesign( Terrain.id -> Terrain else -> None } + + fun all(): List> = listOf( + "Normal" to Normal, + "Satellite" to Satellite, + "Hybrid" to Hybrid, + "Terrain" to Terrain, + "None" to None, + ) } } diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt index da707ea6..57f852ef 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt @@ -76,5 +76,22 @@ sealed class HereMapDesign( RoadNetworkNight.id -> RoadNetworkNight else -> throw IllegalArgumentException("Unsupported MapScene : $id") } + + fun all(): List> = listOf( + "NormalDay" to NormalDay, + "NormalNigh" to NormalNigh, + "Satellite" to Satellite, + "HybridDay" to HybridDay, + "HybridNight" to HybridNight, + "LiteDay" to LiteDay, + "LiteNight" to LiteNight, + "LiteHybridDay" to LiteHybridDay, + "LiteHybridNight" to LiteHybridNight, + "LogisticsDay" to LogisticsDay, + "LogisticsNight" to LogisticsNight, + "LogisticsHybridDay" to LogisticsHybridDay, + "RoadNetworkDay" to RoadNetworkDay, + "RoadNetworkNight" to RoadNetworkNight, + ) } } diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt index e802769c..0419a800 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt @@ -50,5 +50,25 @@ sealed class MapboxMapDesign( NavigationNight.id -> NavigationNight else -> Custom(layerId) } + + fun all(): List> = listOf( + "Standard" to Standard, + "StandardSatellite" to StandardSatellite, + "Streets" to Streets, + "Outdoors" to Outdoors, + "Light" to Light, + "Dark" to Dark, + "Satellite" to Satellite, + "SatelliteStreets" to SatelliteStreets, + "NavigationDay" to NavigationDay, + "NavigationNight" to NavigationNight, + ) + + // カスタムスタイルも併せてリスト化したい場合はこっちを使うらしい + fun allWithCustom( + customs: List> + ): List> = all() + customs.map { + (label, styleId) -> label to Custom(styleId) + } } } From cf9e7014f6773a52fcd9915f6e3f10b5109943d4 Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Tue, 26 Aug 2025 23:46:32 +0900 Subject: [PATCH 26/29] Refactored code based on review feedback.(1st) --- example-app/build.gradle.kts | 1 - .../pages/mapDesign/MapDesignMapComponent.kt | 2 -- .../pages/mapDesign/MapDesignMapPage.kt | 25 ++++++++++++++++--- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/example-app/build.gradle.kts b/example-app/build.gradle.kts index 3ef09501..bc1fe2b5 100644 --- a/example-app/build.gradle.kts +++ b/example-app/build.gradle.kts @@ -104,7 +104,6 @@ dependencies { implementation(project(":mapconductor-for-mapbox")) implementation(project(":mapconductor-for-arcgis")) implementation(libs.androidx.vectordrawable) - implementation(libs.androidx.room.runtime.android) testImplementation(libs.junit) testImplementation(libs.androidx.core) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt index a2b052da..abfc814c 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt @@ -10,13 +10,11 @@ import com.mapconductor.example.MapViewContainer fun MapDesignMapComponent( mapViewState: MapViewState<*>?, modifier: Modifier = Modifier, - onMapClick: OnMapEventHandler = {}, ) { mapViewState?.let { state -> MapViewContainer( modifier = modifier, state = state, - onMapClick = onMapClick, ) {} } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt index 42aff920..7c8787c9 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt @@ -26,17 +26,37 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.TextField import androidx.compose.material3.DropdownMenuItem -import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.compose.viewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun MapDesignMapPage( - viewModel: MapDesignPageViewModel = MapDesignPageViewModelImpl(), onToggleSidebar: () -> Unit = {}, ) { + val viewModel: MapDesignPageViewModel = + viewModel( + factory = + object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(MapDesignPageViewModelImpl::class.java)) { + @Suppress("UNCHECKED_CAST") + return MapDesignPageViewModelImpl() as T + } + throw IllegalArgumentException("Unknown VieModel class") + } + } + ) + val optionsState = viewModel.options.collectAsState() DemoMapPageScaffold( @@ -57,7 +77,6 @@ fun MapDesignMapPage( MapDesignMapComponent( mapViewState = mapViewState.value, - onMapClick = viewModel::onMapClick, ) // Message Card From c7a734c7ba825db3e5817b5048b4c4d197fe342c Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Wed, 27 Aug 2025 00:40:32 +0900 Subject: [PATCH 27/29] Refactored code based on review feedback.(2nd) --- .../pages/mapDesign/MapDesignPageViewModel.kt | 117 ++++++++++++++++-- .../arcgis/ArcGISMapDesignType.kt | 72 +---------- .../googlemaps/GoogleMapDesign.kt | 8 -- .../com/mapconductor/here/HereMapDesign.kt | 17 --- .../mapconductor/mapbox/MapboxMapDesign.kt | 20 --- 5 files changed, 107 insertions(+), 127 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt index ed968f7f..5b5a4ecc 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt @@ -75,24 +75,16 @@ class MapDesignPageViewModelImpl(): this._mapViewState.value = state when(state) { is GoogleMapViewState -> { - _options.value = GoogleMapDesign.all().map { (label, design) -> - MapDesignOptions(label = label, design = design) - } + _options.value = googleMapDesigns } is HereMapViewState -> { - _options.value = HereMapDesign.all().map { (label, design) -> - MapDesignOptions(label = label, design = design) - } + _options.value = hereMapDesigns } is MapboxMapViewState -> { - _options.value = MapboxMapDesign.all().map { (label, design) -> - MapDesignOptions(label = label, design = design) - } + _options.value = mapboxMapDesigns } is ArcGISMapViewState -> { - _options.value = ArcGISDesign.all().map { (label, design) -> - MapDesignOptions(label = label, design = design) - } + _options.value = arcGISMapDesigns } } } @@ -108,4 +100,105 @@ class MapDesignPageViewModelImpl(): override fun removeToast(toastMessage: ToastMessage) { this._messages.value = this._messages.value.filter { it != toastMessage } } + + private val googleMapDesigns = listOf( + MapDesignOptions(label = "Normal", design = GoogleMapDesign.Normal), + MapDesignOptions(label = "Satellite", design = GoogleMapDesign.Satellite), + MapDesignOptions(label = "Hybrid", design = GoogleMapDesign.Hybrid), + MapDesignOptions(label = "Terrain", design = GoogleMapDesign.Terrain), + MapDesignOptions(label = "None", design = GoogleMapDesign.None), + ) + + private val hereMapDesigns = listOf( + MapDesignOptions(label = "NormalDay", design = HereMapDesign.NormalDay), + MapDesignOptions(label = "NormalNigh", design = HereMapDesign.NormalNigh), + MapDesignOptions(label = "Satellite", design = HereMapDesign.Satellite), + MapDesignOptions(label = "HybridDay", design = HereMapDesign.HybridDay), + MapDesignOptions(label = "HybridNight", design = HereMapDesign.HybridNight), + MapDesignOptions(label = "LiteDay", design = HereMapDesign.LiteDay), + MapDesignOptions(label = "LiteNight", design = HereMapDesign.LiteNight), + MapDesignOptions(label = "LiteHybridDay", design = HereMapDesign.LiteHybridDay), + MapDesignOptions(label = "LiteHybridNight", design = HereMapDesign.LiteHybridNight), + MapDesignOptions(label = "LogisticsDay", design = HereMapDesign.LogisticsDay), + MapDesignOptions(label = "LogisticsNight", design = HereMapDesign.LogisticsNight), + MapDesignOptions(label = "LogisticsHybridDay", design = HereMapDesign.LogisticsHybridDay), + MapDesignOptions(label = "RoadNetworkDay", design = HereMapDesign.RoadNetworkDay), + MapDesignOptions(label = "RoadNetworkNight", design = HereMapDesign.RoadNetworkNight), + ) + + private val mapboxMapDesigns = listOf( + MapDesignOptions(label = "Standard", design = MapboxMapDesign.Standard), + MapDesignOptions(label = "StandardSatellite", design = MapboxMapDesign.StandardSatellite), + MapDesignOptions(label = "Streets", design = MapboxMapDesign.Streets), + MapDesignOptions(label = "Outdoors", design = MapboxMapDesign.Outdoors), + MapDesignOptions(label = "Light", design = MapboxMapDesign.Light), + MapDesignOptions(label = "Dark", design = MapboxMapDesign.Dark), + MapDesignOptions(label = "Satellite", design = MapboxMapDesign.Satellite), + MapDesignOptions(label = "SatelliteStreets", design = MapboxMapDesign.SatelliteStreets), + MapDesignOptions(label = "NavigationDay", design = MapboxMapDesign.NavigationDay), + MapDesignOptions(label = "NavigationNight", design = MapboxMapDesign.NavigationNight), + ) + + private val arcGISMapDesigns = listOf( + MapDesignOptions(label = "Streets", design = ArcGISDesign.Companion.Streets), + MapDesignOptions(label = "Imagery", design = ArcGISDesign.Companion.Imagery), + MapDesignOptions(label = "ImageryStandard", design = ArcGISDesign.Companion.ImageryStandard), + MapDesignOptions(label = "ImageryLabels", design = ArcGISDesign.Companion.ImageryLabels), + MapDesignOptions(label = "LightGray", design = ArcGISDesign.Companion.LightGray), + MapDesignOptions(label = "LightGrayBase", design = ArcGISDesign.Companion.LightGrayBase), + MapDesignOptions(label = "LightGrayLabels", design = ArcGISDesign.Companion.LightGrayLabels), + MapDesignOptions(label = "DarkGray", design = ArcGISDesign.Companion.DarkGray), + MapDesignOptions(label = "DarkGrayBase", design = ArcGISDesign.Companion.DarkGrayBase), + MapDesignOptions(label = "DarkGrayLabels", design = ArcGISDesign.Companion.DarkGrayLabels), + MapDesignOptions(label = "Navigation", design = ArcGISDesign.Companion.Navigation), + MapDesignOptions(label = "NavigationNight", design = ArcGISDesign.Companion.NavigationNight), + MapDesignOptions(label = "StreetsNight", design = ArcGISDesign.Companion.StreetsNight), + MapDesignOptions(label = "StreetsRelief", design = ArcGISDesign.Companion.StreetsRelief), + MapDesignOptions(label = "Topographic", design = ArcGISDesign.Companion.Topographic), + MapDesignOptions(label = "Oceans", design = ArcGISDesign.Companion.Oceans), + MapDesignOptions(label = "OceansBase", design = ArcGISDesign.Companion.OceansBase), + MapDesignOptions(label = "OceansLabels", design = ArcGISDesign.Companion.OceansLabels), + MapDesignOptions(label = "Terrain", design = ArcGISDesign.Companion.Terrain), + MapDesignOptions(label = "TerrainBase", design = ArcGISDesign.Companion.TerrainBase), + MapDesignOptions(label = "TerrainDetail", design = ArcGISDesign.Companion.TerrainDetail), + MapDesignOptions(label = "Community", design = ArcGISDesign.Companion.Community), + MapDesignOptions(label = "ChartedTerritory", design = ArcGISDesign.Companion.ChartedTerritory), + MapDesignOptions(label = "ColoredPencil", design = ArcGISDesign.Companion.ColoredPencil), + MapDesignOptions(label = "Nova", design = ArcGISDesign.Companion.Nova), + MapDesignOptions(label = "ModernAntique", design = ArcGISDesign.Companion.ModernAntique), + MapDesignOptions(label = "Midcentury", design = ArcGISDesign.Companion.Midcentury), + MapDesignOptions(label = "Newspaper", design = ArcGISDesign.Companion.Newspaper), + MapDesignOptions(label = "HillshadeLight", design = ArcGISDesign.Companion.HillshadeLight), + MapDesignOptions(label = "HillshadeDark", design = ArcGISDesign.Companion.HillshadeDark), + MapDesignOptions(label = "StreetsReliefBase", design = ArcGISDesign.Companion.StreetsReliefBase), + MapDesignOptions(label = "TopographicBase", design = ArcGISDesign.Companion.TopographicBase), + MapDesignOptions(label = "ChartedTerritoryBase", design = ArcGISDesign.Companion.ChartedTerritoryBase), + MapDesignOptions(label = "ModernAntiqueBase", design = ArcGISDesign.Companion.ModernAntiqueBase), + MapDesignOptions(label = "HumanGeography", design = ArcGISDesign.Companion.HumanGeography), + MapDesignOptions(label = "HumanGeographyBase", design = ArcGISDesign.Companion.HumanGeographyBase), + MapDesignOptions(label = "HumanGeographyDetail", design = ArcGISDesign.Companion.HumanGeographyDetail), + MapDesignOptions(label = "HumanGeographyLabels", design = ArcGISDesign.Companion.HumanGeographyLabels), + MapDesignOptions(label = "HumanGeographyDark", design = ArcGISDesign.Companion.HumanGeographyDark), + MapDesignOptions(label = "HumanGeographyDarkBase", design = ArcGISDesign.Companion.HumanGeographyDarkBase), + MapDesignOptions(label = "HumanGeographyDarkDetail", design = ArcGISDesign.Companion.HumanGeographyDarkDetail), + MapDesignOptions(label = "HumanGeographyDarkLabels", design = ArcGISDesign.Companion.HumanGeographyDarkLabels), + MapDesignOptions(label = "Outdoor", design = ArcGISDesign.Companion.Outdoor), + MapDesignOptions(label = "OsmStandard", design = ArcGISDesign.Companion.OsmStandard), + MapDesignOptions(label = "OsmStandardRelief", design = ArcGISDesign.Companion.OsmStandardRelief), + MapDesignOptions(label = "OsmStandardReliefBase", design = ArcGISDesign.Companion.OsmStandardReliefBase), + MapDesignOptions(label = "OsmStreets", design = ArcGISDesign.Companion.OsmStreets), + MapDesignOptions(label = "OsmStreetsRelief", design = ArcGISDesign.Companion.OsmStreetsRelief), + MapDesignOptions(label = "OsmLightGray", design = ArcGISDesign.Companion.OsmLightGray), + MapDesignOptions(label = "OsmLightGrayBase", design = ArcGISDesign.Companion.OsmLightGrayBase), + MapDesignOptions(label = "OsmLightGrayLabels", design = ArcGISDesign.Companion.OsmLightGrayLabels), + MapDesignOptions(label = "OsmDarkGray", design = ArcGISDesign.Companion.OsmDarkGray), + MapDesignOptions(label = "OsmDarkGrayBase", design = ArcGISDesign.Companion.OsmDarkGrayBase), + MapDesignOptions(label = "OsmDarkGrayLabels", design = ArcGISDesign.Companion.OsmDarkGrayLabels), + MapDesignOptions(label = "OsmStreetsReliefBase", design = ArcGISDesign.Companion.OsmStreetsReliefBase), + MapDesignOptions(label = "OsmBlueprint", design = ArcGISDesign.Companion.OsmBlueprint), + MapDesignOptions(label = "OsmHybrid", design = ArcGISDesign.Companion.OsmHybrid), + MapDesignOptions(label = "OsmHybridDetail", design = ArcGISDesign.Companion.OsmHybridDetail), + MapDesignOptions(label = "OsmNavigation", design = ArcGISDesign.Companion.OsmNavigation), + MapDesignOptions(label = "OsmNavigationDark", design = ArcGISDesign.Companion.OsmNavigationDark), + ) } diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt index 24124195..a5b079eb 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt @@ -30,7 +30,7 @@ data class ArcGISDesign( val Navigation = ArcGISDesign("arc_gis_navigation") val NavigationNight = ArcGISDesign("arc_gis_navigation_night") val StreetsNight = ArcGISDesign("arc_gis_streets_night") - val StreetsRelief = ArcGISDesign("arc_gis_Streets_relief") // "arc_gis_streets_relief"の誤記? + val StreetsRelief = ArcGISDesign("arc_gis_streets_relief") val Topographic = ArcGISDesign("arc_gis_topographic") val Oceans = ArcGISDesign("arc_gis_oceans") val OceansBase = ArcGISDesign("arc_gis_oceans_base") @@ -53,7 +53,7 @@ data class ArcGISDesign( val ModernAntiqueBase = ArcGISDesign("arc_gis_modern_antique_base") val HumanGeography = ArcGISDesign("arc_gis_human_geography") val HumanGeographyBase = ArcGISDesign("arc_gis_human_geography_base") - val HumanGeographyDetail = ArcGISDesign("arc_gis_humanGeography_detail") // "arc_gis_human_geography_detail"の誤記? + val HumanGeographyDetail = ArcGISDesign("arc_gis_human_geography_detail") val HumanGeographyLabels = ArcGISDesign("arc_gis_human_geography_labels") val HumanGeographyDark = ArcGISDesign("arc_gis_human_geography_dark") val HumanGeographyDarkBase = ArcGISDesign("arc_gis_human_geography_dark_base") @@ -210,73 +210,5 @@ data class ArcGISDesign( OsmNavigationDark.id -> BasemapStyle.OsmNavigationDark else -> throw Throwable("unknown design id: \"$designType.id\"") } - - fun all(): List> = listOf( - // ArcGIS ベースマップ - "Streets" to Streets, - "Imagery" to Imagery, - "ImageryStandard" to ImageryStandard, - "ImageryLabels" to ImageryLabels, - "LightGray" to LightGray, - "LightGrayBase" to LightGrayBase, - "LightGrayLabels" to LightGrayLabels, - "DarkGray" to DarkGray, - "DarkGrayBase" to DarkGrayBase, - "DarkGrayLabels" to DarkGrayLabels, - "Navigation" to Navigation, - "NavigationNight" to NavigationNight, - "StreetsNight" to StreetsNight, - "StreetsRelief" to StreetsRelief, - "Topographic" to Topographic, - "Oceans" to Oceans, - "OceansBase" to OceansBase, - "OceansLabels" to OceansLabels, - "Terrain" to Terrain, - "TerrainBase" to TerrainBase, - "TerrainDetail" to TerrainDetail, - "Community" to Community, - "ChartedTerritory" to ChartedTerritory, - "ColoredPencil" to ColoredPencil, - "Nova" to Nova, - "ModernAntique" to ModernAntique, - "Midcentury" to Midcentury, - "Newspaper" to Newspaper, - "HillshadeLight" to HillshadeLight, - "HillshadeDark" to HillshadeDark, - "StreetsReliefBase" to StreetsReliefBase, - "TopographicBase" to TopographicBase, - "ChartedTerritoryBase" to ChartedTerritoryBase, - "ModernAntiqueBase" to ModernAntiqueBase, - - // Human Geography - "HumanGeography" to HumanGeography, - "HumanGeographyBase" to HumanGeographyBase, - "HumanGeographyDetail" to HumanGeographyDetail, - "HumanGeographyLabels" to HumanGeographyLabels, - "HumanGeographyDark" to HumanGeographyDark, - "HumanGeographyDarkBase" to HumanGeographyDarkBase, - "HumanGeographyDarkDetail" to HumanGeographyDarkDetail, - "HumanGeographyDarkLabels" to HumanGeographyDarkLabels, - - // Outdoor / OSM 系 - "Outdoor" to Outdoor, - "OsmStandard" to OsmStandard, - "OsmStandardRelief" to OsmStandardRelief, - "OsmStandardReliefBase" to OsmStandardReliefBase, - "OsmStreets" to OsmStreets, - "OsmStreetsRelief" to OsmStreetsRelief, - "OsmLightGray" to OsmLightGray, - "OsmLightGrayBase" to OsmLightGrayBase, - "OsmLightGrayLabels" to OsmLightGrayLabels, - "OsmDarkGray" to OsmDarkGray, - "OsmDarkGrayBase" to OsmDarkGrayBase, - "OsmDarkGrayLabels" to OsmDarkGrayLabels, - "OsmStreetsReliefBase" to OsmStreetsReliefBase, - "OsmBlueprint" to OsmBlueprint, - "OsmHybrid" to OsmHybrid, - "OsmHybridDetail" to OsmHybridDetail, - "OsmNavigation" to OsmNavigation, - "OsmNavigationDark" to OsmNavigationDark, - ) } } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt index 98b162af..873a17cf 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt @@ -33,13 +33,5 @@ sealed class GoogleMapDesign( Terrain.id -> Terrain else -> None } - - fun all(): List> = listOf( - "Normal" to Normal, - "Satellite" to Satellite, - "Hybrid" to Hybrid, - "Terrain" to Terrain, - "None" to None, - ) } } diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt index 57f852ef..da707ea6 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt @@ -76,22 +76,5 @@ sealed class HereMapDesign( RoadNetworkNight.id -> RoadNetworkNight else -> throw IllegalArgumentException("Unsupported MapScene : $id") } - - fun all(): List> = listOf( - "NormalDay" to NormalDay, - "NormalNigh" to NormalNigh, - "Satellite" to Satellite, - "HybridDay" to HybridDay, - "HybridNight" to HybridNight, - "LiteDay" to LiteDay, - "LiteNight" to LiteNight, - "LiteHybridDay" to LiteHybridDay, - "LiteHybridNight" to LiteHybridNight, - "LogisticsDay" to LogisticsDay, - "LogisticsNight" to LogisticsNight, - "LogisticsHybridDay" to LogisticsHybridDay, - "RoadNetworkDay" to RoadNetworkDay, - "RoadNetworkNight" to RoadNetworkNight, - ) } } diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt index 0419a800..e802769c 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt @@ -50,25 +50,5 @@ sealed class MapboxMapDesign( NavigationNight.id -> NavigationNight else -> Custom(layerId) } - - fun all(): List> = listOf( - "Standard" to Standard, - "StandardSatellite" to StandardSatellite, - "Streets" to Streets, - "Outdoors" to Outdoors, - "Light" to Light, - "Dark" to Dark, - "Satellite" to Satellite, - "SatelliteStreets" to SatelliteStreets, - "NavigationDay" to NavigationDay, - "NavigationNight" to NavigationNight, - ) - - // カスタムスタイルも併せてリスト化したい場合はこっちを使うらしい - fun allWithCustom( - customs: List> - ): List> = all() + customs.map { - (label, styleId) -> label to Custom(styleId) - } } } From 0fe31fa2dcf1e97e0911e8f0b6afd0723c501d8f Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Wed, 27 Aug 2025 22:45:51 +0900 Subject: [PATCH 28/29] Refactored code based on review feedback.(3rd) --- .../example/pages/mapDesign/MapDesignPageViewModel.kt | 4 ---- gradle/libs.versions.toml | 3 --- 2 files changed, 7 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt index 5b5a4ecc..ee16d146 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt @@ -31,8 +31,6 @@ interface MapDesignPageViewModel { val mapViewState: StateFlow?> val messages: StateFlow> - var design: Int - val options: StateFlow> fun onMapViewChanged(state: MapViewState<*>) @@ -69,8 +67,6 @@ class MapDesignPageViewModelImpl(): private val _options: MutableStateFlow> = MutableStateFlow(emptyList()) override val options: StateFlow> = _options.asStateFlow() - override var design by mutableStateOf(1) - override fun onMapViewChanged(state: MapViewState<*>) { this._mapViewState.value = state when(state) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ecb653b2..c675bf42 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,6 @@ secretsGradlePlugin = "2.0.1" arcgisMapsKotlin = "200.7.0" uiTooling = "1.8.1" vectordrawable = "1.2.0" -roomRuntimeAndroid = "2.7.2" [libraries] # Android 基本 @@ -77,9 +76,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secretsGradlePlugin" } -ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" } androidx-vectordrawable = { group = "androidx.vectordrawable", name = "vectordrawable", version.ref = "vectordrawable" } -androidx-room-runtime-android = { group = "androidx.room", name = "room-runtime-android", version.ref = "roomRuntimeAndroid" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 29a74736a8a7e33cc7cbe1a564c18a1c8e4b55eb Mon Sep 17 00:00:00 2001 From: KouTsukigase Date: Wed, 27 Aug 2025 22:57:57 +0900 Subject: [PATCH 29/29] Refactored code based on review feedback.(4th) --- .../pages/mapDesign/MapDesignMapComponent.kt | 1 - .../pages/mapDesign/MapDesignMapPage.kt | 78 ++++--- .../pages/mapDesign/MapDesignPageViewModel.kt | 215 +++++++++--------- 3 files changed, 151 insertions(+), 143 deletions(-) diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt index abfc814c..46746739 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapComponent.kt @@ -3,7 +3,6 @@ package com.mapconductor.example.pages.mapDesign import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.mapconductor.core.map.MapViewState -import com.mapconductor.core.map.OnMapEventHandler import com.mapconductor.example.MapViewContainer @Composable diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt index 7c8787c9..472456b5 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignMapPage.kt @@ -3,46 +3,44 @@ package com.mapconductor.example.pages.mapDesign import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.padding +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.Text +import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp -import com.mapconductor.example.ui.DemoMapPageScaffold -import com.mapconductor.example.ui.MessageCard +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.compose.viewModel import com.mapconductor.arcgis.ArcGISDesign import com.mapconductor.arcgis.ArcGISMapViewState import com.mapconductor.core.map.MapDesignType import com.mapconductor.core.map.MapViewState +import com.mapconductor.example.ui.DemoMapPageScaffold +import com.mapconductor.example.ui.MessageCard import com.mapconductor.googlemaps.GoogleMapDesign import com.mapconductor.googlemaps.GoogleMapViewState import com.mapconductor.here.HereMapDesign import com.mapconductor.here.HereMapViewState import com.mapconductor.mapbox.MapboxMapDesign import com.mapconductor.mapbox.MapboxMapViewState -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.TextField -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewmodel.compose.viewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun MapDesignMapPage( - onToggleSidebar: () -> Unit = {}, -) { +fun MapDesignMapPage(onToggleSidebar: () -> Unit = {}) { val viewModel: MapDesignPageViewModel = viewModel( factory = @@ -54,7 +52,7 @@ fun MapDesignMapPage( } throw IllegalArgumentException("Unknown VieModel class") } - } + }, ) val optionsState = viewModel.options.collectAsState() @@ -66,12 +64,15 @@ fun MapDesignMapPage( ) { paddingValues -> val mapViewState = viewModel.mapViewState.collectAsState() - fun callChangeMapDesignType(state: MapViewState<*>,designType: MapDesignType<*>) { + fun callChangeMapDesignType( + state: MapViewState<*>, + designType: MapDesignType<*>, + ) { when (state) { is GoogleMapViewState -> (designType as? GoogleMapDesign)?.let { state.changeMapDesignType(it) } - is HereMapViewState -> (designType as? HereMapDesign )?.let { state.changeMapDesignType(it) } + is HereMapViewState -> (designType as? HereMapDesign)?.let { state.changeMapDesignType(it) } is MapboxMapViewState -> (designType as? MapboxMapDesign)?.let { state.changeMapDesignType(it) } - is ArcGISMapViewState -> (designType as? ArcGISDesign )?.let { state.changeMapDesignType(it) } + is ArcGISMapViewState -> (designType as? ArcGISDesign)?.let { state.changeMapDesignType(it) } } } @@ -95,15 +96,17 @@ fun MapDesignMapPage( val items = optionsState.value // SDKキー(Google/Here/Mapbox/ArcGISで切替) - val sdkKey = when (mapViewState.value) { - is GoogleMapViewState -> "google" - is HereMapViewState -> "here" - is MapboxMapViewState -> "mapbox" - is ArcGISMapViewState -> "arcgis" - else -> "none" - } + val sdkKey = + when (mapViewState.value) { + is GoogleMapViewState -> "google" + is HereMapViewState -> "here" + is MapboxMapViewState -> "mapbox" + is ArcGISMapViewState -> "arcgis" + else -> "none" + } - var selectedLabel by rememberSaveable(sdkKey) { // SDKごとに独立して保存 + var selectedLabel by rememberSaveable(sdkKey) { + // SDKごとに独立して保存 mutableStateOf(items.firstOrNull()?.label ?: "") } @@ -115,13 +118,14 @@ fun MapDesignMapPage( val enabled = mapViewState.value != null && items.isNotEmpty() - key(sdkKey) { // ★ SDK切替でサブツリーごと作り直し + key(sdkKey) { + // ★ SDK切替でサブツリーごと作り直し ExposedDropdownMenuBox( expanded = expanded, - onExpandedChange = { if (enabled) expanded = !expanded } + onExpandedChange = { if (enabled) expanded = !expanded }, ) { TextField( - modifier = Modifier.menuAnchor(), + modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable, enabled), value = selectedLabel, onValueChange = {}, readOnly = true, @@ -130,7 +134,7 @@ fun MapDesignMapPage( ) ExposedDropdownMenu( expanded = expanded, - onDismissRequest = { expanded = false } + onDismissRequest = { expanded = false }, ) { items.forEach { item -> DropdownMenuItem( @@ -141,7 +145,7 @@ fun MapDesignMapPage( mapViewState.value?.let { state -> callChangeMapDesignType(state, item.design) } - } + }, ) } } diff --git a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt index ee16d146..a0f5ed07 100644 --- a/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/pages/mapDesign/MapDesignPageViewModel.kt @@ -1,7 +1,6 @@ package com.mapconductor.example.pages.mapDesign import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import com.mapconductor.arcgis.ArcGISDesign @@ -23,7 +22,7 @@ import kotlinx.coroutines.flow.asStateFlow data class MapDesignOptions( val label: String, - val design: MapDesignType<*> + val design: MapDesignType<*>, ) interface MapDesignPageViewModel { @@ -32,6 +31,7 @@ interface MapDesignPageViewModel { val messages: StateFlow> val options: StateFlow> + fun onMapViewChanged(state: MapViewState<*>) fun onMapClick(clicked: GeoPoint) @@ -41,10 +41,9 @@ interface MapDesignPageViewModel { fun removeToast(toastMessage: ToastMessage) } -class MapDesignPageViewModelImpl(): +class MapDesignPageViewModelImpl : ViewModel(), MapDesignPageViewModel { - override val initCameraPosition = MapCameraPosition( position = @@ -69,7 +68,7 @@ class MapDesignPageViewModelImpl(): override fun onMapViewChanged(state: MapViewState<*>) { this._mapViewState.value = state - when(state) { + when (state) { is GoogleMapViewState -> { _options.value = googleMapDesigns } @@ -97,104 +96,110 @@ class MapDesignPageViewModelImpl(): this._messages.value = this._messages.value.filter { it != toastMessage } } - private val googleMapDesigns = listOf( - MapDesignOptions(label = "Normal", design = GoogleMapDesign.Normal), - MapDesignOptions(label = "Satellite", design = GoogleMapDesign.Satellite), - MapDesignOptions(label = "Hybrid", design = GoogleMapDesign.Hybrid), - MapDesignOptions(label = "Terrain", design = GoogleMapDesign.Terrain), - MapDesignOptions(label = "None", design = GoogleMapDesign.None), - ) - - private val hereMapDesigns = listOf( - MapDesignOptions(label = "NormalDay", design = HereMapDesign.NormalDay), - MapDesignOptions(label = "NormalNigh", design = HereMapDesign.NormalNigh), - MapDesignOptions(label = "Satellite", design = HereMapDesign.Satellite), - MapDesignOptions(label = "HybridDay", design = HereMapDesign.HybridDay), - MapDesignOptions(label = "HybridNight", design = HereMapDesign.HybridNight), - MapDesignOptions(label = "LiteDay", design = HereMapDesign.LiteDay), - MapDesignOptions(label = "LiteNight", design = HereMapDesign.LiteNight), - MapDesignOptions(label = "LiteHybridDay", design = HereMapDesign.LiteHybridDay), - MapDesignOptions(label = "LiteHybridNight", design = HereMapDesign.LiteHybridNight), - MapDesignOptions(label = "LogisticsDay", design = HereMapDesign.LogisticsDay), - MapDesignOptions(label = "LogisticsNight", design = HereMapDesign.LogisticsNight), - MapDesignOptions(label = "LogisticsHybridDay", design = HereMapDesign.LogisticsHybridDay), - MapDesignOptions(label = "RoadNetworkDay", design = HereMapDesign.RoadNetworkDay), - MapDesignOptions(label = "RoadNetworkNight", design = HereMapDesign.RoadNetworkNight), - ) - - private val mapboxMapDesigns = listOf( - MapDesignOptions(label = "Standard", design = MapboxMapDesign.Standard), - MapDesignOptions(label = "StandardSatellite", design = MapboxMapDesign.StandardSatellite), - MapDesignOptions(label = "Streets", design = MapboxMapDesign.Streets), - MapDesignOptions(label = "Outdoors", design = MapboxMapDesign.Outdoors), - MapDesignOptions(label = "Light", design = MapboxMapDesign.Light), - MapDesignOptions(label = "Dark", design = MapboxMapDesign.Dark), - MapDesignOptions(label = "Satellite", design = MapboxMapDesign.Satellite), - MapDesignOptions(label = "SatelliteStreets", design = MapboxMapDesign.SatelliteStreets), - MapDesignOptions(label = "NavigationDay", design = MapboxMapDesign.NavigationDay), - MapDesignOptions(label = "NavigationNight", design = MapboxMapDesign.NavigationNight), - ) - - private val arcGISMapDesigns = listOf( - MapDesignOptions(label = "Streets", design = ArcGISDesign.Companion.Streets), - MapDesignOptions(label = "Imagery", design = ArcGISDesign.Companion.Imagery), - MapDesignOptions(label = "ImageryStandard", design = ArcGISDesign.Companion.ImageryStandard), - MapDesignOptions(label = "ImageryLabels", design = ArcGISDesign.Companion.ImageryLabels), - MapDesignOptions(label = "LightGray", design = ArcGISDesign.Companion.LightGray), - MapDesignOptions(label = "LightGrayBase", design = ArcGISDesign.Companion.LightGrayBase), - MapDesignOptions(label = "LightGrayLabels", design = ArcGISDesign.Companion.LightGrayLabels), - MapDesignOptions(label = "DarkGray", design = ArcGISDesign.Companion.DarkGray), - MapDesignOptions(label = "DarkGrayBase", design = ArcGISDesign.Companion.DarkGrayBase), - MapDesignOptions(label = "DarkGrayLabels", design = ArcGISDesign.Companion.DarkGrayLabels), - MapDesignOptions(label = "Navigation", design = ArcGISDesign.Companion.Navigation), - MapDesignOptions(label = "NavigationNight", design = ArcGISDesign.Companion.NavigationNight), - MapDesignOptions(label = "StreetsNight", design = ArcGISDesign.Companion.StreetsNight), - MapDesignOptions(label = "StreetsRelief", design = ArcGISDesign.Companion.StreetsRelief), - MapDesignOptions(label = "Topographic", design = ArcGISDesign.Companion.Topographic), - MapDesignOptions(label = "Oceans", design = ArcGISDesign.Companion.Oceans), - MapDesignOptions(label = "OceansBase", design = ArcGISDesign.Companion.OceansBase), - MapDesignOptions(label = "OceansLabels", design = ArcGISDesign.Companion.OceansLabels), - MapDesignOptions(label = "Terrain", design = ArcGISDesign.Companion.Terrain), - MapDesignOptions(label = "TerrainBase", design = ArcGISDesign.Companion.TerrainBase), - MapDesignOptions(label = "TerrainDetail", design = ArcGISDesign.Companion.TerrainDetail), - MapDesignOptions(label = "Community", design = ArcGISDesign.Companion.Community), - MapDesignOptions(label = "ChartedTerritory", design = ArcGISDesign.Companion.ChartedTerritory), - MapDesignOptions(label = "ColoredPencil", design = ArcGISDesign.Companion.ColoredPencil), - MapDesignOptions(label = "Nova", design = ArcGISDesign.Companion.Nova), - MapDesignOptions(label = "ModernAntique", design = ArcGISDesign.Companion.ModernAntique), - MapDesignOptions(label = "Midcentury", design = ArcGISDesign.Companion.Midcentury), - MapDesignOptions(label = "Newspaper", design = ArcGISDesign.Companion.Newspaper), - MapDesignOptions(label = "HillshadeLight", design = ArcGISDesign.Companion.HillshadeLight), - MapDesignOptions(label = "HillshadeDark", design = ArcGISDesign.Companion.HillshadeDark), - MapDesignOptions(label = "StreetsReliefBase", design = ArcGISDesign.Companion.StreetsReliefBase), - MapDesignOptions(label = "TopographicBase", design = ArcGISDesign.Companion.TopographicBase), - MapDesignOptions(label = "ChartedTerritoryBase", design = ArcGISDesign.Companion.ChartedTerritoryBase), - MapDesignOptions(label = "ModernAntiqueBase", design = ArcGISDesign.Companion.ModernAntiqueBase), - MapDesignOptions(label = "HumanGeography", design = ArcGISDesign.Companion.HumanGeography), - MapDesignOptions(label = "HumanGeographyBase", design = ArcGISDesign.Companion.HumanGeographyBase), - MapDesignOptions(label = "HumanGeographyDetail", design = ArcGISDesign.Companion.HumanGeographyDetail), - MapDesignOptions(label = "HumanGeographyLabels", design = ArcGISDesign.Companion.HumanGeographyLabels), - MapDesignOptions(label = "HumanGeographyDark", design = ArcGISDesign.Companion.HumanGeographyDark), - MapDesignOptions(label = "HumanGeographyDarkBase", design = ArcGISDesign.Companion.HumanGeographyDarkBase), - MapDesignOptions(label = "HumanGeographyDarkDetail", design = ArcGISDesign.Companion.HumanGeographyDarkDetail), - MapDesignOptions(label = "HumanGeographyDarkLabels", design = ArcGISDesign.Companion.HumanGeographyDarkLabels), - MapDesignOptions(label = "Outdoor", design = ArcGISDesign.Companion.Outdoor), - MapDesignOptions(label = "OsmStandard", design = ArcGISDesign.Companion.OsmStandard), - MapDesignOptions(label = "OsmStandardRelief", design = ArcGISDesign.Companion.OsmStandardRelief), - MapDesignOptions(label = "OsmStandardReliefBase", design = ArcGISDesign.Companion.OsmStandardReliefBase), - MapDesignOptions(label = "OsmStreets", design = ArcGISDesign.Companion.OsmStreets), - MapDesignOptions(label = "OsmStreetsRelief", design = ArcGISDesign.Companion.OsmStreetsRelief), - MapDesignOptions(label = "OsmLightGray", design = ArcGISDesign.Companion.OsmLightGray), - MapDesignOptions(label = "OsmLightGrayBase", design = ArcGISDesign.Companion.OsmLightGrayBase), - MapDesignOptions(label = "OsmLightGrayLabels", design = ArcGISDesign.Companion.OsmLightGrayLabels), - MapDesignOptions(label = "OsmDarkGray", design = ArcGISDesign.Companion.OsmDarkGray), - MapDesignOptions(label = "OsmDarkGrayBase", design = ArcGISDesign.Companion.OsmDarkGrayBase), - MapDesignOptions(label = "OsmDarkGrayLabels", design = ArcGISDesign.Companion.OsmDarkGrayLabels), - MapDesignOptions(label = "OsmStreetsReliefBase", design = ArcGISDesign.Companion.OsmStreetsReliefBase), - MapDesignOptions(label = "OsmBlueprint", design = ArcGISDesign.Companion.OsmBlueprint), - MapDesignOptions(label = "OsmHybrid", design = ArcGISDesign.Companion.OsmHybrid), - MapDesignOptions(label = "OsmHybridDetail", design = ArcGISDesign.Companion.OsmHybridDetail), - MapDesignOptions(label = "OsmNavigation", design = ArcGISDesign.Companion.OsmNavigation), - MapDesignOptions(label = "OsmNavigationDark", design = ArcGISDesign.Companion.OsmNavigationDark), - ) + private val googleMapDesigns = + listOf( + MapDesignOptions(label = "Normal", design = GoogleMapDesign.Normal), + MapDesignOptions(label = "Satellite", design = GoogleMapDesign.Satellite), + MapDesignOptions(label = "Hybrid", design = GoogleMapDesign.Hybrid), + MapDesignOptions(label = "Terrain", design = GoogleMapDesign.Terrain), + MapDesignOptions(label = "None", design = GoogleMapDesign.None), + ) + + private val hereMapDesigns = + listOf( + MapDesignOptions(label = "NormalDay", design = HereMapDesign.NormalDay), + MapDesignOptions(label = "NormalNigh", design = HereMapDesign.NormalNigh), + MapDesignOptions(label = "Satellite", design = HereMapDesign.Satellite), + MapDesignOptions(label = "HybridDay", design = HereMapDesign.HybridDay), + MapDesignOptions(label = "HybridNight", design = HereMapDesign.HybridNight), + MapDesignOptions(label = "LiteDay", design = HereMapDesign.LiteDay), + MapDesignOptions(label = "LiteNight", design = HereMapDesign.LiteNight), + MapDesignOptions(label = "LiteHybridDay", design = HereMapDesign.LiteHybridDay), + MapDesignOptions(label = "LiteHybridNight", design = HereMapDesign.LiteHybridNight), + MapDesignOptions(label = "LogisticsDay", design = HereMapDesign.LogisticsDay), + MapDesignOptions(label = "LogisticsNight", design = HereMapDesign.LogisticsNight), + MapDesignOptions(label = "LogisticsHybridDay", design = HereMapDesign.LogisticsHybridDay), + MapDesignOptions(label = "RoadNetworkDay", design = HereMapDesign.RoadNetworkDay), + MapDesignOptions(label = "RoadNetworkNight", design = HereMapDesign.RoadNetworkNight), + ) + + private val mapboxMapDesigns = + listOf( + MapDesignOptions(label = "Standard", design = MapboxMapDesign.Standard), + MapDesignOptions(label = "StandardSatellite", design = MapboxMapDesign.StandardSatellite), + MapDesignOptions(label = "Streets", design = MapboxMapDesign.Streets), + MapDesignOptions(label = "Outdoors", design = MapboxMapDesign.Outdoors), + MapDesignOptions(label = "Light", design = MapboxMapDesign.Light), + MapDesignOptions(label = "Dark", design = MapboxMapDesign.Dark), + MapDesignOptions(label = "Satellite", design = MapboxMapDesign.Satellite), + MapDesignOptions(label = "SatelliteStreets", design = MapboxMapDesign.SatelliteStreets), + MapDesignOptions(label = "NavigationDay", design = MapboxMapDesign.NavigationDay), + MapDesignOptions(label = "NavigationNight", design = MapboxMapDesign.NavigationNight), + ) + + private val arcGISMapDesigns = + listOf( + MapDesignOptions(label = "Streets", design = ArcGISDesign.Companion.Streets), + MapDesignOptions(label = "Imagery", design = ArcGISDesign.Companion.Imagery), + MapDesignOptions(label = "ImageryStandard", design = ArcGISDesign.Companion.ImageryStandard), + MapDesignOptions(label = "ImageryLabels", design = ArcGISDesign.Companion.ImageryLabels), + MapDesignOptions(label = "LightGray", design = ArcGISDesign.Companion.LightGray), + MapDesignOptions(label = "LightGrayBase", design = ArcGISDesign.Companion.LightGrayBase), + MapDesignOptions(label = "LightGrayLabels", design = ArcGISDesign.Companion.LightGrayLabels), + MapDesignOptions(label = "DarkGray", design = ArcGISDesign.Companion.DarkGray), + MapDesignOptions(label = "DarkGrayBase", design = ArcGISDesign.Companion.DarkGrayBase), + MapDesignOptions(label = "DarkGrayLabels", design = ArcGISDesign.Companion.DarkGrayLabels), + MapDesignOptions(label = "Navigation", design = ArcGISDesign.Companion.Navigation), + MapDesignOptions(label = "NavigationNight", design = ArcGISDesign.Companion.NavigationNight), + MapDesignOptions(label = "StreetsNight", design = ArcGISDesign.Companion.StreetsNight), + MapDesignOptions(label = "StreetsRelief", design = ArcGISDesign.Companion.StreetsRelief), + MapDesignOptions(label = "Topographic", design = ArcGISDesign.Companion.Topographic), + MapDesignOptions(label = "Oceans", design = ArcGISDesign.Companion.Oceans), + MapDesignOptions(label = "OceansBase", design = ArcGISDesign.Companion.OceansBase), + MapDesignOptions(label = "OceansLabels", design = ArcGISDesign.Companion.OceansLabels), + MapDesignOptions(label = "Terrain", design = ArcGISDesign.Companion.Terrain), + MapDesignOptions(label = "TerrainBase", design = ArcGISDesign.Companion.TerrainBase), + MapDesignOptions(label = "TerrainDetail", design = ArcGISDesign.Companion.TerrainDetail), + MapDesignOptions(label = "Community", design = ArcGISDesign.Companion.Community), + MapDesignOptions(label = "ChartedTerritory", design = ArcGISDesign.Companion.ChartedTerritory), + MapDesignOptions(label = "ColoredPencil", design = ArcGISDesign.Companion.ColoredPencil), + MapDesignOptions(label = "Nova", design = ArcGISDesign.Companion.Nova), + MapDesignOptions(label = "ModernAntique", design = ArcGISDesign.Companion.ModernAntique), + MapDesignOptions(label = "Midcentury", design = ArcGISDesign.Companion.Midcentury), + MapDesignOptions(label = "Newspaper", design = ArcGISDesign.Companion.Newspaper), + MapDesignOptions(label = "HillshadeLight", design = ArcGISDesign.Companion.HillshadeLight), + MapDesignOptions(label = "HillshadeDark", design = ArcGISDesign.Companion.HillshadeDark), + MapDesignOptions(label = "StreetsReliefBase", design = ArcGISDesign.Companion.StreetsReliefBase), + MapDesignOptions(label = "TopographicBase", design = ArcGISDesign.Companion.TopographicBase), + MapDesignOptions(label = "ChartedTerritoryBase", design = ArcGISDesign.Companion.ChartedTerritoryBase), + MapDesignOptions(label = "ModernAntiqueBase", design = ArcGISDesign.Companion.ModernAntiqueBase), + MapDesignOptions(label = "HumanGeography", design = ArcGISDesign.Companion.HumanGeography), + MapDesignOptions(label = "HumanGeographyBase", design = ArcGISDesign.Companion.HumanGeographyBase), + MapDesignOptions(label = "HumanGeographyDetail", design = ArcGISDesign.Companion.HumanGeographyDetail), + MapDesignOptions(label = "HumanGeographyLabels", design = ArcGISDesign.Companion.HumanGeographyLabels), + MapDesignOptions(label = "HumanGeographyDark", design = ArcGISDesign.Companion.HumanGeographyDark), + MapDesignOptions(label = "HumanGeographyDarkBase", design = ArcGISDesign.Companion.HumanGeographyDarkBase), + MapDesignOptions(label = "HumanGeographyDarkDetail", design = + ArcGISDesign.Companion.HumanGeographyDarkDetail), + MapDesignOptions(label = "HumanGeographyDarkLabels", design = + ArcGISDesign.Companion.HumanGeographyDarkLabels), + MapDesignOptions(label = "Outdoor", design = ArcGISDesign.Companion.Outdoor), + MapDesignOptions(label = "OsmStandard", design = ArcGISDesign.Companion.OsmStandard), + MapDesignOptions(label = "OsmStandardRelief", design = ArcGISDesign.Companion.OsmStandardRelief), + MapDesignOptions(label = "OsmStandardReliefBase", design = ArcGISDesign.Companion.OsmStandardReliefBase), + MapDesignOptions(label = "OsmStreets", design = ArcGISDesign.Companion.OsmStreets), + MapDesignOptions(label = "OsmStreetsRelief", design = ArcGISDesign.Companion.OsmStreetsRelief), + MapDesignOptions(label = "OsmLightGray", design = ArcGISDesign.Companion.OsmLightGray), + MapDesignOptions(label = "OsmLightGrayBase", design = ArcGISDesign.Companion.OsmLightGrayBase), + MapDesignOptions(label = "OsmLightGrayLabels", design = ArcGISDesign.Companion.OsmLightGrayLabels), + MapDesignOptions(label = "OsmDarkGray", design = ArcGISDesign.Companion.OsmDarkGray), + MapDesignOptions(label = "OsmDarkGrayBase", design = ArcGISDesign.Companion.OsmDarkGrayBase), + MapDesignOptions(label = "OsmDarkGrayLabels", design = ArcGISDesign.Companion.OsmDarkGrayLabels), + MapDesignOptions(label = "OsmStreetsReliefBase", design = ArcGISDesign.Companion.OsmStreetsReliefBase), + MapDesignOptions(label = "OsmBlueprint", design = ArcGISDesign.Companion.OsmBlueprint), + MapDesignOptions(label = "OsmHybrid", design = ArcGISDesign.Companion.OsmHybrid), + MapDesignOptions(label = "OsmHybridDetail", design = ArcGISDesign.Companion.OsmHybridDetail), + MapDesignOptions(label = "OsmNavigation", design = ArcGISDesign.Companion.OsmNavigation), + MapDesignOptions(label = "OsmNavigationDark", design = ArcGISDesign.Companion.OsmNavigationDark), + ) }