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 @@ -23,7 +23,7 @@ The library is available on [Maven Central](https://central.sonatype.com/artifac

## License

OpenMapView is MIT licensed, so you can use it in commercial apps. Its five runtime dependencies (androidx core-ktx, lifecycle-runtime-ktx, Compose ui-graphics, ktor-client-android and DiskLruCache) are all Apache 2.0. See the [Dependency License Report](https://afarber.github.io/OpenMapView/licenses/) for details.
OpenMapView is MIT licensed, so you can use it in commercial apps. Its four runtime dependencies (androidx core-ktx, lifecycle-runtime-ktx, Compose ui-graphics and ktor-client-android) are all Apache 2.0. See the [Dependency License Report](https://afarber.github.io/OpenMapView/licenses/) for details.

## Features

Expand Down
1 change: 0 additions & 1 deletion docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ Before adding new dependencies to OpenMapView, verify the following:
- androidx.lifecycle:lifecycle-runtime-ktx
- androidx.compose.ui:ui-graphics
- io.ktor:ktor-client-android
- com.jakewharton:disklrucache

## Automated Git Hooks (Recommended)

Expand Down
31 changes: 18 additions & 13 deletions docs/PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ OpenMapView implements a two-tier cache hierarchy:
│ Level 2: Disk Cache (LRU) │
│ - Persistent across app restarts │
│ - 50MB maximum size │
│ - PNG compressed tiles │
│ - Tiles as sent by the server │
└─────────────────┬───────────────────────┘
│ Cache miss
v
Expand All @@ -92,22 +92,25 @@ Implemented using Android's `LruCache`:
New tiles are written to both levels as soon as they are downloaded, so they survive the process even if they are never evicted from memory:

```kotlin
fun put(tile: TileCoordinate, bitmap: Bitmap) {
fun put(tile: TileCoordinate, bitmap: Bitmap, bytes: ByteArray) {
memoryCache.put(tile, bitmap)
diskCache?.put(tile, bitmap) // called from the IO download coroutine
diskCache?.put(tile, bytes) // the server's bytes, called from the IO download coroutine
}
```

### Disk Cache (Level 2)

Implemented using Jake Wharton's DiskLruCache library:
`DiskTileCache` keeps one file per tile, without any extra library:

- **Access time:** Milliseconds (disk I/O)
- **Size:** 50MB maximum
- **Format:** PNG compressed images
- **Location:** App cache directory (`context.cacheDir/tiles`)
- **Format:** The PNG or JPEG bytes as sent by the tile server, so tiles are never re-encoded. They are decoded with RGB_565, the same as fresh downloads
- **Location:** App cache directory (`context.cacheDir/tiles2/<source>/<z>_<x>_<y>.tile`)
- **Persistence:** Survives app restarts
- **Eviction:** LRU with size limit
- **Eviction:** When the 50MB are exceeded, the least recently used tiles are deleted until 45MB are left
- **Expiry:** Tiles older than 7 days (the minimum of the OSM tile usage policy) are downloaded again. Reading a tile does not make it younger, the download time is stored in the file
- **Offline fallback:** If downloading an expired tile fails, the old tile is shown and retried with the usual backoff (see [Retry Backoff](#retry-backoff))
- **Writes:** Each tile is written to a temporary file and renamed, so an interrupted write never leaves a broken tile

**Benefits:**
- Reduces network requests on app restart
Expand All @@ -119,7 +122,7 @@ Implemented using Jake Wharton's DiskLruCache library:
When a tile is found in disk cache, it's promoted back to memory cache:

```kotlin
val diskBitmap = diskCache?.get(tile)
val diskBitmap = diskCache?.get(tile) // null if missing or expired
if (diskBitmap != null) {
// Promote to memory for faster subsequent access
memoryCache.put(tile, diskBitmap)
Expand Down Expand Up @@ -274,10 +277,13 @@ Tile downloads use Kotlin coroutines for efficient async I/O:

```kotlin
scope.launch(Dispatchers.IO) {
tileDownloadSemaphore.withPermit {
val bitmap = tileDownloader.downloadTile(url)
// ...
}
// Disk hits don't wait for a download permit
val bitmap = cache.get(tile) ?: tileDownloadSemaphore.withPermit {
tileDownloader.downloadTileBytes(url)?.let { bytes ->
decodeTile(bytes)?.also { cache.put(tile, it, bytes) }
}
} ?: cache.getStale(tile) // expired tile as offline fallback
// ...
}
```

Expand Down Expand Up @@ -347,5 +353,4 @@ For optimal performance:

- [Android Bitmap Best Practices](https://developer.android.com/topic/performance/graphics)
- [OSM Tile Usage Policy](https://operations.osmfoundation.org/policies/tiles/)
- [DiskLruCache](https://github.com/JakeWharton/DiskLruCache)
- [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-guide.html)
7 changes: 4 additions & 3 deletions docs/TESTING_UNIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The `--continue` flag ensures all tests run even if some fail, useful for gettin

## Test Structure

### Current Test Coverage (461 tests across 35 test classes)
### Current Test Coverage (482 tests across 36 test classes)

| Test Class | Tests | Description |
|------------|-------|-------------|
Expand All @@ -61,7 +61,7 @@ The `--continue` flag ensures all tests run even if some fail, useful for gettin
| **CameraPositionTest** | 7 | Camera position validation and equality |
| **CameraUpdateFactoryTest** | 11 | Camera update creation (zoom, pan, position) |
| **CircleTest** | 4 | Circle defaults, radius validation, identity equality |
| **DiskTileCacheTest** | 8 | Persistent disk cache with DiskLruCache, per-source namespaces |
| **DiskTileCacheTest** | 18 | Persistent disk cache: raw tile bytes, expiry, LRU trimming, atomic writes, per-source namespaces |
| **GeoJsonParserTest** | 14 | GeoJSON parsing (Point, LineString, Polygon, Multi*, Features) |
| **GeodesicUtilsTest** | 17 | Great-circle interpolation for geodesic shapes |
| **GroundOverlayTest** | 10 | Ground overlay position/bounds validation and equality |
Expand All @@ -70,6 +70,7 @@ The `--continue` flag ensures all tests run even if some fail, useful for gettin
| **LatLngTest** | 11 | Latitude clamping and longitude wrapping |
| **MapControllerOverlaysTest** | 13 | Circles, ground and tile overlays, their touch detection, info windows, drawing all shape types |
| **MapControllerTest** | 103 | Zoom, pan, marker/polyline/polygon management, touch detection, padding, camera bounds, edge effects |
| **MapControllerTileLoadingTest** | 7 | Base tile loading order (disk, network, stale fallback) and retry backoff |
| **MapFeaturesTest** | 5 | Feature query methods (traffic, indoor, buildings, myLocation) |
| **MapProjectionTest** | 8 | Projection screen/LatLng conversion, pan offset, padding, visible region |
| **MapTypeTest** | 18 | Map type switching and validation |
Expand All @@ -81,7 +82,7 @@ The `--continue` flag ensures all tests run even if some fail, useful for gettin
| **PredefinedTileProvidersTest** | 2 | URL templates of the predefined overlay tile providers, Stadia key lookup |
| **ProjectionTest** | 12 | Web Mercator projection calculations |
| **SnapshotTest** | 8 | Map snapshot API and callbacks |
| **TileCacheTest** | 9 | Memory LRU cache behavior, clearing, namespaces, disk writes |
| **TileCacheTest** | 13 | Memory LRU cache behavior, clearing, namespaces, disk writes, stale tiles |
| **TileDownloaderTest** | 5 | HTTP tile downloading with mocked Ktor client, HTTP errors, cancellation |
| **TileOverlayTest** | 5 | Tile overlay defaults, transparency validation, copy helpers |
| **TileRetryTrackerTest** | 4 | Exponential backoff for failed tiles |
Expand Down
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ compose-material-icons = "1.7.8"
ktor = "3.6.0"

# Utilities
disklrucache = "2.0.2"

# Testing
junit = "4.13.2"
Expand Down Expand Up @@ -56,7 +55,6 @@ ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version
ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" }

# Utilities
disklrucache = { group = "com.jakewharton", name = "disklrucache", version.ref = "disklrucache" }

# Testing - Unit
junit = { group = "junit", name = "junit", version.ref = "junit" }
Expand Down
1 change: 0 additions & 1 deletion openmapview/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.ktor.client.android)
implementation(libs.disklrucache)

// Unit testing
testImplementation(libs.junit)
Expand Down
Loading
Loading