VeloSpot v1.0.19
VeloSpot v1.0.19
Added
- Cyclist avatar as the live-location marker β the plain blue location dot (and the green navigation heading-arrow puck) is replaced by a full-colour 2D cyclist sprite shown both on the idle map and during turn-by-turn navigation. It's a clean 3rd-person / top-down rider (helmet, jersey shoulders, arms reaching to the handlebar, frame and two wheels with rims, legs on the pedals) drawn with a soft contact shadow and a thin white keyline so it pops on any basemap (new
R.drawable.ic_cyclist_avatar, reworkedcreateLocationMarkerIconwith a stamped outline). The sprite is rendered as an upright billboard (iconRotationAlignment/iconPitchAlignment = viewport) so the tilted 3D navigation map never flattens or squishes it; since the navigation camera keeps the heading pointing "up", the rider naturally appears from behind for a true 3rd-person feel. The now-unusedcreateNavigationArrowIconarrow puck was removed. - Ride tracking β record your ride as a timeline ("My rides") β riders can now record a ride and review it afterwards with full statistics:
- One-tap recording from a dedicated red record/stop FAB stacked above the My location button (keeps the already-busy menu uncluttered). While recording, a compact live-stats card at the top of the map shows the running time, distance and current speed, with Stop and Discard actions, and the travelled track is drawn live on the map as a coloured polyline (new
velospot-tracksource/layer). - Automatic recording during navigation β starting turn-by-turn navigation auto-starts a recording for the whole trip and saves it when navigation ends (or on auto-park arrival); a manually-started recording is never interrupted by navigation.
- Persistent history in a dedicated, isolated Room database (
RidesDatabase/recorded_rides, independent of the parking and saved-places stores) β a new "My rides" menu entry opens a timeline list of past rides (date, distance, duration, average speed). - Ride detail with statistics + speed timeline β tapping a ride redraws its track on the map and opens a sheet with moving time, max speed, elevation gain/loss and a Canvas-drawn speed-over-time chart, plus a delete action.
- Elevation support β elevation gain/loss now comes from BRouter's accurate terrain data (the SRTM elevation baked into its
.rd5segment files, read per route node βRoutePoint.elevationMeters) whenever a ride is recorded while navigating offline; the rider's live position is snapped to the route and its terrain elevation is fed to the tracker instead of the very noisy raw GPS altitude. For manual rides (or the online OSRM fallback, which carries no elevation) it falls back to GPS altitude, now low-pass filtered with a 3 m dead-band so a parked bike no longer racks up phantom metres (GeoCoordinate.altitudeMeters, pure unit-testedRideTracker). GPS altitude is requested whenever navigation or a recording is active. Fully localised across all eight supported languages and covered by newRideTrackerunit tests.
- One-tap recording from a dedicated red record/stop FAB stacked above the My location button (keeps the already-busy menu uncluttered). While recording, a compact live-stats card at the top of the map shows the running time, distance and current speed, with Stop and Discard actions, and the travelled track is drawn live on the map as a coloured polyline (new
- Follow camera you can break and re-lock β for navigation and recording β the map now stays centred on the live position not only during turn-by-turn navigation but also while recording a ride. In both modes you can freely pan/zoom the map by hand mid-trip: a touch gesture unlocks the follow camera (the heading arrow / location keeps tracking and route progress keeps updating), and a dedicated re-centre button appears on the map (an extended FAB stacked clear of the location/record buttons) that snaps the camera back onto you and resumes following until you pan again. The button disappears and the lock is dropped automatically once neither navigation nor a recording is active. Navigation's per-frame 3D camera glides smoothly back from wherever you left the map (
MapViewModel.isFollowingLocation,NavigationManager.setFollowing, gesture detection viaREASON_API_GESTURE). Fully localised across all eight supported languages.
Changed
- Faster, interruptible navigation start-up tilt-in β when navigation begins, the camera's "tilt-in" into the 3D view eases in noticeably faster (zoom/pitch time constants scaled by 0.28) and snaps the last imperceptible bit so it finishes crisply instead of lingering on the exponential tail. Crucially the intro is now interruptible: a pan/zoom touch is detected directly on the map and instantly cancels the intro and breaks follow, so the map reacts to your gesture without any wait (no more hard input lock). Follow resumes via the re-centre button (
NavigationManagerintro phase + direct gesture handling). - Ride tracking β GPS drift filtering + moving-average smoothing β recorded tracks no longer show the long "spike" lines and implausible peak speeds (e.g. 95 km/h on a leisurely ride) that GPS multipath produces in urban canyons / under 3D building shadow. Each fix now carries its horizontal accuracy (
GeoCoordinate.accuracyMeters/TrackPoint.accuracyMeters, read fromLocation.accuracyin both the Google-Play and F-Droid location sources) and the pureRideTrackerrejects a fix outright when its accuracy is worse than 30 m or when the implied segment speed exceeds a plausible bike bound (lowered from ~108 to ~90 km/h). Rejected fixes are no longer appended to the track, counted towards distance/max-speed, or drawn on the live polyline (theRideTrackingControllernow mirrors only accepted points onto the map). Accepted positions are additionally passed through a small moving-average window (3 fixes) so the residual side-to-side jitter is smoothed out, while distance/speed/moving-time are still measured on the raw fixes so the totals stay accurate. Covered by newRideTrackerunit tests. - Favourites isolated in their own database (no more accidental wipes) β user favourites used to share the asset-seeded
BikeParkingDatabase, which is configured withfallbackToDestructiveMigration; any future parking-schema bump without an explicit migration would have silently dropped the favourites along with the parking data. Favourites now live in a dedicated, isolatedFavoritesDatabase(mirroringSavedPlacesDatabase/RidesDatabase) that a parking-data migration can never touch. Existing favourites are copied over once from the legacy table on first launch (ATTACH β¦ INSERT OR IGNORE β¦ SELECT, guarded by a one-time flag); the legacy parking-database schema is left byte-for-byte unchanged so the bundled SQLite asset still validates. - Per-API HTTP clients with appropriate timeouts β the single shared
OkHttpClientwas split into dedicated,@Namedclients inNetworkModule: a Nominatim client (carrying the new rate limiter), a segments client with a 5-minute read timeout for the 100 MB+ BRouter offline-routing downloads (so a slow-but-progressing download is no longer aborted at 30 s), and the default client for OSRM/everything else. - OSRM DTOs use Moshi code-gen adapters β
OsrmRouteResponseDto/OsrmRouteDto/OsrmGeometryDtonow carry@JsonClass(generateAdapter = true), matching the Nominatim DTOs (faster parsing, ProGuard-safe, no reflection fallback). - Single source of truth for the OSRM host β the OSRM bicycle URL is no longer duplicated;
OsrmApi.getBikeRoutetakes a relative path resolved against the one base URL configured inNetworkModule, removing the constant-drift risk betweenNetworkModuleandRoutingRepositoryImpl. - Faster ride-track drawing during recording β the live ride polyline used to be rebuilt from scratch on every GPS fix (re-mapping the entire, ever-growing track β O(nΒ²) work and N fresh
RoutePointallocations per ride), which churned the GC and triggered redundant recompositions on long rides. Since each fix appends exactly one point, the track now grows by a single incremental append. The route-elevation lookup was likewise changed from a full route rescan per fix to a cursor that resumes from the last match (amortised ~O(1)), so accurate BRouter terrain elevation no longer costs an O(route) scan on every fix. - Smoother navigation on long routes β skip redundant route-line rebuilds β during live navigation the travelled/remaining route split (
NavigationManager.renderRouteSplit) was rebuilt from scratch on every GPS fix (~1 Hz), reconstructing the full polyline (O(route)) and re-uploading it to the GPU even when the rider had barely moved within the same segment β a source of frame drops on long routes and lower-end devices. The rebuild is now skipped while the rider stays on the same route segment and the snapped position has moved less than 12 m (the imperceptible shift of the single split-boundary point); the smoothly-eased location puck, redrawn every frame, still carries the live motion. Forced renders on route start and after a style reload keep the line correct. MapViewModelsplit into focused controllers (Single Responsibility) β the ~1170-line map view-model that mixed address search, offline routing, ride tracking, saved places, the parked bike and live navigation was decomposed into six small, independently testable controllers (AddressSearchController,OfflineRoutingController,RideTrackingController,SavedPlacesController,ParkedBikeController,NavigationController), shrinking the view-model to ~650 lines. Each owns its own state; the view-model now orchestrates and re-exposes their flows, and cross-feature effects (camera, selection reset, location-accuracy, toasts, geocoding, route calculation, auto-park-on-arrival, off-route reroute and the debug GPS simulator) are passed as callbacks. Pure internal refactor β the public view-model API and all exposed UI state are unchanged.
Removed
- Dead Overpass live-fetch code β the unused
OverpassApi,OverpassDtoandOverpassMapper(leftovers from the pre-bundled-database era), plus the never-calledwriteSpaces/readSpaces/lastSyncEpochMscache methods and the now-orphanedtoEntity/toEntitiesmappers. Parking data is served read-only from the bundled SQLite asset.
Fixed
- Cyclist avatar no longer disappears when toggling dark mode mid-navigation β switching the theme reloads the map style, which wipes all custom sources/layers/images. During navigation the normal renderer deliberately skips re-creating
SOURCE_LOCATION(suppressLocationDot, sinceNavigationManagerowns the puck), andwritePuckonly updated an already-existing source β so after a reload the rider vanished until navigation ended.NavigationManager.writePucknow upserts (create-or-update) the location source, so the avatar is redrawn immediately after a style reload. - Nominatim rate limiting (OSM usage policy) β a new
NominatimRateLimitInterceptoron the dedicated Nominatim client enforces the required β€ 1 request/second, so rapid user actions (e.g. tapping several parking pins to resolve addresses) can no longer burst past the limit and risk an IP ban. - OSRM HTTP errors handled gracefully β
OsrmApi.getBikeRoutenow returnsResponse<β¦>; the fallback routing surfaces 4xx/5xx as aRoutingFailedExceptioninstead of throwing a raw exception.
π₯ APK Variants
| File | Description |
|---|---|
VeloSpot-v1.0.19.apk |
F-Droid flavor β no Google services, ready to sideload; this is the APK F-Droid reproducibly verifies |
VeloSpot-v1.0.19-googlePlay.apk |
Google Play flavor β includes Google Play Services Location |
Installation (sideload)
- Download the APK below.
- On your Android device: Settings β Apps β Install unknown apps β allow your browser or file manager.
- Open the APK and tap Install.
The F-Droid flavor is also available on F-Droid.