diff --git a/README.es-419.md b/README.es-419.md index 7b2150a0..32da9af5 100644 --- a/README.es-419.md +++ b/README.es-419.md @@ -28,7 +28,7 @@ Con MapConductor puedes: * Construir funciones de mapas independientes del proveedor, como mapas de calor y agrupamiento de marcadores * Mantener tu código de aplicación enfocado en el comportamiento del mapa, no en las diferencias específicas de cada SDK -![](./images/es-419-comic-why-map-conductor.jpg) +![](./docs/src/assets/top-page/es-419-comic-why-map-conductor.jpg) --- @@ -160,7 +160,7 @@ fun SimpleMapScreen(modifier: Modifier) { Este ejemplo usa MapLibre Maps, pero los objetos del mapa están escritos usando conceptos de MapConductor. La misma lógica de superposiciones se puede adaptar a otros proveedores compatibles. -![](./images/simple-map-screen.png) +![](./docs/src/assets/top-page/simple-map-screen.png) --- diff --git a/README.ja.md b/README.ja.md index cc131fcd..a1fc0d65 100644 --- a/README.ja.md +++ b/README.ja.md @@ -28,7 +28,7 @@ MapConductorを使うと、以下のことができます: * プロバイダーに依存しないヒートマップやマーカークラスタリングなどのマップ機能を構築する * アプリケーションコードをSDK固有の差異ではなくマップの挙動に集中させる -![](./images/japanese-comic-why-map-conductor.jpg) +![](./docs/src/assets/top-page/japanese-comic-why-map-conductor.jpg) --- @@ -160,7 +160,7 @@ fun SimpleMapScreen(modifier: Modifier) { この例ではMapLibre Mapsを使用していますが、マップオブジェクトはMapConductorの概念を使って書かれています。同じオーバーレイのロジックを他のサポートされているプロバイダーに適用できます。 -![](./images/simple-map-screen.png) +![](./docs/src/assets/top-page/simple-map-screen.png) --- diff --git a/README.md b/README.md index 84adef5d..7c16f8b5 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ With MapConductor, you can: * Build provider-independent map features such as heatmaps and marker clustering * Keep your application code focused on map behavior, not SDK-specific differences -![](./images/english-comic-why-map-conductor.jpg) +![](./docs/src/assets/top-page/english-comic-why-map-conductor.jpg) --- @@ -160,7 +160,7 @@ fun SimpleMapScreen(modifier: Modifier) { This example uses MapLibre Maps, but the map objects are written using MapConductor concepts. The same overlay logic can be adapted to other supported providers. -![](./images/simple-map-screen.png) +![](./docs/src/assets/top-page/simple-map-screen.png) --- diff --git a/android-for-arcgis b/android-for-arcgis index cfd6143c..9d3d6be2 160000 --- a/android-for-arcgis +++ b/android-for-arcgis @@ -1 +1 @@ -Subproject commit cfd6143cb339f7f61dd4cd27e4e8a080596e6716 +Subproject commit 9d3d6be2985903827b2f035c04096116418a7170 diff --git a/android-for-googlemaps b/android-for-googlemaps index 557108ce..4d13211e 160000 --- a/android-for-googlemaps +++ b/android-for-googlemaps @@ -1 +1 @@ -Subproject commit 557108ceda2a604185875378f0dc924d842469b7 +Subproject commit 4d13211e12622bd6faedf1cdf84c001349e81070 diff --git a/android-for-here b/android-for-here index 4dea3d7f..9b612224 160000 --- a/android-for-here +++ b/android-for-here @@ -1 +1 @@ -Subproject commit 4dea3d7f2b1f231818af1a0a0fed766b2db7958c +Subproject commit 9b612224144dd425d1f85e43ab36f7533b616eed diff --git a/android-for-mapbox b/android-for-mapbox index eaa70434..4da7f784 160000 --- a/android-for-mapbox +++ b/android-for-mapbox @@ -1 +1 @@ -Subproject commit eaa704344acbe3772ed99f9a279624768dff9f5c +Subproject commit 4da7f7842758062422f1b8f85a93c5c5e5b17fec diff --git a/android-for-maplibre b/android-for-maplibre index 411d63db..54fc47c4 160000 --- a/android-for-maplibre +++ b/android-for-maplibre @@ -1 +1 @@ -Subproject commit 411d63db8a1b664bac5ea7372ef8e003fca64a48 +Subproject commit 54fc47c4c9d8127dc6e2d3e962f9cb6a4988e2c1 diff --git a/android-geojson-layer b/android-geojson-layer index bfa194c0..55158170 160000 --- a/android-geojson-layer +++ b/android-geojson-layer @@ -1 +1 @@ -Subproject commit bfa194c07a0f37f4a6f8f16bee04e61b9dece291 +Subproject commit 55158170d5128b0c4e943fb226d934dbd0bb3269 diff --git a/android-heatmap b/android-heatmap index 33e4b230..f04f128f 160000 --- a/android-heatmap +++ b/android-heatmap @@ -1 +1 @@ -Subproject commit 33e4b23003f7db4e7e75f8cf8f0d006827af2811 +Subproject commit f04f128f6a7a73b47cc3e6a2f04b23b888b5983b diff --git a/android-icons b/android-icons index 67cb4b1d..b15c5ac9 160000 --- a/android-icons +++ b/android-icons @@ -1 +1 @@ -Subproject commit 67cb4b1d46d26174378dd3342ab615e4bb94055b +Subproject commit b15c5ac948fca3c65936c5816126f237df866e85 diff --git a/android-marker-clustering b/android-marker-clustering index 11e9b7f9..b10fdacb 160000 --- a/android-marker-clustering +++ b/android-marker-clustering @@ -1 +1 @@ -Subproject commit 11e9b7f933e2fad36adbed692543eeed08dc33b9 +Subproject commit b10fdacb70aadde6f8237caf24ee09b2021fd254 diff --git a/android-sdk-core b/android-sdk-core index 651c56c1..e4fb21d0 160000 --- a/android-sdk-core +++ b/android-sdk-core @@ -1 +1 @@ -Subproject commit 651c56c10adc3b1e6cb8b347394ea374cbcd86c4 +Subproject commit e4fb21d03a55977c805dc5d62679d6e09e6e6f4c diff --git a/images/english-comic-why-map-conductor.jpg b/docs/src/assets/top-page/english-comic-why-map-conductor.jpg similarity index 100% rename from images/english-comic-why-map-conductor.jpg rename to docs/src/assets/top-page/english-comic-why-map-conductor.jpg diff --git a/images/es-419-comic-why-map-conductor.jpg b/docs/src/assets/top-page/es-419-comic-why-map-conductor.jpg similarity index 100% rename from images/es-419-comic-why-map-conductor.jpg rename to docs/src/assets/top-page/es-419-comic-why-map-conductor.jpg diff --git a/images/japanese-comic-why-map-conductor.jpg b/docs/src/assets/top-page/japanese-comic-why-map-conductor.jpg similarity index 100% rename from images/japanese-comic-why-map-conductor.jpg rename to docs/src/assets/top-page/japanese-comic-why-map-conductor.jpg diff --git a/images/simple-map-screen.png b/docs/src/assets/top-page/simple-map-screen.png similarity index 100% rename from images/simple-map-screen.png rename to docs/src/assets/top-page/simple-map-screen.png diff --git a/docs/src/content/docs/core/georectbounds.mdx b/docs/src/content/docs/core/georectbounds.mdx index ec7bc65c..30c1d44c 100644 --- a/docs/src/content/docs/core/georectbounds.mdx +++ b/docs/src/content/docs/core/georectbounds.mdx @@ -146,3 +146,27 @@ The bounds can be dynamically extended to include new points: ### Equality Comparison + +## Fitting the Camera to Bounds + +To move and zoom the camera so a `GeoRectBounds` is fully visible, use `fitBounds`: + +```kotlin +val bounds = GeoRectBounds( + southWest = GeoPoint.fromLatLong(37.7649, -122.4294), + northEast = GeoPoint.fromLatLong(37.7849, -122.4094) +) +mapViewState.fitBounds(bounds, padding = 32) +``` + +The `padding` parameter adds screen-space inset in pixels on all sides. Each provider calculates the resulting zoom level natively, so the exact zoom may differ slightly between providers. + +A common pattern is to build bounds from a set of points and then fit: + +```kotlin +val bounds = GeoRectBounds() +markers.forEach { bounds.extend(it.position) } +if (!bounds.isEmpty) { + mapViewState.fitBounds(bounds, padding = 48) +} +``` diff --git a/docs/src/content/docs/core/mapcameraposition.mdx b/docs/src/content/docs/core/mapcameraposition.mdx index 7e12bb99..391c58c2 100644 --- a/docs/src/content/docs/core/mapcameraposition.mdx +++ b/docs/src/content/docs/core/mapcameraposition.mdx @@ -204,3 +204,33 @@ The visible region describes the actual geographic area displayed on screen afte + +## Fitting Bounds + +Use `fitBounds` to move and zoom the camera so that a `GeoRectBounds` is fully visible: + +```kotlin +val bounds = GeoRectBounds( + southWest = GeoPoint.fromLatLong(37.7649, -122.4294), + northEast = GeoPoint.fromLatLong(37.7849, -122.4094) +) +mapViewState.fitBounds(bounds, padding = 32) +``` + +The `padding` parameter adds screen-space inset in pixels on all sides. Each provider calculates the resulting zoom level natively. + +### Showing Multiple Points + +To frame a set of markers or route points, extend a bounds and then fit: + + + +The `fitBounds` call replaces the manual zoom calculation shown above: + +```kotlin +val bounds = GeoRectBounds() +allPoints.forEach { bounds.extend(it) } +if (!bounds.isEmpty) { + mapViewState.fitBounds(bounds, padding = 48) +} +``` diff --git a/docs/src/content/docs/es-419/core/georectbounds.mdx b/docs/src/content/docs/es-419/core/georectbounds.mdx index a0e9ac4b..5bad7331 100644 --- a/docs/src/content/docs/es-419/core/georectbounds.mdx +++ b/docs/src/content/docs/es-419/core/georectbounds.mdx @@ -143,3 +143,27 @@ import EqualsToExample from '~/components/core/georectbounds/EqualsToExample.ast ### Comparación de igualdad + +## Ajustar la cámara a los límites + +Para mover y hacer zoom de la cámara de modo que un `GeoRectBounds` sea completamente visible, usa `fitBounds`: + +```kotlin +val bounds = GeoRectBounds( + southWest = GeoPoint.fromLatLong(37.7649, -122.4294), + northEast = GeoPoint.fromLatLong(37.7849, -122.4094) +) +mapViewState.fitBounds(bounds, padding = 32) +``` + +El parámetro `padding` agrega un margen en píxeles en todos los lados del espacio de pantalla. Cada proveedor calcula el nivel de zoom resultante de forma nativa, por lo que el zoom exacto puede diferir ligeramente entre proveedores. + +Un patrón común es construir límites desde un conjunto de puntos y luego ajustar: + +```kotlin +val bounds = GeoRectBounds() +markers.forEach { bounds.extend(it.position) } +if (!bounds.isEmpty) { + mapViewState.fitBounds(bounds, padding = 48) +} +``` diff --git a/docs/src/content/docs/es-419/core/mapcameraposition.mdx b/docs/src/content/docs/es-419/core/mapcameraposition.mdx index 12c0586a..1bab322d 100644 --- a/docs/src/content/docs/es-419/core/mapcameraposition.mdx +++ b/docs/src/content/docs/es-419/core/mapcameraposition.mdx @@ -204,3 +204,33 @@ La región visible describe el área geográfica real que se muestra en pantalla + +## Ajustar a límites + +Usa `fitBounds` para mover y hacer zoom de la cámara de modo que un `GeoRectBounds` sea completamente visible: + +```kotlin +val bounds = GeoRectBounds( + southWest = GeoPoint.fromLatLong(37.7649, -122.4294), + northEast = GeoPoint.fromLatLong(37.7849, -122.4094) +) +mapViewState.fitBounds(bounds, padding = 32) +``` + +El parámetro `padding` agrega un margen en píxeles en todos los lados del espacio de pantalla. Cada proveedor calcula el nivel de zoom resultante de forma nativa. + +### Mostrar múltiples puntos + +Para encuadrar un conjunto de marcadores o puntos de ruta, extiende los límites y luego ajusta: + + + +La llamada a `fitBounds` reemplaza el cálculo manual de zoom mostrado arriba: + +```kotlin +val bounds = GeoRectBounds() +allPoints.forEach { bounds.extend(it) } +if (!bounds.isEmpty) { + mapViewState.fitBounds(bounds, padding = 48) +} +``` diff --git a/docs/src/content/docs/es-419/index.mdx b/docs/src/content/docs/es-419/index.mdx index 344a5102..83fb3e0a 100644 --- a/docs/src/content/docs/es-419/index.mdx +++ b/docs/src/content/docs/es-419/index.mdx @@ -12,7 +12,7 @@ lastUpdated: false import { CardGrid, Card } from "@astrojs/starlight/components"; import { Image } from "astro:assets"; -import topBanner from "~/assets/top-banner.png"; +import topBanner from "~/assets/top-page/es-419-comic-why-map-conductor.jpg"; import QuickExampleTabs from "~/components/index/QuickExampleTabs.astro"; import Features from "~/components/index/Features.astro"; import MapConductorIntroduction from "~/components/index/MapConductorIntroduction.astro"; diff --git a/docs/src/content/docs/es-419/modules.mdx b/docs/src/content/docs/es-419/modules.mdx index c7d66e07..8629ab79 100644 --- a/docs/src/content/docs/es-419/modules.mdx +++ b/docs/src/content/docs/es-419/modules.mdx @@ -93,6 +93,17 @@ Overlay de mapa de calor independiente de la implementación: Funciona con cualquier proveedor de mapas mediante una capa raster basada en tiles. Ver [Heatmap](/es-419/experimental/heatmap/). +### `android-geojson-layer` + +Capa GeoJSON independiente de la implementación del mapa: + +- Composable `GeoJSONLayer` +- `GeoJSONFeature` para datos estáticos +- `GeoJSONFeatureState` para integración con Compose state +- `GeoJSONParser` para parseo en lote y por streaming + +Funciona con cualquier proveedor de mapas mediante una capa raster basada en tiles. Ver [GeoJSON Layer](/es-419/experimental/geojson-layer/). + ### `android-marker-clustering` Agrupación automática de marcadores para todos los proveedores: diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index ec687d89..623f3cdc 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -12,7 +12,7 @@ lastUpdated: false import { CardGrid, Card } from "@astrojs/starlight/components"; import { Image } from "astro:assets"; -import topBanner from "~/assets/top-banner.png"; +import topBanner from "~/assets/top-page/english-comic-why-map-conductor.jpg"; import QuickExampleTabs from "~/components/index/QuickExampleTabs.astro"; import Features from "~/components/index/Features.astro"; import MapConductorIntroduction from "~/components/index/MapConductorIntroduction.astro"; diff --git a/docs/src/content/docs/ja/core/georectbounds.mdx b/docs/src/content/docs/ja/core/georectbounds.mdx index 925f4084..1c9e10b8 100644 --- a/docs/src/content/docs/ja/core/georectbounds.mdx +++ b/docs/src/content/docs/ja/core/georectbounds.mdx @@ -147,3 +147,27 @@ import EqualsToExample from '~/components/core/georectbounds/EqualsToExample.ast ### 等価性の比較 + +## バウンズへのカメラフィット + +`GeoRectBounds` が画面内に収まるようにカメラを移動・ズームするには `fitBounds` を使用します: + +```kotlin +val bounds = GeoRectBounds( + southWest = GeoPoint.fromLatLong(37.7649, -122.4294), + northEast = GeoPoint.fromLatLong(37.7849, -122.4094) +) +mapViewState.fitBounds(bounds, padding = 32) +``` + +`padding` パラメーターは全辺にスクリーンスペースのインセット(ピクセル単位)を追加します。ズームレベルの計算は各プロバイダーがネイティブに行うため、プロバイダー間で結果が若干異なる場合があります。 + +複数のポイントからバウンズを構築してフィットする典型的なパターン: + +```kotlin +val bounds = GeoRectBounds() +markers.forEach { bounds.extend(it.position) } +if (!bounds.isEmpty) { + mapViewState.fitBounds(bounds, padding = 48) +} +``` diff --git a/docs/src/content/docs/ja/core/mapcameraposition.mdx b/docs/src/content/docs/ja/core/mapcameraposition.mdx index fbc479bd..2644ec53 100644 --- a/docs/src/content/docs/ja/core/mapcameraposition.mdx +++ b/docs/src/content/docs/ja/core/mapcameraposition.mdx @@ -204,3 +204,33 @@ MapConductor のズームレベルは、おおよそ Google Maps のスケール + +## バウンズへのフィット + +`fitBounds` を使うと、`GeoRectBounds` が画面内に収まるようにカメラを移動・ズームできます: + +```kotlin +val bounds = GeoRectBounds( + southWest = GeoPoint.fromLatLong(37.7649, -122.4294), + northEast = GeoPoint.fromLatLong(37.7849, -122.4094) +) +mapViewState.fitBounds(bounds, padding = 32) +``` + +`padding` パラメーターは全辺にスクリーンスペースのインセット(ピクセル単位)を追加します。ズームレベルの計算は各プロバイダーがネイティブに行います。 + +### 複数ポイントの表示 + +マーカーやルートポイントを画面内に収めるには、バウンズを拡張してからフィットします: + + + +上記の手動ズーム計算は `fitBounds` で置き換えられます: + +```kotlin +val bounds = GeoRectBounds() +allPoints.forEach { bounds.extend(it) } +if (!bounds.isEmpty) { + mapViewState.fitBounds(bounds, padding = 48) +} +``` diff --git a/docs/src/content/docs/ja/index.mdx b/docs/src/content/docs/ja/index.mdx index 96208f2a..43fecc52 100644 --- a/docs/src/content/docs/ja/index.mdx +++ b/docs/src/content/docs/ja/index.mdx @@ -12,7 +12,7 @@ lastUpdated: false import { CardGrid, Card } from "@astrojs/starlight/components"; import { Image } from "astro:assets"; -import topBanner from "~/assets/top-banner.png"; +import topBanner from "~/assets/top-page/japanese-comic-why-map-conductor.jpg"; import QuickExampleTabs from "~/components/index/QuickExampleTabs.astro"; import Features from "~/components/index/Features.astro"; import MapConductorIntroduction from "~/components/index/MapConductorIntroduction.astro"; diff --git a/docs/src/content/docs/modules.mdx b/docs/src/content/docs/modules.mdx index 74a68b26..7238cee5 100644 --- a/docs/src/content/docs/modules.mdx +++ b/docs/src/content/docs/modules.mdx @@ -93,6 +93,17 @@ Map-implementation-agnostic heatmap overlay: Works with any map provider via a tile-based raster layer. See [Heatmap](/experimental/heatmap/). +### `android-geojson-layer` + +Map-implementation-agnostic GeoJSON layer: + +- `GeoJSONLayer` composable +- `GeoJSONFeature` for static data +- `GeoJSONFeatureState` for Compose state integration +- `GeoJSONParser` for bulk and streaming parse + +Works with any map provider via a tile-based raster layer. See [GeoJSON Layer](/experimental/geojson-layer/). + ### `android-marker-clustering` Automatic marker clustering across all map providers: 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 9561417f..50f4be79 100644 --- a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt +++ b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt @@ -25,6 +25,7 @@ import com.mapconductor.example.pages.map.arcgis2d.ArcGISMapView2DPage import com.mapconductor.example.pages.map.basic.StoreMapPage import com.mapconductor.example.pages.map.camerasync.CameraSyncPage import com.mapconductor.example.pages.map.design.MapDesignMapPage +import com.mapconductor.example.pages.map.fitbounds.FitBoundsMapPage import com.mapconductor.example.pages.map.flyto.FlyToMapIcons import com.mapconductor.example.pages.map.flyto.FlyToMapPage import com.mapconductor.example.pages.map.tilt.TiltMapPage @@ -113,6 +114,10 @@ fun DemoAppScreen(initPage: String = "map") { id = "map-flyTo", title = "Move camera", ), + SidebarItem( + id = "map-fitBounds", + title = "Fit Bounds", + ), SidebarItem( id = "map-design", title = "Map design", @@ -250,6 +255,11 @@ fun DemoAppScreen(initPage: String = "map") { onToggleSidebar = navigationViewModel::toggleSidebar, ) } + "map-fitBounds" -> { + FitBoundsMapPage( + onToggleSidebar = navigationViewModel::toggleSidebar, + ) + } "marker-basic" -> { MarkerBasicPage( 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 4a01a894..ece53b3f 100644 --- a/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt +++ b/example-app/src/main/java/com/mapconductor/example/MapViewContainer.kt @@ -27,6 +27,7 @@ fun MapViewContainer( markerTiling: MarkerTilingOptions? = null, onMapLoaded: OnMapLoadedHandler? = null, onMapClick: OnMapEventHandler? = null, + onMapLongClick: OnMapEventHandler? = null, onCameraMoveStart: OnCameraMoveHandler? = null, onCameraMove: OnCameraMoveHandler? = null, onCameraMoveEnd: OnCameraMoveHandler? = null, @@ -41,6 +42,7 @@ fun MapViewContainer( markerTiling = markerTiling, onMapLoaded = onMapLoaded, onMapClick = onMapClick, + onMapLongClick = onMapLongClick, onCameraMoveStart = onCameraMoveStart, onCameraMove = onCameraMove, onCameraMoveEnd = onCameraMoveEnd, @@ -54,6 +56,7 @@ fun MapViewContainer( markerTiling = markerTiling, onMapLoaded = onMapLoaded, onMapClick = onMapClick, + onMapLongClick = onMapLongClick, onCameraMoveStart = onCameraMoveStart, onCameraMove = onCameraMove, onCameraMoveEnd = onCameraMoveEnd, @@ -67,6 +70,7 @@ fun MapViewContainer( markerTiling = markerTiling, onMapLoaded = onMapLoaded, onMapClick = onMapClick, + onMapLongClick = onMapLongClick, onCameraMoveStart = onCameraMoveStart, onCameraMove = onCameraMove, onCameraMoveEnd = onCameraMoveEnd, @@ -80,6 +84,7 @@ fun MapViewContainer( markerTiling = markerTiling, onMapLoaded = onMapLoaded, onMapClick = onMapClick, + onMapLongClick = onMapLongClick, onCameraMoveStart = onCameraMoveStart, onCameraMove = onCameraMove, onCameraMoveEnd = onCameraMoveEnd, @@ -93,6 +98,7 @@ fun MapViewContainer( markerTiling = markerTiling, onMapLoaded = onMapLoaded, onMapClick = onMapClick, + onMapLongClick = onMapLongClick, onCameraMoveStart = onCameraMoveStart, onCameraMove = onCameraMove, onCameraMoveEnd = onCameraMoveEnd, diff --git a/example-app/src/main/java/com/mapconductor/example/pages/map/fitbounds/FitBoundsMapComponent.kt b/example-app/src/main/java/com/mapconductor/example/pages/map/fitbounds/FitBoundsMapComponent.kt new file mode 100644 index 00000000..ca9bc93d --- /dev/null +++ b/example-app/src/main/java/com/mapconductor/example/pages/map/fitbounds/FitBoundsMapComponent.kt @@ -0,0 +1,32 @@ +package com.mapconductor.example.pages.map.fitbounds + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.mapconductor.core.features.GeoPoint +import com.mapconductor.core.map.MapViewStateInterface +import com.mapconductor.core.map.OnMapEventHandler +import com.mapconductor.core.marker.Marker +import com.mapconductor.core.marker.MarkerState +import com.mapconductor.core.polygon.Polygon +import com.mapconductor.core.polygon.PolygonState +import com.mapconductor.example.MapViewContainer + +@Composable +fun FitBoundsMapComponent( + mapViewState: MapViewStateInterface<*>?, + marker: MarkerState, + boundsPolygon: PolygonState?, + onMapLongClick: OnMapEventHandler? = null, + modifier: Modifier = Modifier, +) { + mapViewState?.let { state -> + MapViewContainer( + modifier = modifier, + state = state, + onMapLongClick = onMapLongClick, + ) { + Marker(marker) + boundsPolygon?.let { Polygon(it) } + } + } +} diff --git a/example-app/src/main/java/com/mapconductor/example/pages/map/fitbounds/FitBoundsMapPage.kt b/example-app/src/main/java/com/mapconductor/example/pages/map/fitbounds/FitBoundsMapPage.kt new file mode 100644 index 00000000..04aadc8e --- /dev/null +++ b/example-app/src/main/java/com/mapconductor/example/pages/map/fitbounds/FitBoundsMapPage.kt @@ -0,0 +1,50 @@ +package com.mapconductor.example.pages.map.fitbounds + +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.runtime.remember +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.DefaultMapViewItems +import com.mapconductor.example.ui.DemoMapPageScaffold +import com.mapconductor.example.ui.MessageCard + +@Composable +fun FitBoundsMapPage( + onToggleSidebar: () -> Unit = {}, +) { + val viewModel: FitBoundsPageViewModelInterface = remember { FitBoundsPageViewModel() } + + DemoMapPageScaffold( + menuItems = DefaultMapViewItems(viewModel.initCameraPosition), + onToggleSidebar = onToggleSidebar, + onMapViewStateChanged = viewModel::onMapViewChanged, + ) { paddingValues -> + val mapViewState = viewModel.mapViewState.collectAsState() + val boundsPolygon = viewModel.boundsPolygon.collectAsState() + + FitBoundsMapComponent( + mapViewState = mapViewState.value, + marker = viewModel.marker, + boundsPolygon = boundsPolygon.value, + ) + + 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 = "Fit Bounds", + ) { + androidx.compose.material3.Text("マーカーをドラッグして範囲を指定し、ドロップすると fitBounds で地図が移動します。") + } + } +} diff --git a/example-app/src/main/java/com/mapconductor/example/pages/map/fitbounds/FitBoundsPageViewModel.kt b/example-app/src/main/java/com/mapconductor/example/pages/map/fitbounds/FitBoundsPageViewModel.kt new file mode 100644 index 00000000..259ffe96 --- /dev/null +++ b/example-app/src/main/java/com/mapconductor/example/pages/map/fitbounds/FitBoundsPageViewModel.kt @@ -0,0 +1,109 @@ +package com.mapconductor.example.pages.map.fitbounds + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mapconductor.core.features.GeoPoint +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import com.mapconductor.core.features.GeoRectBounds +import com.mapconductor.core.map.MapCameraPosition +import com.mapconductor.core.map.MapViewStateInterface +import com.mapconductor.core.marker.MarkerState +import com.mapconductor.core.polygon.PolygonState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlin.math.max +import kotlin.math.min + +interface FitBoundsPageViewModelInterface { + val initCameraPosition: MapCameraPosition + val mapViewState: StateFlow?> + val marker: MarkerState + val boundsPolygon: StateFlow + + fun onMapViewChanged(state: MapViewStateInterface<*>) +} + +class FitBoundsPageViewModel : ViewModel(), FitBoundsPageViewModelInterface { + + private val initialPosition = GeoPoint(35.68, 139.76) + + override val initCameraPosition = MapCameraPosition( + position = initialPosition, + zoom = 10.0, + bearing = 0.0, + tilt = 0.0, + paddings = null, + ) + + private val _mapViewState = MutableStateFlow?>(null) + override val mapViewState: StateFlow?> = _mapViewState.asStateFlow() + + private var dragStartPosition: GeoPoint? = null + + private val _boundsPolygon = MutableStateFlow(null) + override val boundsPolygon: StateFlow = _boundsPolygon.asStateFlow() + + override val marker: MarkerState = MarkerState( + id = "fitbounds_marker", + position = initialPosition, + draggable = true, + onDragStart = ::onDragStart, + onDrag = ::onDrag, + onDragEnd = ::onDragEnd, + ) + + override fun onMapViewChanged(state: MapViewStateInterface<*>) { + _mapViewState.value?.cameraPosition?.let { state.moveCameraTo(it) } + _mapViewState.value = state + } + + private fun onDragStart(marker: MarkerState) { + dragStartPosition = GeoPoint.from(marker.position) + } + + private fun onDrag(marker: MarkerState) { + val start = dragStartPosition ?: return + val current = GeoPoint.from(marker.position) + _boundsPolygon.value = buildPolygon(start, current) + } + + private fun onDragEnd(marker: MarkerState) { + val start = dragStartPosition ?: return + val current = GeoPoint.from(marker.position) + val bounds = buildBounds(start, current) + _mapViewState.value?.fitBounds(bounds) + dragStartPosition = null + viewModelScope.launch { + delay(1500) + _boundsPolygon.value = null + } + } + + private fun buildBounds(a: GeoPoint, b: GeoPoint) = GeoRectBounds( + southWest = GeoPoint(min(a.latitude, b.latitude), min(a.longitude, b.longitude)), + northEast = GeoPoint(max(a.latitude, b.latitude), max(a.longitude, b.longitude)), + ) + + private fun buildPolygon(a: GeoPoint, b: GeoPoint): PolygonState { + val minLat = min(a.latitude, b.latitude) + val maxLat = max(a.latitude, b.latitude) + val minLon = min(a.longitude, b.longitude) + val maxLon = max(a.longitude, b.longitude) + return PolygonState( + id = "fitbounds_polygon", + points = listOf( + GeoPoint(minLat, minLon), + GeoPoint(minLat, maxLon), + GeoPoint(maxLat, maxLon), + GeoPoint(maxLat, minLon), + ), + strokeColor = Color.Red, + strokeWidth = 2.dp, + fillColor = Color.Red.copy(alpha = 0.3f), + ) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e3b34468..9a0e098f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,15 +11,15 @@ junit = "4.13.2" junitVersion = "1.3.0" espressoCore = "3.7.0" -lifecycleRuntimeKtx = "2.10.0" +lifecycleRuntimeKtx = "2.11.0" activityCompose = "1.13.0" -lifecycleViewmodelCompose = "2.10.0" +lifecycleViewmodelCompose = "2.11.0" # https://developer.android.com/develop/ui/compose/bom/bom-mapping # gradle.properties?kotlinCompilerExtensionVersion?????? -compose = "1.11.2" -composeBom = "2026.05.01" +compose = "1.11.3" +composeBom = "2026.06.00" okhttp = "5.4.0" runner = "1.7.0" @@ -32,13 +32,13 @@ ktLint = "14.2.0" geographiclib = "2.1" mapLibreSdk = "13.3.0" mapLibreAnnotation = "3.0.2" -runtime = "1.11.2" -runtimeVersion = "1.11.2" +runtime = "1.11.3" +runtimeVersion = "1.11.3" materialVersion = "1.14.0" jts = "1.20.0" coroutines = "1.11.0" -uiGraphics = "1.11.2" -foundation = "1.11.2" +uiGraphics = "1.11.3" +foundation = "1.11.3" [libraries] # Android ?? diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 18226d06..1d10968c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Apr 11 01:40:57 PDT 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists