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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Add to your `build.gradle.kts`:

```kotlin
dependencies {
implementation("de.afarber:openmapview:0.11.0")
implementation("de.afarber:openmapview:0.12.0")
}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ HttpClient(Android) {
All requests include a user-agent header as required by OSM tile usage policy:

```kotlin
header("User-Agent", "OpenMapView/0.11.0 (https://github.com/afarber/OpenMapView)")
header("User-Agent", "OpenMapView/0.12.0 (https://github.com/afarber/OpenMapView)")
```

### Coroutine-Based Downloads
Expand Down
15 changes: 14 additions & 1 deletion docs/PUBLIC_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ This document lists all public non-deprecated methods from Google's MapView and
| -------------------------- | ----------- | ----- |
| `addMarker(MarkerOptions)` | `Marker` | |
| `clear()` | `void` | |
| `showInfoWindow(Marker)` | `void` | Shows marker's info window with auto-dismiss support |
| `hideInfoWindow(Marker)` | `void` | Hides marker's info window and cancels auto-dismiss |

---

## Marker Class

| Method | Return Type | Notes |
| ------------------ | ----------- | -------------------------------------------------------------------- |
| `showInfoWindow()` | `void` | Shows this marker's info window (auto-dismiss if configured via UiSettings) |
| `hideInfoWindow()` | `void` | Hides this marker's info window |
| `isInfoWindowShown`| `Boolean` | Returns whether this marker's info window is currently shown |

---

Expand Down Expand Up @@ -222,6 +234,7 @@ Methods available on the UiSettings object returned by `getUiSettings()`:
| `setMapToolbarEnabled(boolean)` | `void` | Not implemented - use openInExternalApp() instead (see External Map Integration section) |
| `isMapToolbarEnabled()` | `boolean` | Always returns false |
| `setAllGesturesEnabled(boolean)` | `void` | |
| `infoWindowAutoDismiss` | `Duration` | OpenMapView-specific: auto-dismiss info windows after duration (ZERO = disabled) |

---

Expand Down Expand Up @@ -287,7 +300,7 @@ OpenMapView provides comprehensive event listener support using Kotlin `fun inte
| `setOnMapLoadedCallback(OnMapLoadedCallback)` | `void` | Not implemented - tiles load asynchronously, callback could be added |
| `setInfoWindowAdapter(InfoWindowAdapter)` | `void` | Not implemented - custom adapters not yet implemented |
| `setOnInfoWindowClickListener(OnInfoWindowClickListener)` | `void` | |
| `setOnInfoWindowCloseListener(OnInfoWindowCloseListener)` | `void` | Not implemented |
| `setOnInfoWindowCloseListener(OnInfoWindowCloseListener)` | `void` | Called when info window is closed (manual or auto-dismiss) |
| `setOnInfoWindowLongClickListener(OnInfoWindowLongClickListener)` | `void` | Not implemented |
| `setOnMyLocationButtonClickListener(OnMyLocationButtonClickListener)` | `void` | Not implemented |
| `setOnMyLocationClickListener(OnMyLocationClickListener)` | `void` | Not implemented |
Expand Down
2 changes: 1 addition & 1 deletion docs/REPLACING_GOOGLE_MAPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ dependencies {
```kotlin
// Add to build.gradle.kts
dependencies {
implementation("de.afarber:openmapview:0.11.0")
implementation("de.afarber:openmapview:0.12.0")
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ fun ArrowToolbar(
modifier = Modifier.size(56.dp),
shape = RoundedCornerShape(topEnd = ToolbarCornerRadius, bottomEnd = ToolbarCornerRadius),
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = OsmParkGreen,
containerColor = OsmHighwayPink,
contentColor = Color.Black,
),
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Remove
import androidx.compose.material.icons.filled.ZoomIn
import androidx.compose.material.icons.filled.ZoomOut
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
Expand Down Expand Up @@ -54,7 +54,7 @@ fun ZoomToolbar(
contentColor = Color.Black,
),
) {
Icon(Icons.Default.Add, contentDescription = "Zoom In")
Icon(Icons.Default.ZoomIn, contentDescription = "Zoom In")
}
FilledIconButton(
onClick = onZoomOutClick,
Expand All @@ -65,7 +65,7 @@ fun ZoomToolbar(
contentColor = Color.Black,
),
) {
Icon(Icons.Default.Remove, contentDescription = "Zoom Out")
Icon(Icons.Default.ZoomOut, contentDescription = "Zoom Out")
}
}
}
Expand Down
154 changes: 96 additions & 58 deletions examples/Example03Markers/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
# Example03Markers - Marker Overlays and Click Handling
# Example03Markers - Marker Navigation and Info Windows

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

This example demonstrates the marker system in OpenMapView, including marker rendering, touch detection, and click event handling.
This example demonstrates the marker system in OpenMapView, including marker rendering, touch detection, info windows, and marker navigation.

## Features Demonstrated

- Multiple markers at different geographic locations
- Default red teardrop marker icons with color variations
- Both API styles: Kotlin direct instantiation and Google Maps builder pattern
- Marker click detection and callbacks
- Toast notifications on marker click
- Markers with title and snippet metadata
- Marker positioning with proper anchor points
- Markers that stay fixed during pan and zoom
- Info windows showing marker titles and snippets
- Multiple markers at real Bochum landmark locations
- Default teardrop marker icons with color variations
- Marker click detection and selection tracking
- Info windows showing marker titles and snippets with auto-dismiss
- Navigation between markers with prev/next buttons
- Info window toggle via FAB or marker tap
- Real-time status display (selection index, camera state)
- Visual feedback when info window is shown (red text)

## Screenshot

Expand All @@ -39,30 +38,75 @@ This example demonstrates the marker system in OpenMapView, including marker ren
adb shell am start -n de.afarber.openmapview.example03markers/.MainActivity
```

## Project Structure

```
example03markers/
├── MainActivity.kt # Main activity and MapViewScreen composable
├── MarkerToolbar.kt # Horizontal toolbar with prev/next navigation buttons
├── StatusToolbar.kt # Status overlay showing selection index and camera state
├── MarkerData.kt # Marker data class and Bochum POI locations
└── Constants.kt # Colors, dimensions, and durations
```

## Code Highlights

### Adding Markers - Kotlin Style
### MainActivity.kt

```kotlin
OpenMapView(context).apply {
setCenter(LatLng(51.4661, 7.2491)) // Bochum, Germany
setZoom(14.0)

// Kotlin-style direct instantiation
addMarker(
Marker(
position = LatLng(51.4661, 7.2491),
title = "Bochum City Center",
snippet = "Welcome to Bochum!",
@Composable
fun MapViewScreen() {
val lifecycleOwner = LocalLifecycleOwner.current
var mapView: OpenMapView? by remember { mutableStateOf(null) }
var selectedIndex by remember { mutableIntStateOf(0) }
var selectedMarker: Marker? by remember { mutableStateOf(null) }
var isInfoWindowShown by remember { mutableStateOf(false) }

Box(modifier = Modifier.fillMaxSize()) {
AndroidView(
factory = { ctx ->
OpenMapView(ctx).apply {
lifecycleOwner.lifecycle.addObserver(this)
setCenter(initialLocation)
setZoom(13.0f)
getUiSettings().infoWindowAutoDismiss = 10.seconds

setOnMarkerClickListener { marker ->
selectedMarker = marker
isInfoWindowShown = marker.isInfoWindowShown
true
}
setOnInfoWindowCloseListener {
isInfoWindowShown = false
}
mapView = this
}
},
modifier = Modifier.fillMaxSize(),
)
)

StatusToolbar(selectedIndex, selectedMarker?.title, cameraState, isInfoWindowShown, ...)
MarkerToolbar(onPrevClick = { ... }, onNextClick = { ... })
}
}
```

### Adding Markers - Kotlin Style

```kotlin
addMarker(
Marker(
position = LatLng(51.4783, 7.2231),
title = "Bochum Hauptbahnhof",
snippet = "Main railway station",
icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED),
)
)
```

### Adding Markers - Google Maps Style

```kotlin
// Google Maps API builder pattern
addMarker(
MarkerOptions()
.position(LatLng(51.4650, 7.2500))
Expand All @@ -72,57 +116,51 @@ addMarker(
)
```

### Click Listener
### OSM-Inspired Colors (Constants.kt)

```kotlin
setOnMarkerClickListener { marker ->
val message = buildString {
append(marker.title ?: "Marker")
if (marker.snippet != null) {
append("\n")
append(marker.snippet)
}
}
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
true // Consume the click event
}
val OsmParkGreen = Color(0xFFAAD3A2) // Navigation buttons (prev/next)
val OsmHighwayPink = Color(0xFFE892A2) // Info window toggle FAB
val OsmWaterBlue = Color(0xFFAAD3DF) // Reserved for future use
```

### Key Concepts

- **Marker**: Data class with position, title, snippet, icon, anchor, and tag
- **addMarker()**: Add a marker to the map
- **removeMarker()**: Remove a specific marker
- **clearMarkers()**: Remove all markers
- **getMarkers()**: Get list of all markers
- **setOnMarkerClickListener()**: Handle marker click events
- **Default icon**: Red teardrop shape generated via MarkerIconFactory
- **Custom icons**: Provide your own Bitmap via the `icon` parameter
- **setOnInfoWindowClickListener()**: Handle info window click events
- **setOnInfoWindowCloseListener()**: Handle info window close events (manual or auto-dismiss)
- **infoWindowAutoDismiss**: Auto-dismiss info windows after a duration

## What to Test

1. **Launch the app** - you should see 5 red markers around Bochum
2. **Click a marker** - Toast message shows title and snippet
3. **Pan the map** - markers stay at correct geographic positions
4. **Zoom in/out** - markers remain properly positioned
5. **Click different markers** - each shows its own title/snippet
1. **Launch the app** - you should see 6 colored markers at Bochum landmarks
2. **Tap a marker** - info window shows title and snippet, status text turns red
3. **Tap the same marker again** - info window closes, status text turns black
4. **Tap info window** - toast message confirms the click
5. **Tap prev/next buttons** - navigate between markers with camera animation
6. **Tap the FAB** - toggles info window on selected marker
7. **Wait 10 seconds** - info window auto-dismisses, status text turns black
8. **Pan/zoom the map** - markers stay at correct geographic positions

## Marker Locations

This example displays 5 markers:

| Location | Coordinates | Description |
| -------- | ------------------- | ------------------ |
| Center | 51.4661°N, 7.2491°E | Bochum City Center |
| North | 51.4700°N, 7.2550°E | North Location |
| South | 51.4620°N, 7.2430°E | South Location |
| West | 51.4680°N, 7.2380°E | West Location |
| East | 51.4640°N, 7.2600°E | East Location |
This example displays 6 markers at notable Bochum landmarks:

**Note on marker positioning:** While the 4 outer markers are placed on N, S, W, E sides of the central marker (by adjusting latitude/longitude), they do not appear strictly above, below, left, right on the screen. This is due to the Web Mercator projection used by OpenStreetMap, which distorts distances and angles, especially at higher latitudes. The further from the equator, the more pronounced this distortion becomes.
| Location | Coordinates | Description |
| ----------------- | ------------------- | -------------------- |
| Hauptbahnhof | 51.4783°N, 7.2231°E | Main railway station |
| Ruhr University | 51.4452°N, 7.2622°E | Ruhr-Universitat |
| Rathaus | 51.4816°N, 7.2166°E | City Hall |
| Bermuda3eck | 51.4807°N, 7.2222°E | Entertainment dist. |
| Bergbau-Museum | 51.4892°N, 7.2174°E | Mining Museum |
| Starlight Express | 51.4649°N, 7.2043°E | Musical theater |

## Custom Marker Icons

To use custom marker icons instead of the default red teardrop:
To use custom marker icons instead of the default teardrop:

```kotlin
// Create custom bitmap (e.g., from resources)
Expand Down Expand Up @@ -171,6 +209,6 @@ Click detection uses:

## Map Location

**Default Center:** Bochum, Germany (51.4661°N, 7.2491°E) at zoom 14.0
**Default Center:** Calculated from marker positions (~51.47°N, 7.22°E) at zoom 13.0

All 5 markers are positioned around Bochum within ~1km radius.
All 6 markers are positioned around Bochum at real landmark locations.
Binary file modified examples/Example03Markers/screenshot.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2025 Alexander Farber
* SPDX-License-Identifier: MIT
*
* This file is part of the OpenMapView project (https://github.com/afarber/OpenMapView)
*/

package de.afarber.openmapview.example03markers

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

/**
* Constants for the Example03Markers app: colors, dimensions, and durations.
*/

/** Green color used by OpenStreetMap for parks and forests. */
val OsmParkGreen = Color(0xFFAAD3A2)

/** Pink color used by OpenStreetMap for highways and major roads. */
val OsmHighwayPink = Color(0xFFE892A2)

/** Blue color used by OpenStreetMap for water areas (lakes, rivers). */
val OsmWaterBlue = Color(0xFFAAD3DF)

/** Shared corner radius for all toolbar components (matches Material3 FAB). */
val ToolbarCornerRadius = 16.dp

/** Duration after which info windows are automatically dismissed. */
val InfoWindowAutoDismissDuration: Duration = 5.seconds
Loading