VeloSpot v1.0.22
VeloSpot v1.0.22
Added
- Ride heatmap overlay β a new Ride heatmap map layer turns your recorded rides into a colour heatmap that reveals where you cycle most. All recorded GPS tracks are aggregated into a compact grid (β11 m cells) weighted by how often you've ridden through each spot, so frequently used streets glow hotter (cool blue β red). Toggle it in Layers; it's off by default and persisted, sits beneath the map pins (which stay tappable), and is built/aggregated off the main thread. The pure aggregation logic (
RideHeatmap) is unit-tested and the overlay reuses the existing recorded-ride data, so no extra storage is needed. Fully localised across all eight supported languages (MapLayerCategory.HEATMAP,updateHeatmapLayer). - Spoken turn-by-turn voice guidance (TTS) β navigation can now read the upcoming-turn instructions aloud via Android
TextToSpeech, building on the existing on-screen turn banner. It speaks an early prepare cue when a turn comes within ~150 m ("In 150 m, turn left"), a final imminent cue within ~30 m ("Now turn left") and an arrival cue at the destination; each cue fires once per turn (re-arming after you pass it), and off-route situations are suppressed until a reroute lands. A new "Voice guidance" switch in the Settings sheet (Appearance & map) toggles it β disabled by default (opt-in) and persisted across sessions. The decision logic (NavigationVoiceCues) is pure and unit-tested, the engine wrapper (NavigationVoiceGuide) matches the app locale and uses navigation-guidance audio attributes, and the flag lives inVoiceGuidancePreferences. Fully localised across all eight supported languages. - "Route hilliness" slider β flatter offline routes on demand β the offline Routing Profile sheet now carries a discrete Route hilliness slider with five steps (Any β Gentle β Flatter β Low climb β Flattest) that lets you trade a bit of distance for less climbing. Each step maps to an extra uphill penalty (
ElevationPreference.uphillExtraCost) handed to BRouter as a uniformuphill_extraprofile parameter, which is added on top of every bundled profile's own uphill cost β so Any leaves routing unchanged and flatter levels progressively avoid ascents. The choice is persisted (OfflineRoutingPreferences.getElevationPreference) and applied to point-to-point, on-demand and round-trip routing; changing it while navigating immediately recomputes the active route. Offline (BRouter) only β the online OSRM fallback has no elevation model and ignores it. Theuphill_extraparameter was added to all five profiles (trekking,fastbike,mtb,gravel,shortest,PROFILES_VERSION4 β 5) and is parse-checked byBRouterProfileIntegrityTest; the levelβcost mapping is covered byElevationPreferenceTest. Fully localised across all eight supported languages.
Fixed
- No profile starts the route on the sidewalk anymore β opening navigation on the pavement was possible in two ways, both now closed for every bundled profile (
PROFILES_VERSION4 β 8): (1) the start-snapping guard (check_start_way+noStartWay=footway,sidewalk) was only in trekking/gravel, so fastbike/mtb/shortest could snap the start onto afootway=sidewalkβ it's now declared in all five; and (2) cycling afootway=sidewalkwas cheaper than the parallel carriageway in several profiles, so the route hugged the pavement: trekking/shortest rode a bicycle-allowed sidewalk almost for free, and the MTB profile (which heavily penalises paved roads) made even a plain sidewalk cheaper than a residential/tertiary road. Afootway=sidewalksurcharge now keeps every profile on the carriageway (the MTB surcharge is large enough to beat its high road cost). A regression test (BRouterNoStartWayProbeTest) asserts every profile both populatesnoStartWaysand prices a sidewalk above a tertiary road. - Round trips now work with every cycling profile β generating a Round trip with a profile that enables
consider_elevation/consider_forest/consider_river(trekking, fastbike, mtb) silently produced no route ("route data incomplete"). With no explicit start heading, BRouter derived one viagetRandomDirectionFromData, which for those profiles reads area-info data and parses adummy.brfthat VeloSpot doesn't bundle. The round-trip generator now always hands BRouter a concrete start direction (a random heading when the rider doesn't pick one), bypassing that path entirely so loops build for every profile (BRouterEngine.calculateRoundTrip, regression-guarded byBRouterRoundTripDirectionTest). - Offline routing no longer fails when the start-U-turn correction can't reroute β the standstill second pass (which re-runs BRouter with a forced forward direction to drop a spurious start hairpin) used to overwrite the valid first-pass route, so if BRouter couldn't satisfy the forced direction (one-way nets, dead-ends) the whole request failed with "route data incomplete". The second pass is now best-effort: its result is only adopted when it actually produces a route, otherwise the good first-pass route is kept (
BRouterEngine.calculateRoute). - Offline routing with "Any" hilliness no longer breaks on some profiles β selecting Any used to send routing down a different BRouter code path (no key-value injection, a different profile-cache key) than the other levels, which left some profiles (e.g. gravel) unable to produce a route on Any while every other setting worked. The
uphill_extraparameter is now always passed as a key-value map β Any simply carries a zero penalty β so every hilliness level uses the identical, working path (BRouterEngine.elevationKeyValues, guarded byBRouterProfileIntegrityTest).
π₯ APK Variants
| File | Description |
|---|---|
VeloSpot-v1.0.22.apk |
F-Droid flavor β no Google services, ready to sideload; this is the APK F-Droid reproducibly verifies |
VeloSpot-v1.0.22-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.