VeloSpot v1.0.18
VeloSpot v1.0.18
Added
- More countries β France π«π· and Luxembourg π±πΊ bike-parking coverage β the bundled OSM parking dataset is no longer Germany-only. Two additional pre-built SQLite assets (
app/src/main/assets/bike_parking_france.db,bike_parking_luxembourg.db, generated by the samescripts/extract_osm_parking.py) are now merged into the single on-device database, so the map's bounding-box queries transparently return parking spots in these countries too. Implemented as a data-only Room migration (BikeParkingDatabasev3 β v4) that bulk-imports the extra datasets into the Germany-seededbike_parking_spacestable on first open / app upgrade β preserving existing favorites and Germany data (INSERT OR IGNORE, OSM element IDs are globally unique). - Multi-country address search with home-country bias β the Nominatim forward-geocoding search now covers all bundled countries (
countrycodes=de,fr,lu) instead of Germany only. When the user's location is known, results are biased toward their surroundings via aviewbox(β 110 km half-span) withbounded=0, so matches in the country the user is currently in rank first without excluding the other countries (NominatimApi.search,NominatimGeocoder.searchAddress,MapViewModel.onSearchQueryChanged). - About screen β a new "About" entry in the menu opens a sheet showing the app name, a link to the website (https://velospot.app), the per-country dataset status (Germany 08.08.2026, France & Luxembourg 18.06.2026) and a link to the privacy policy (https://velospot.app/privacy). Fully localised across all eight supported languages (
AboutSheet). - Keep the screen awake during navigation β while a route is being navigated the display no longer dims or locks; the
keepScreenOnflag is set on the map view for the duration of active navigation and cleared automatically when navigation ends (MainMapScreen). - "Where did I park my bike?" β remember your parked-bike location β a new feature lets riders save exactly where they left their bike and find it again later:
- Park your bike from the map menu ("Park bike here" drops the marker at your current GPS position) or from any tapped custom pin (a "Park bike here" action in the pin sheet), with a confirmation toast.
- Persistent amber bike marker β a distinctive amber pin carrying a white bike glyph (
createParkedBikeIcon(), newvelospot-parked-bikesource/layer/image) stays on the map across app restarts until you collect the bike, clearly distinct from the green saved-place star, blue custom pin and red search pin. - Find-my-bike sheet β tapping the marker (or the menu entry, which then reads "My parked bike") opens a sheet showing how long ago you parked, the reverse-geocoded address, and the live distance from your current position, with Navigate to my bike (full in-app routing) and I picked up my bike (clears the marker) actions.
- Lightweight, isolated persistence β exactly one parked bike is stored at a time in its own
SharedPreferences-backed store, exposed reactively (ParkedBikeRepository+ impl, new domain modelParkedBike, Hilt provider andMapViewModelwiring:parkBikeAtCurrentLocation,parkBikeAt,showParkedBike,navigateToParkedBike,pickUpBike). Fully localised across all eight supported languages and covered by newMapViewModelunit tests. - Auto-park on arrival β when you navigate to a genuine bike parking spot, reaching it is detected automatically (the live route progress drops below a 25 m arrival radius) and the bike is parked at the destination without any extra tap, ending navigation and dropping the persistent marker with an "arrived β bike parked here" confirmation (
maybeAutoParkOnArrivalinMapViewModel, reusing the existingNavigationManagerroute-progress tracking; only real map parking spots auto-park β synthetic destinations like custom pins, address-search results, saved places and the parked bike itself never do). Covered by two new unit tests. - Bigger, more legible parked-bike pin β the amber marker icon was scaled up (~18 %) so it reads clearly at a glance, and the parked-bike detail sheet's action buttons now match the standard button height used across the saved-place and custom-pin sheets (the bike-rack detail sheet's previously taller buttons were aligned to the same default).
- No more pin overlap at the parked spot β when the bike is parked on a real parking spot, that spot's marker is now hidden so only the single amber parked-bike pin shows (the spot reappears once the bike is picked up), and the parked-bike pin takes click priority during hit-testing β so tapping it always opens the "my parked bike" sheet (with parked-ago time, distance, navigate and "I picked up my bike") instead of falling through to the underlying parking-spot sheet. Implemented via a location match in
buildParkingFeatures(isParkedAt, ~12 m radius) and a reorderedqueryRenderedFeatureschain inMapInitializer. - Consistent dismiss action on the parking-spot sheet β the bike parking-spot detail sheet (
SelectedSpaceSheet) now offers a bottom "Remove pin" text button that closes the sheet, mirroring the custom-pin and address-search sheets so dismissing a marker's detail view is consistent across the whole map.
- Marker clustering for the parking layer β major map performance boost β at city-level zoom the ~100 000 bike-parking markers are now aggregated into native MapLibre clusters instead of being drawn as thousands of overlapping individual symbols. The parking
GeoJsonSourceis created with clustering enabled (clusterMaxZoom = 13,clusterRadius = 60); the existing icon layer is filtered to non-clustered points (!has("point_count")), and two new layers render the cluster bubble (CircleLayerwith a step-scaled radius) and its count label (SymbolLayer,point_count_abbreviated, "Noto Sans Bold"). Tapping a cluster animates the camera to the source'sgetClusterExpansionZoom, so it smoothly breaks apart. The currently selected spot and the active navigation destination are rendered on a dedicated non-clustered highlight layer (velospot-parking-highlight-*) so they always stay visible on top, never disappearing into a cluster. This drastically reduces the number of rendered symbols when panning/zooming dense areas. Implemented inMapStyleLayers.kt,MapMarkerRenderer.kt(newClusterRenderStyle, split bulk/highlight feature building) and the click handling inMapInitializer.kt. - Live 3D turn-by-turn navigation β navigation is now a real, mitlaufende 3D experience similar to Google Maps, driven by a new self-contained
NavigationManagerand a pure, unit-testedcore/navigationpackage:- 3D follow camera β once navigation starts the camera centres on the live GPS position with a fixed 60Β° pitch, a speed/turn-dependent zoom (closer at standstill / before turns, further out while cruising) and a bearing that smoothly follows the direction of travel. A
Choreographerframe loop interpolates position, bearing, zoom and tilt every frame (frame-rate-independent exponential smoothing) so the motion stays ruckelfrei despite the ~3 s GPS cadence. - Map matching (snap-to-route) β each raw GPS fix is snapped onto the active BRouter polyline (
RouteMatcher) so the heading arrow rides the road instead of jittering beside it; the location puck is a rotating navigation arrow (IMG_LOCATION_NAV) aligned to the live heading. - Live route progress + ETA β the navigation card now shows the dynamically shrinking remaining distance and a remaining-time estimate (ETA), recomputed on every fix; the already-travelled part of the route is greyed out while the remaining part stays in the theme colour (split
velospot-route/velospot-route-traveledline layers). - Off-route detection & auto-reroute β straying more than ~30 m from the route for several consecutive fixes triggers a silent BRouter recalculation from the current position to the destination (throttled, self re-arming), with an "off route β recalculatingβ¦" hint in the overlay.
- 3D buildings β a
fill-extrusionlayer pulls the OpenMapTiles building footprints into 3D (usingrender_height/render_min_height); enabled during navigation and for the 3D resting view. - GPS heading + speed β
GeoCoordinatenow carries optionalbearing+speedMetersPerSecond, populated by both flavorLocationRepositoryImpls, feeding the heading arrow and the speed-dependent zoom.
- 3D follow camera β once navigation starts the camera centres on the live GPS position with a fixed 60Β° pitch, a speed/turn-dependent zoom (closer at standstill / before turns, further out while cruising) and a bearing that smoothly follows the direction of travel. A
- 2D / 3D map view switch β a new "Map view" entry in the menu opens a sheet with a segmented 2D/3D selector (animated preview tiles). The choice is persisted (
NavigationModePreferences) and applied live to the resting map (flat north-up vs. 45Β° tilt + 3D buildings); after navigation ends the map returns to the saved perspective. Active navigation always uses the full 3D camera regardless of the setting. - GPS route simulator (debug) β a debug-only "Simulate route" menu entry drives a synthetic GPS track along the active BRouter route (with bearing + speed, snap/off-route compatible), so the whole live-navigation pipeline can be tested from the couch without moving. Backed by a unit-tested
RouteSimulator; real GPS updates are ignored while simulating.
Fixed
- Crash on first open with the multi-country database β bumping the database to v4 made Room run its full post-migration
TableInfovalidation, which then failed because theidx_parking_lat_lonindex present in the bundled assets was not declared onBikeParkingSpaceEntity(Migration didn't properly handle: bike_parking_spaces). The index is now declared on the entity so the expected schema matches the seeded database (and it speeds up the bounding-box queries). - Parking pins no longer get stuck at the wrong size β the zoom-bucket-scaled parking marker icons (
IMG_NORMALβ¦IMG_MUTED_SELECTED) were only registered on the style once (registerIconsguarded eachaddImagewithgetImage(id) == null), so zooming never updated their on-map pixel size β they stayed frozen at the size of the zoom level they were first registered at and only refreshed on a full style reload (e.g. a dark-mode toggle). That made the pins occasionally appear too large/small for the current zoom. The zoom-dependent icons are now re-registered in place on every icon-set change (matching the location dot), so they always match the current zoom level. - Address-search result now shows the full custom-pin sheet β selecting an address from the search dropdown previously opened a reduced sheet that only offered "Navigate here". It now reuses the exact same card as a tapped custom pin, so a searched location also offers Save as favourite (with the naming dialog β persistent saved place) and Remove pin alongside navigation. The
CustomMapPinSheetheader/subtitle are now parameterised (the search variant shows the "Address" title without the tap-to-move hint);MapViewModelgainssaveSearchPinAsFavorite(), and the now-redundantSearchPinSheetwas removed.
π₯ APK Variants
| File | Description |
|---|---|
VeloSpot-v1.0.18.apk |
F-Droid flavor β no Google services, ready to sideload; this is the APK F-Droid reproducibly verifies |
VeloSpot-v1.0.18-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.