Skip to content

VeloSpot v1.0.18

Choose a tag to compare

@github-actions github-actions released this 19 Jun 12:35
ba20581

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 same scripts/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 (BikeParkingDatabase v3 β†’ v4) that bulk-imports the extra datasets into the Germany-seeded bike_parking_spaces table 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 a viewbox (β‰ˆ 110 km half-span) with bounded=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 keepScreenOn flag 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(), new velospot-parked-bike source/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 model ParkedBike, Hilt provider and MapViewModel wiring: parkBikeAtCurrentLocation, parkBikeAt, showParkedBike, navigateToParkedBike, pickUpBike). Fully localised across all eight supported languages and covered by new MapViewModel unit 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 (maybeAutoParkOnArrival in MapViewModel, reusing the existing NavigationManager route-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 reordered queryRenderedFeatures chain in MapInitializer.
    • 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 GeoJsonSource is 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 (CircleLayer with a step-scaled radius) and its count label (SymbolLayer, point_count_abbreviated, "Noto Sans Bold"). Tapping a cluster animates the camera to the source's getClusterExpansionZoom, 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 in MapStyleLayers.kt, MapMarkerRenderer.kt (new ClusterRenderStyle, split bulk/highlight feature building) and the click handling in MapInitializer.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 NavigationManager and a pure, unit-tested core/navigation package:
    • 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 Choreographer frame 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-traveled line 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-extrusion layer pulls the OpenMapTiles building footprints into 3D (using render_height / render_min_height); enabled during navigation and for the 3D resting view.
    • GPS heading + speed β€” GeoCoordinate now carries optional bearing + speedMetersPerSecond, populated by both flavor LocationRepositoryImpls, feeding the heading arrow and the speed-dependent zoom.
  • 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 TableInfo validation, which then failed because the idx_parking_lat_lon index present in the bundled assets was not declared on BikeParkingSpaceEntity (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 (registerIcons guarded each addImage with getImage(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 CustomMapPinSheet header/subtitle are now parameterised (the search variant shows the "Address" title without the tap-to-move hint); MapViewModel gains saveSearchPinAsFavorite(), and the now-redundant SearchPinSheet was 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)

  1. Download the APK below.
  2. On your Android device: Settings β†’ Apps β†’ Install unknown apps β†’ allow your browser or file manager.
  3. Open the APK and tap Install.

The F-Droid flavor is also available on F-Droid.