Skip to content

Commit 84da245

Browse files
committed
Draw bounds and locations
1 parent 7abe4c0 commit 84da245

8 files changed

Lines changed: 265 additions & 52 deletions

File tree

examples/Example01Pan/README.md

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
[Back to README](../../README.md)
44

5-
This example demonstrates the core functionality of OpenMapView: displaying OpenStreetMap tiles and responding to touch pan gestures.
5+
This example demonstrates the core functionality of OpenMapView: displaying OpenStreetMap tiles, responding to touch pan gestures, and using camera controls with status display.
66

77
## Features Demonstrated
88

99
- Map tile rendering from OpenStreetMap
1010
- Touch pan/drag gestures
11-
- Smooth real-time map updates
12-
- Basic OpenMapView setup
11+
- Arrow button toolbar for programmatic panning
12+
- Preset location buttons with animated camera moves
13+
- Camera bounds constraints with visual polyline indicator
14+
- Real-time camera state and position display
15+
- Colored markers at preset locations
1316

1417
## Screenshot
1518

@@ -34,38 +37,83 @@ This example demonstrates the core functionality of OpenMapView: displaying Open
3437
adb shell am start -n de.afarber.openmapview.example01pan/.MainActivity
3538
```
3639

40+
## Project Structure
41+
42+
```
43+
example01pan/
44+
├── MainActivity.kt # Main activity and MapViewScreen composable
45+
├── ArrowToolbar.kt # Horizontal toolbar with pan arrow buttons
46+
├── LocationToolbar.kt # Vertical toolbar with preset location buttons
47+
├── ControlToolbar.kt # Vertical toolbar with bounds/reset controls
48+
├── StatusToolbar.kt # Status overlay showing camera state
49+
└── Colors.kt # OSM-inspired colors and shared dimensions
50+
```
51+
3752
## Code Highlights
3853

3954
### MainActivity.kt
4055

4156
```kotlin
4257
@Composable
4358
fun MapViewScreen() {
44-
AndroidView(
45-
factory = { context ->
46-
OpenMapView(context).apply {
47-
setCenter(LatLng(51.4661, 7.2491)) // Bochum, Germany
48-
setZoom(14.0)
49-
}
50-
},
51-
modifier = Modifier.fillMaxSize(),
52-
)
59+
val lifecycleOwner = LocalLifecycleOwner.current
60+
var mapView: OpenMapView? by remember { mutableStateOf(null) }
61+
62+
Box(modifier = Modifier.fillMaxSize()) {
63+
AndroidView(
64+
factory = { ctx ->
65+
OpenMapView(ctx).apply {
66+
lifecycleOwner.lifecycle.addObserver(this)
67+
setCenter(LatLng(51.4661, 7.2491)) // Bochum, Germany
68+
setZoom(14.0f)
69+
mapView = this
70+
}
71+
},
72+
modifier = Modifier.fillMaxSize(),
73+
)
74+
75+
// Toolbars overlay the map
76+
StatusToolbar(...)
77+
ArrowToolbar(...)
78+
LocationToolbar(...)
79+
ControlToolbar(...)
80+
}
5381
}
5482
```
5583

84+
### OSM-Inspired Colors (Colors.kt)
85+
86+
```kotlin
87+
val OsmParkGreen = Color(0xFFAAD3A2) // Parks and forests
88+
val OsmHighwayPink = Color(0xFFE892A2) // Highways and roads
89+
val OsmWaterBlue = Color(0xFFAAD3DF) // Water areas
90+
```
91+
5692
### Key Concepts
5793

5894
- **LatLng**: Represents geographic coordinates (latitude, longitude)
5995
- **setCenter()**: Sets the initial map center position
6096
- **setZoom()**: Sets zoom level (2.0 = world view, 19.0 = street level)
61-
- **Touch handling**: Built-in via OpenMapView's onTouchEvent()
97+
- **moveCamera()**: Instantly moves the camera (no animation)
98+
- **animateCamera()**: Smoothly animates camera to new position
99+
- **setLatLngBoundsForCameraTarget()**: Constrains camera movement to bounds
100+
- **OnCameraMoveListener**: Callback for camera position changes
62101

63102
## What to Test
64103

65104
1. **Pan the map** by dragging with your finger/mouse
66-
2. **Observe tiles loading** as you pan to new areas
67-
3. **Check smooth rendering** - map should update in real-time without lag
105+
2. **Use arrow buttons** to pan the map programmatically
106+
3. **Tap location buttons** (Loc 1, 2, 3) to animate to preset positions
107+
4. **Toggle bounds** to constrain camera movement (blue polyline shows bounds)
108+
5. **Tap Reset** to return to initial position
109+
6. **Observe status overlay** showing camera state and coordinates
68110

69111
## Map Location
70112

71-
**Default Center:** Bochum, Germany (51.4661°N, 7.2491°E)
113+
**Default Center:** Bochum, Germany (51.4661N, 7.2491E)
114+
115+
**Preset Locations:**
116+
- Location 1 (Red marker): North-West of center
117+
- Location 2 (Green marker): East of center
118+
- Location 3 (Magenta marker): South-West of center
119+
- Initial (Cyan marker): Center position
-4.07 MB
Loading

examples/Example01Pan/src/main/kotlin/de/afarber/openmapview/example01pan/ArrowToolbar.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ import androidx.compose.ui.graphics.Color
2626
import androidx.compose.ui.graphics.RectangleShape
2727
import androidx.compose.ui.unit.dp
2828

29+
/**
30+
* A horizontal toolbar with four arrow buttons for panning the map.
31+
*
32+
* Displays left, up, down, and right arrow buttons in a row with OSM park green background.
33+
*
34+
* @param onLeftClick Callback invoked when the left arrow button is clicked.
35+
* @param onUpClick Callback invoked when the up arrow button is clicked.
36+
* @param onDownClick Callback invoked when the down arrow button is clicked.
37+
* @param onRightClick Callback invoked when the right arrow button is clicked.
38+
* @param modifier Modifier to be applied to the toolbar.
39+
*/
2940
@Composable
3041
fun ArrowToolbar(
3142
onLeftClick: () -> Unit,

examples/Example01Pan/src/main/kotlin/de/afarber/openmapview/example01pan/Colors.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,18 @@ package de.afarber.openmapview.example01pan
1010
import androidx.compose.ui.graphics.Color
1111
import androidx.compose.ui.unit.dp
1212

13-
// OSM-inspired colors
13+
/**
14+
* OSM-inspired colors and shared dimensions for the Example01Pan app.
15+
*/
16+
17+
/** Green color used by OpenStreetMap for parks and forests. */
1418
val OsmParkGreen = Color(0xFFAAD3A2)
19+
20+
/** Pink color used by OpenStreetMap for highways and major roads. */
1521
val OsmHighwayPink = Color(0xFFE892A2)
22+
23+
/** Blue color used by OpenStreetMap for water areas (lakes, rivers). */
1624
val OsmWaterBlue = Color(0xFFAAD3DF)
1725

26+
/** Shared corner radius for all toolbar components. */
1827
val ToolbarCornerRadius = 8.dp

examples/Example01Pan/src/main/kotlin/de/afarber/openmapview/example01pan/ControlToolbar.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ import androidx.compose.ui.Modifier
2121
import androidx.compose.ui.graphics.Color
2222
import androidx.compose.ui.unit.dp
2323

24+
/**
25+
* A vertical toolbar with map control buttons.
26+
*
27+
* Contains a bounds toggle button and a reset button, using OSM water blue background.
28+
*
29+
* @param boundsEnabled Whether camera bounds constraint is currently enabled.
30+
* @param onBoundsClick Callback invoked when the bounds toggle button is clicked.
31+
* @param onResetClick Callback invoked when the reset button is clicked.
32+
* @param modifier Modifier to be applied to the toolbar.
33+
*/
2434
@Composable
2535
fun ControlToolbar(
2636
boundsEnabled: Boolean,

examples/Example01Pan/src/main/kotlin/de/afarber/openmapview/example01pan/LocationToolbar.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ import androidx.compose.ui.graphics.RectangleShape
2323
import androidx.compose.ui.unit.dp
2424
import de.afarber.openmapview.LatLng
2525

26+
/**
27+
* A vertical toolbar displaying buttons for navigating to preset locations.
28+
*
29+
* Each button is labeled "Loc 1", "Loc 2", etc. and uses OSM highway pink background.
30+
*
31+
* @param locations List of [LatLng] positions to display as buttons.
32+
* @param onLocationClick Callback invoked when a location button is clicked, receiving the [LatLng].
33+
* @param modifier Modifier to be applied to the toolbar.
34+
*/
2635
@Composable
2736
fun LocationToolbar(
2837
locations: List<LatLng>,

examples/Example01Pan/src/main/kotlin/de/afarber/openmapview/example01pan/MainActivity.kt

Lines changed: 98 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,38 @@ package de.afarber.openmapview.example01pan
1010
import android.os.Bundle
1111
import androidx.activity.ComponentActivity
1212
import androidx.activity.compose.setContent
13-
import androidx.compose.foundation.background
1413
import androidx.compose.foundation.layout.Box
15-
import androidx.compose.foundation.layout.Column
1614
import androidx.compose.foundation.layout.fillMaxSize
1715
import androidx.compose.foundation.layout.padding
18-
import androidx.compose.foundation.shape.RoundedCornerShape
1916
import androidx.compose.material3.MaterialTheme
2017
import androidx.compose.material3.Surface
21-
import androidx.compose.material3.Text
2218
import androidx.compose.runtime.Composable
2319
import androidx.compose.runtime.getValue
2420
import androidx.compose.runtime.mutableStateOf
2521
import androidx.compose.runtime.remember
2622
import androidx.compose.runtime.setValue
2723
import androidx.compose.ui.Alignment
2824
import androidx.compose.ui.Modifier
29-
import androidx.compose.ui.draw.clip
30-
import androidx.compose.ui.graphics.Color
3125
import androidx.compose.ui.unit.dp
3226
import androidx.compose.ui.viewinterop.AndroidView
27+
import de.afarber.openmapview.BitmapDescriptorFactory
3328
import de.afarber.openmapview.CameraUpdateFactory
3429
import de.afarber.openmapview.LatLng
3530
import de.afarber.openmapview.LatLngBounds
31+
import de.afarber.openmapview.Marker
3632
import de.afarber.openmapview.OnCameraMoveStartedListener
3733
import de.afarber.openmapview.OpenMapView
34+
import de.afarber.openmapview.Polyline
3835

36+
/**
37+
* Main activity demonstrating OpenMapView panning and camera controls.
38+
*
39+
* This example showcases:
40+
* - Map panning with arrow buttons
41+
* - Preset location navigation with animated camera moves
42+
* - Camera bounds constraints with visual polyline indicator
43+
* - Real-time camera state and position display
44+
*/
3945
class MainActivity : ComponentActivity() {
4046
override fun onCreate(savedInstanceState: Bundle?) {
4147
super.onCreate(savedInstanceState)
@@ -52,6 +58,12 @@ class MainActivity : ComponentActivity() {
5258
}
5359
}
5460

61+
/**
62+
* Main composable screen containing the map and control toolbars.
63+
*
64+
* Displays an OpenMapView with overlaid toolbars for navigation and status display.
65+
* The map is centered on Bochum, Germany with preset locations marked.
66+
*/
5567
@Composable
5668
fun MapViewScreen() {
5769
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
@@ -73,6 +85,30 @@ fun MapViewScreen() {
7385
var cameraState by remember { mutableStateOf("Idle") }
7486
var centerPosition by remember { mutableStateOf("%.4f, %.4f".format(initialLocation.latitude, initialLocation.longitude)) }
7587
var boundsEnabled by remember { mutableStateOf(false) }
88+
var boundsPolyline: Polyline? by remember { mutableStateOf(null) }
89+
90+
// Polyline to visualize the bounds rectangle
91+
val boundsOutline = Polyline(
92+
points = listOf(
93+
bochumBounds.southwest,
94+
LatLng(bochumBounds.southwest.latitude, bochumBounds.northeast.longitude),
95+
bochumBounds.northeast,
96+
LatLng(bochumBounds.northeast.latitude, bochumBounds.southwest.longitude),
97+
bochumBounds.southwest, // Close the rectangle
98+
),
99+
strokeColor = androidx.compose.ui.graphics.Color.Blue,
100+
strokeWidth = 3f,
101+
)
102+
103+
// Helper to update center position after moveCamera() calls.
104+
// Note: moveCamera() moves the camera instantly without animation, and unlike
105+
// animateCamera(), it does not trigger OnCameraMoveListener. Therefore, we must
106+
// manually update centerPosition after each moveCamera() call (e.g., arrow buttons).
107+
fun updateCenterPosition() {
108+
mapView?.getCameraPosition()?.target?.let { pos ->
109+
centerPosition = "%.4f, %.4f".format(pos.latitude, pos.longitude)
110+
}
111+
}
76112

77113
Box(modifier = Modifier.fillMaxSize()) {
78114
// Map view
@@ -84,6 +120,36 @@ fun MapViewScreen() {
84120
setCenter(initialLocation)
85121
setZoom(14.0f)
86122

123+
// Add markers for preset locations
124+
addMarker(
125+
Marker(
126+
position = initialLocation,
127+
title = "Initial location",
128+
icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_CYAN),
129+
),
130+
)
131+
addMarker(
132+
Marker(
133+
position = location1,
134+
title = "Location 1",
135+
icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED),
136+
),
137+
)
138+
addMarker(
139+
Marker(
140+
position = location2,
141+
title = "Location 2",
142+
icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN),
143+
),
144+
)
145+
addMarker(
146+
Marker(
147+
position = location3,
148+
title = "Location 3",
149+
icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA),
150+
),
151+
)
152+
87153
// Camera move started listener
88154
setOnCameraMoveStartedListener { reason ->
89155
cameraState = when (reason) {
@@ -96,8 +162,7 @@ fun MapViewScreen() {
96162

97163
// Camera move listener - updates position during movement
98164
setOnCameraMoveListener {
99-
val pos = getCameraPosition().target
100-
centerPosition = "%.4f, %.4f".format(pos.latitude, pos.longitude)
165+
updateCenterPosition()
101166
}
102167

103168
// Camera idle listener
@@ -112,39 +177,33 @@ fun MapViewScreen() {
112177
)
113178

114179
// Status overlay at top
115-
Column(
180+
StatusToolbar(
181+
cameraState = cameraState,
182+
centerPosition = centerPosition,
183+
boundsEnabled = boundsEnabled,
116184
modifier = Modifier
117185
.align(Alignment.TopCenter)
118-
.padding(16.dp)
119-
.clip(RoundedCornerShape(ToolbarCornerRadius))
120-
.background(Color.White)
121186
.padding(16.dp),
122-
horizontalAlignment = Alignment.CenterHorizontally,
123-
) {
124-
Text(
125-
text = "Camera: $cameraState",
126-
color = Color.Black,
127-
)
128-
Text(
129-
text = "Center: $centerPosition",
130-
color = Color.Black,
131-
)
132-
Text(
133-
text = "Bounds: ${if (boundsEnabled) "On" else "Off"}",
134-
color = if (boundsEnabled) {
135-
Color.Black
136-
} else {
137-
Color.Red
138-
},
139-
)
140-
}
187+
)
141188

142189
// Arrow toolbar at bottom
143190
ArrowToolbar(
144-
onLeftClick = { mapView?.moveCamera(CameraUpdateFactory.scrollBy(-100f, 0f)) },
145-
onUpClick = { mapView?.moveCamera(CameraUpdateFactory.scrollBy(0f, -100f)) },
146-
onDownClick = { mapView?.moveCamera(CameraUpdateFactory.scrollBy(0f, 100f)) },
147-
onRightClick = { mapView?.moveCamera(CameraUpdateFactory.scrollBy(100f, 0f)) },
191+
onLeftClick = {
192+
mapView?.moveCamera(CameraUpdateFactory.scrollBy(-100f, 0f))
193+
updateCenterPosition()
194+
},
195+
onUpClick = {
196+
mapView?.moveCamera(CameraUpdateFactory.scrollBy(0f, -100f))
197+
updateCenterPosition()
198+
},
199+
onDownClick = {
200+
mapView?.moveCamera(CameraUpdateFactory.scrollBy(0f, 100f))
201+
updateCenterPosition()
202+
},
203+
onRightClick = {
204+
mapView?.moveCamera(CameraUpdateFactory.scrollBy(100f, 0f))
205+
updateCenterPosition()
206+
},
148207
modifier = Modifier
149208
.align(Alignment.BottomCenter)
150209
.padding(bottom = 16.dp),
@@ -167,9 +226,13 @@ fun MapViewScreen() {
167226
onBoundsClick = {
168227
if (boundsEnabled) {
169228
mapView?.setLatLngBoundsForCameraTarget(null)
229+
boundsPolyline?.let { mapView?.removePolyline(it) }
230+
boundsPolyline = null
170231
boundsEnabled = false
171232
} else {
172233
mapView?.setLatLngBoundsForCameraTarget(bochumBounds)
234+
boundsPolyline = boundsOutline
235+
mapView?.addPolyline(boundsOutline)
173236
boundsEnabled = true
174237
}
175238
},

0 commit comments

Comments
 (0)