|
| 1 | +# Example08Circles - Circle Overlays with Meter-Based Radius |
| 2 | + |
| 3 | +This example demonstrates the circle system in OpenMapView, including circle rendering with meter-based radius, styling options, z-index ordering, and both API patterns. |
| 4 | + |
| 5 | +## Features Demonstrated |
| 6 | + |
| 7 | +- Multiple circles with different radii (in meters) |
| 8 | +- Both API styles: Kotlin direct instantiation and Google Maps builder pattern |
| 9 | +- Circle styling (stroke color, fill color, stroke width) |
| 10 | +- Z-index ordering for overlapping circles |
| 11 | +- Circle click detection with hit testing |
| 12 | +- Clickable circles with touch event handling |
| 13 | +- Toast notifications showing circle properties |
| 14 | +- FAB buttons for adding random circles and clearing all |
| 15 | +- Meter-to-pixel conversion using Mercator projection |
| 16 | + |
| 17 | +## Screenshot |
| 18 | + |
| 19 | + |
| 20 | + |
| 21 | +## Quick Start |
| 22 | + |
| 23 | +### Option 1: Run in Android Studio |
| 24 | + |
| 25 | +1. Open the OpenMapView project in Android Studio |
| 26 | +2. Select `examples.Example08Circles` from the run configuration dropdown |
| 27 | +3. Click Run (green play button) |
| 28 | +4. Deploy to your device or emulator |
| 29 | + |
| 30 | +### Option 2: Build and Install from Command Line |
| 31 | + |
| 32 | +```bash |
| 33 | +# From project root - build, install, and launch |
| 34 | +./gradlew :examples:Example08Circles:installDebug |
| 35 | + |
| 36 | +# Launch the app |
| 37 | +adb shell am start -n de.afarber.openmapview.example08circles/.MainActivity |
| 38 | +``` |
| 39 | + |
| 40 | +## Code Highlights |
| 41 | + |
| 42 | +### Adding Circles - Google Maps Style |
| 43 | + |
| 44 | +```kotlin |
| 45 | +// CircleOptions builder pattern (Google Maps compatible) |
| 46 | +mapView.addCircle( |
| 47 | + CircleOptions() |
| 48 | + .center(LatLng(51.4661, 7.2491)) |
| 49 | + .radius(500f) // Radius in meters |
| 50 | + .strokeColor(Color.RED) |
| 51 | + .strokeWidth(5f) |
| 52 | + .fillColor(Color.argb(64, 255, 0, 0)) |
| 53 | + .clickable(true) |
| 54 | + .zIndex(2f) |
| 55 | + .tag("Small Red Circle - 500m") |
| 56 | +) |
| 57 | +``` |
| 58 | + |
| 59 | +### Adding Circles - Kotlin Style |
| 60 | + |
| 61 | +```kotlin |
| 62 | +// Direct instantiation (Kotlin-idiomatic) |
| 63 | +mapView.addCircle( |
| 64 | + Circle( |
| 65 | + center = LatLng(51.4661, 7.2491), |
| 66 | + radius = 750f, // Radius in meters |
| 67 | + strokeColor = Color.MAGENTA, |
| 68 | + strokeWidth = 6f, |
| 69 | + fillColor = Color.argb(64, 255, 0, 255), |
| 70 | + clickable = true, |
| 71 | + zIndex = 1.5f, |
| 72 | + tag = "Kotlin Style Circle - 750m" |
| 73 | + ) |
| 74 | +) |
| 75 | +``` |
| 76 | + |
| 77 | +### Circle Click Listener |
| 78 | + |
| 79 | +```kotlin |
| 80 | +setOnCircleClickListener { circle -> |
| 81 | + val tagStr = circle.tag?.toString() ?: "Unknown Circle" |
| 82 | + val coordStr = "%.4f, %.4f".format( |
| 83 | + circle.center.latitude, |
| 84 | + circle.center.longitude |
| 85 | + ) |
| 86 | + Toast.makeText( |
| 87 | + context, |
| 88 | + "$tagStr\nCenter: $coordStr\nZ-Index: ${circle.zIndex}", |
| 89 | + Toast.LENGTH_SHORT |
| 90 | + ).show() |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +### Key Concepts |
| 95 | + |
| 96 | +- **Circle**: Data class with center (LatLng), radius (meters), styling, and z-index |
| 97 | +- **CircleOptions**: Fluent builder for Google Maps API compatibility |
| 98 | +- **Radius in meters**: Real-world distances, converted to pixels via Mercator projection |
| 99 | +- **addCircle()**: Accepts both Circle objects and CircleOptions builders |
| 100 | +- **removeCircle()**: Remove a specific circle |
| 101 | +- **clearCircles()**: Remove all circles |
| 102 | +- **setOnCircleClickListener()**: Handle circle click events |
| 103 | +- **Z-index ordering**: Controls draw order (higher = on top) |
| 104 | + |
| 105 | +## What to Test |
| 106 | + |
| 107 | +1. **Launch the app** - you should see 4 circles with different colors and sizes |
| 108 | +2. **Click a circle** - Toast shows circle properties (tag, center, z-index) |
| 109 | +3. **Observe z-ordering** - Small red circle (z=2) appears on top |
| 110 | +4. **Click FAB (+)** - Adds a random circle with random radius and color |
| 111 | +5. **Click FAB (×)** - Clears all circles |
| 112 | +6. **Zoom in/out** - Circles scale correctly maintaining meter-based radius |
| 113 | +7. **Pan the map** - Circles stay at correct geographic positions |
| 114 | + |
| 115 | +## Circle Specifications |
| 116 | + |
| 117 | +The demo includes 4 initial circles: |
| 118 | + |
| 119 | +| Circle | Center Offset | Radius | Color | Z-Index | Tag | |
| 120 | +| ------ | ------------- | ------ | ------- | ------- | -------------------------- | |
| 121 | +| 1 | (0, 0) | 500m | Red | 2.0 | Small Red Circle - 500m | |
| 122 | +| 2 | (+0.01, +0.01)| 1000m | Blue | 1.0 | Medium Blue Circle - 1000m | |
| 123 | +| 3 | (-0.01, -0.01)| 1500m | Green | 0.0 | Large Green Circle - 1500m | |
| 124 | +| 4 | (+0.005, -0.015)| 750m | Magenta | 1.5 | Kotlin Style Circle - 750m | |
| 125 | + |
| 126 | +**Z-Index Visual Order:** Green (bottom, z=0) → Blue (z=1) → Magenta (z=1.5) → Red (top, z=2) |
| 127 | + |
| 128 | +## Technical Details |
| 129 | + |
| 130 | +### Meter-to-Pixel Conversion |
| 131 | + |
| 132 | +Circles use real-world meter-based radius, converted to screen pixels using the Mercator projection formula: |
| 133 | + |
| 134 | +```kotlin |
| 135 | +metersPerPixel = 156543.03392 * cos(latitude) / 2^zoom |
| 136 | +radiusInPixels = radius / metersPerPixel |
| 137 | +``` |
| 138 | + |
| 139 | +This ensures circles maintain accurate geographic size at all zoom levels. |
| 140 | + |
| 141 | +### Circle Rendering Order |
| 142 | + |
| 143 | +Circles are drawn in this order: |
| 144 | +1. Polygons (filled shapes) |
| 145 | +2. **Circles** (between polygons and polylines) |
| 146 | +3. Polylines (line shapes) |
| 147 | +4. Markers (on top) |
| 148 | + |
| 149 | +Within the same shape type, z-index determines order (lower z-index drawn first). |
| 150 | + |
| 151 | +### Touch Detection |
| 152 | + |
| 153 | +Click detection uses distance calculation: |
| 154 | +- Compute distance from touch point to circle center |
| 155 | +- Hit if distance ≤ radius (in pixels) + strokeWidth/2 |
| 156 | +- Checks circles in reverse order (top to bottom) for correct z-ordering |
| 157 | + |
| 158 | +## Styling Options |
| 159 | + |
| 160 | +### Stroke Customization |
| 161 | + |
| 162 | +```kotlin |
| 163 | +CircleOptions() |
| 164 | + .strokeColor(Color.RED) // Outline color |
| 165 | + .strokeWidth(10f) // Outline width in pixels |
| 166 | +``` |
| 167 | + |
| 168 | +### Fill Customization |
| 169 | + |
| 170 | +```kotlin |
| 171 | +CircleOptions() |
| 172 | + .fillColor(Color.argb(128, 255, 0, 0)) // Semi-transparent fill |
| 173 | +``` |
| 174 | + |
| 175 | +### Alpha transparency in fill color allows overlapping circles to show through. |
| 176 | + |
| 177 | +## Interactive Features |
| 178 | + |
| 179 | +### Add Random Circles |
| 180 | + |
| 181 | +The floating action button (+) adds random circles: |
| 182 | +- Random center within ~1.5km of Bochum |
| 183 | +- Random radius: 300-1500 meters |
| 184 | +- Random color (RGB) |
| 185 | +- Random stroke width: 3-12 pixels |
| 186 | +- Random z-index: 0-5 |
| 187 | +- Clickable with auto-generated tag |
| 188 | + |
| 189 | +### Clear All Circles |
| 190 | + |
| 191 | +The floating action button (×) removes all circles from the map. |
| 192 | + |
| 193 | +## Next Steps |
| 194 | + |
| 195 | +- Try [Example03Markers](../Example03Markers) for marker overlays |
| 196 | +- Try [Example04Polylines](../Example04Polylines) for polylines and polygons |
| 197 | +- Try [Example06Clicks](../Example06Clicks) for map click listeners |
| 198 | +- Modify circle radii to experiment with different sizes |
| 199 | +- Try overlapping circles to see z-index ordering in action |
| 200 | +- Add circles at your own locations |
| 201 | + |
| 202 | +## Map Location |
| 203 | + |
| 204 | +**Default Center:** Bochum, Germany (51.4661°N, 7.2491°E) at zoom 12.0 |
| 205 | + |
| 206 | +All circles are positioned around Bochum within ~2km radius. |
0 commit comments