gmaps_vehicle_tracker 0.1.1 copy "gmaps_vehicle_tracker: ^0.1.1" to clipboard
gmaps_vehicle_tracker: ^0.1.1 copied to clipboard

Smooth, road-aware live vehicle tracking on Google Maps: route matching, bounded prediction, camera follow, built-in vehicle markers and an Uber-style vehicle selector.

gmaps_vehicle_tracker #

Smooth, road-aware live vehicle tracking (Uber / Rapido style) on Google Maps for Flutter.

You feed it timestamped location samples and, optionally, a route. It turns them into smooth motion that follows the road, with a stable heading, bounded prediction, freshness reporting, optional camera follow, built-in vehicle markers and route styling. Everything is drawn on google_maps_flutter, which this package re-exports.

Screenshots #

Ride selector + live tracking Fleet on real roads Close-up (debug: raw GPS dots) Motion and feed settings Built-in vehicle gallery
Uber-style ride selector with live tracking Mixed fleet driving real roads Vehicle following the road through noisy GPS Motion preset and GPS feed settings Built-in vehicles at real marker size
Platforms Android (verified on device, with a native no-flicker marker path) · iOS (configured, not yet device-verified; uses the plugin marker path)
Requires Flutter ≥ 3.32, Dart ≥ 3.12 (sdk: ^3.12.0), google_maps_flutter ^2.18
Network None. The library makes no HTTP calls and stores nothing.

Contents #

  1. Setup
  2. Quick start
  3. Concepts
  4. Built-in vehicles
  5. API reference
  6. How the motion works
  7. Rendering and performance
  8. Limitations
  9. Costs, terms, privacy
  10. Adding or replacing built-in art
  11. Troubleshooting
  12. Development

1. Setup #

dependencies:
  gmaps_vehicle_tracker:
    path: ../gmaps_vehicle_tracker   # or your git/pub source

One import gives you this package and all of google_maps_flutter:

import 'package:gmaps_vehicle_tracker/gmaps_vehicle_tracker.dart';

Google Maps keys. Enable Maps SDK for Android and Maps SDK for iOS in Google Cloud and restrict the key: Android package name + SHA-1, iOS bundle ID.

  • Android: add the key to AndroidManifest.xml:
    <meta-data android:name="com.google.android.geo.API_KEY" android:value="${MAPS_API_KEY}"/>
    
    The example injects MAPS_API_KEY from android/local.properties, which is not committed.
  • iOS: call GMSServices.provideAPIKey(key) in AppDelegate. The example reads GMSApiKey from Info.plist, which is filled from ios/Flutter/Secrets.xcconfig. The default google_maps_flutter_ios implementation is legacy; consider google_maps_flutter_ios_sdk10 (iOS 16+) or _sdk9 (iOS 15+).
  • Android warm-up (optional): call GoogleMapsFlutterAndroid.warmup() early to avoid first-map jank.

2. Quick start #

final route = TrackingRoute.fromEncodedPolyline(encodedPolylineFromBackend);
final tracker = VehicleTracker(route: route, icon: const VehicleIcon.builtIn(VehicleType.car));
tracker.bindStream(myLocationStream); // Stream<LocationSample>

TrackingMap(
  trackers: [tracker],
  camera: CameraDirector(),           // follows north-up by default
  initialCameraPosition: CameraPosition(target: route.start.toLatLng(), zoom: 16),
  mapStyle: TrackingMapStyles.clean,  // less clutter
  padding: const EdgeInsets.only(bottom: 200),
);

// later: tracker.dispose();

3. Concepts #

Concept What it is
Sample A real observation from your backend or GPS (LocationSample). Authoritative.
Rendered state The visual position drawn each frame (RenderedVehicleState). An estimate: never store it as the real location.
Render delay The vehicle is drawn slightly in the past (adaptive, about 0.25–4 s depending on preset), so most frames interpolate between two real samples.
Prediction When samples stop, motion continues briefly with decaying speed, then holds. Bounded by time and distance.
Route matching With a route, samples are projected to a distance along the route. The vehicle moves along the road, never cuts corners and never reverses because of a late sample.
Primary / secondary The first primary tracker gets full frame rate, the route line, stops and camera follow. Secondary trackers (nearby cars, fleets) update at a lower rate.
Phase Freshness of data: tracking, predicting, stale, lost, and so on. Drive your "location delayed" UI from it.

4. Built-in vehicles #

12 top-down images ship with the package. Each is nose-up, transparent, has a soft rotation-invariant shadow, and comes in 1x–4x densities. Sizes follow real vehicle length, compressed so small vehicles stay visible. Size = whole image including shadow padding, in logical pixels (dp).

Image VehicleArt VehicleType Type default Marker size (dp) displayName
carWhite carWhite car yes 34 × 64 Cab
carBlue carBlue car 35 × 64 Cab Premium
carTaxi carTaxi car 35 × 64 Taxi
carHatchback carHatchback car 34 × 64 Mini
motorcycleBlack motorcycleBlack motorcycle yes (also used for bicycle at 0.85×) 29 × 52 Bike
motorcycleBlue motorcycleBlue motorcycle 28 × 52 Bike Plus
motorcycleTaxi motorcycleTaxi motorcycle 28 × 52 Bike Taxi
busWhite busWhite bus yes 32 × 92 City Bus
busBlue busBlue bus 32 × 92 Coach
busYellow busYellow bus 32 × 92 School Bus
autoRickshaw autoRickshaw autoRickshaw yes 33 × 54 Auto
eRickshaw eRickshaw eRickshaw (toto) yes 34 × 56 Toto

Body length (nose to tail): bike 40 dp, auto 42 dp, e-rickshaw 44 dp, car 52 dp, bus 80 dp. VehicleType.bicycle has no art yet and uses motorcycleBlack at 0.85× (≈ 24 × 44 dp).

const VehicleIcon.builtIn(VehicleType.bus);                    // default bus art, bus size
VehicleIcon.art(VehicleArt.carTaxi);                           // specific image
VehicleIcon.builtIn(VehicleType.car, art: VehicleArt.carBlue, scale: 1.2);
VehicleArt.setDefault(VehicleType.car, VehicleArt.carTaxi);    // app-wide default

The example app's Built-in vehicle gallery screen shows every image at its real marker size.


5. API reference #

5.1 VehicleTracker #

One tracked vehicle. You own it: call dispose().

Constructors

Constructor Purpose
VehicleTracker({...}) Live mode; you push samples.
VehicleTracker.history(List<LocationSample> samples, {..., double speed = 1, bool autoPlay = true}) Replay a recorded trip. Samples without timestamps are ignored. Controlled with playback.
VehicleTracker.simulated(TrackingRoute route, {..., SimulationProfile profile = const SimulationProfile(), bool loop = true}) A simulated vehicle with a realistic noisy GPS feed, for demos and tests.

Common constructor parameters

Parameter Type Default Description
id String? auto vehicle-N Stable id (used in map object ids).
config TrackerConfig? TrackerConfig(profile: <icon type>.profile) Motion, prediction, matching, validation. See 5.12.
icon VehicleIcon VehicleIcon.builtIn(VehicleType.car) Marker image.
route TrackingRoute? null Route to follow (not on simulated, which takes it positionally).
priority VehiclePriority primary primary gets full rate, route line, stops and camera; secondary gets a reduced rate.
clock TrackingClock SystemTrackingClock() Time source (inject FakeTrackingClock in tests).

Members

Member Description
addSample(LocationSample) Add one live sample.
addSamples(Iterable<LocationSample>) Add a batch, e.g. a reconnect burst; any order.
bindStream(Stream<LocationSample>, {bool cancelOnError = false}) Subscribe to a stream; replaces any previous binding. Returns the subscription.
Future<void> setRoute(TrackingRoute) Set or replace the route. Routes with more than 2,000 points are indexed off the UI isolate. Throws ArgumentError for fewer than 2 distinct points.
clearRoute() Continue without a route.
setIcon(VehicleIcon) Swap the marker image; the old one stays until the new one is ready.
pause() / resume() Freeze or resume rendering (and history playback).
reset() Clear samples and visual state; keep route and icon (e.g. a new trip).
dispose() Release everything.
rendered ValueListenable<RenderedVehicleState?>, updated per frame while drawn.
status ValueListenable<TrackingStatus>, updated on change.
events Stream<TrackingEvent> (broadcast).
icon ValueListenable<VehicleIcon>.
route Current TrackingRoute?.
routeChanges ValueListenable<int>, bumps on route set / replace / clear.
playback PlaybackController? (history mode).
simulator, simulationTime Simulated mode: the RouteSimulator (ground truth) and elapsed seconds.
mode, priority, config, id, clock As constructed.

5.2 LocationSample #

Field Type Required Unit / notes
position GeoPoint yes WGS-84 degrees. LocationSample.latLng(lat, lng, ...) is a shortcut.
timestamp DateTime? strongly recommended Event time at the device (UTC). Without it the receipt time is used and confidence drops. Device clock skew is estimated and corrected automatically.
heading double? no Degrees clockwise from north. Ignored below minSpeedForHeading. Never required: heading is derived from the route or from movement.
speed double? no m/s. Improves stop detection and filtering.
accuracy double? no Metres (68 % radius). Default ValidationConfig.defaultAccuracy (15 m).
altitude double? no Carried through, unused.
sequence int? no Monotonic per source; used for de-duplication.
id String? no Opaque, for your diagnostics.

Validation.

  • Rejected: invalid or NaN coordinates, (0, 0) unless allowed, and accuracy worse than maxAccuracy.
  • Dropped: duplicates (same sequence, or same time and position) and samples older than what is already shown (late).
  • Physically impossible jumps are rejected, unless 3 consecutive samples agree; then the vehicle relocates (snaps).
  • Out-of-order samples newer than the shown time are re-ordered.

5.3 TrackingRoute / RouteStop #

API Description
TrackingRoute(List<GeoPoint> points, {String? id, List<RouteStop> stops = const []}) At least 2 distinct points. A different id means a different route.
TrackingRoute.fromEncodedPolyline(String encoded, {int precision = 5, String? id, List<RouteStop> stops}) Google encoded polyline (Routes API encodedPolyline; use polylineQuality: HIGH_QUALITY).
points, stops, id, start, end Accessors.
RouteStop(GeoPoint position, {RouteStopKind kind = waypoint, String? label}) RouteStopKind.pickup, .destination, .waypoint. Drawn as markers for the primary tracker.

Use real road geometry. The vehicle follows the route exactly. A hand-made polyline won't line up with the streets Google draws.

5.4 VehicleIcon #

All variants are rasterized once per (icon, size, pixel ratio) and cached. Rotation is a marker property, so images are never rebuilt per frame.

Constructor Parameters
VehicleIcon.builtIn(VehicleType type, {VehicleArt? art, double scale = 1}) Bundled image, sized for the type.
VehicleIcon.art(VehicleArt art, {double scale = 1}) A specific bundled image.
VehicleIcon.asset(String assetName, {String? package, double? width, Offset anchor, double noseHeadingOffset = 0, bool rotates = true}) Your PNG asset. width in dp; null means intrinsic size.
VehicleIcon.bytes(Uint8List bytes, {required double width, Offset anchor, double noseHeadingOffset = 0, bool rotates = true, String? key}) Encoded image bytes. Give a stable key when you recreate the bytes.
VehicleIcon.imageProvider(ImageProvider provider, {required double width, ..., String? key}) Any provider, e.g. NetworkImage. Your app controls the network policy.
VehicleIcon.picture(PictureIconBuilder builder, {required double width, required double height, required String key, ...}) A vector ui.Picture (render SVGs with your SVG package).
Common parameter Default Meaning
anchor Offset(0.5, 0.5) Rotation pivot / anchor as a fraction of the image.
noseHeadingOffset 0 Direction the art faces, in degrees clockwise from up (e.g. 90 for east-facing art).
rotates true false for pin-style icons (not rotated, not flat).

Getters: resolvedArt, builtInSize, cacheKey, isBuiltIn, isAsset, isBytes, isProvider, isPicture.

Custom art should be top-down with the nose pointing up (or set noseHeadingOffset), have a transparent background, and have any shadow baked in. Live widgets, animated images and shaders are not possible with map markers.

5.5 VehicleType / VehicleArt #

VehicleType member Description
profile Default VehicleProfile (kinematic limits).
arts Bundled images of this type.
defaultArt Image used by VehicleIcon.builtIn(type).
fallbackScale Scale used when the type borrows another type's art (bicycle: 0.85).
VehicleArt member Description
values All 12 images (see section 4).
type, fileName, assetName, logicalSize, displayName Metadata.
VehicleArt.package 'gmaps_vehicle_tracker', for Image.asset(art.assetName, package: VehicleArt.package).
VehicleArt.setDefault(VehicleType, VehicleArt?) Change the default image of a type app-wide; null restores it.

5.5b VehicleSelector / VehicleOption / VehicleIconImage #

An Uber-style picker that shows each vehicle's image and name. It's purely presentational: keep the selection in your state.

VehicleSelector parameter Default Meaning
options required List<VehicleOption>.
selectedId required Id of the highlighted option (null = none).
onSelected required ValueChanged<VehicleOption>, e.g. tracker.setIcon(o.icon).
layout VehicleSelectorLayout.list list (image · name/subtitle · trailing) or cards (horizontal).
imageWidth / imageHeight 72 / 44 Image box.
imageRotation 90 Clockwise rotation of the nose-up art (90 = facing right).
selectedColor onSurface Border of the selected option.
padding h12 v4
shrinkWrap true false when placed in an Expanded / sheet.
physics Scroll physics.
cardWidth 108 Width of each card (cards layout).
VehicleOption Meaning
VehicleOption({required id, required icon, required title, subtitle, trailing, badge, enabled = true}) Any VehicleIcon (built-in or custom).
VehicleOption.art(VehicleArt art, {id, title, subtitle, trailing, badge, enabled}) id defaults to art.name; title defaults to art.displayName (Cab, Taxi, Bike Taxi, Auto, Toto, City Bus, …).

VehicleIconImage(VehicleIcon icon, {width, height, rotationDegrees = 0, fit = BoxFit.contain}) renders any vehicle icon as a normal Flutter widget.

5.6 TrackingMap #

A GoogleMap that draws and animates the trackers. It passes through every GoogleMap option. It does not dispose trackers.

Parameter Type Default Notes
trackers List<VehicleTracker> required Rebuild with a new list to add or remove vehicles.
initialCameraPosition CameraPosition required
camera CameraDirector? internal, north-up follow Pass your own to control follow / recenter.
style TrackingStyle TrackingStyle() Route, stop and vehicle style.
markers, polylines, circles, polygons, heatmaps, tileOverlays, groundOverlays, clusterManagers sets empty Your own map objects, merged with the library's.
padding EdgeInsets zero Keep UI clear of the Google logo; camera framing uses the padded area.
mapId String? Cloud map styling / advanced markers.
mapStyle String? JSON style, e.g. TrackingMapStyles.clean.
mapType MapType normal
markerType GoogleMapMarkerType marker For your markers.
cameraTargetBounds, minMaxZoomPreference unbounded
myLocationEnabled bool false
myLocationButtonEnabled bool false
trafficEnabled bool false
buildingsEnabled bool true Set false for a cleaner look.
indoorViewEnabled, liteModeEnabled bool false
compassEnabled bool true
zoomControlsEnabled, mapToolbarEnabled bool false
rotateGesturesEnabled, scrollGesturesEnabled, zoomGesturesEnabled, tiltGesturesEnabled bool true
layoutDirection TextDirection?
gestureRecognizers set empty For maps inside scrollables.
onMapCreated MapCreatedCallback? Gives you the GoogleMapController.
onTap, onLongPress ArgumentCallback<LatLng>?
onCameraMoveStarted, onCameraMove, onCameraIdle Also fire for library-driven camera moves.
onLayerCreated ValueChanged<TrackingLayer>? Advanced.

5.7 TrackingMap.builder / TrackingMapHooks #

Build the GoogleMap yourself for complete control.

TrackingMap.builder(
  trackers: [...], camera: camera, style: style, padding: padding,
  builder: (context, hooks) => GoogleMap(
    markers: hooks.mergeMarkers(myMarkers), // required
    onMapCreated: hooks.onMapCreated,       // required
    onCameraMove: hooks.onCameraMove,       // required
    ...
  ),
)
TrackingMapHooks Description
mergeMarkers([Set<Marker> own]) Your markers plus the library's: stop markers, and vehicles on the plugin path.
trackerMarkers The library markers alone.
onMapCreated(GoogleMapController) Must be wired.
onCameraMove(CameraPosition) Must be wired.
layer The underlying TrackingLayer.

5.8 TrackingLayer #

The engine behind TrackingMap. Use it directly only with a completely custom map widget.

Member Description
TrackingLayer({required TickerProvider vsync, trackers, style, camera, maxPrimaryHz = 60, secondaryHz = 15, preferNativeMarkers = true, clock})
addTracker / removeTracker / setTrackers Manage vehicles.
attach(GoogleMapController) / detach() Bind to the map.
markers ValueListenable<Set<Marker>> to merge into GoogleMap.markers.
handleCameraMove(CameraPosition) Forward from GoogleMap.onCameraMove.
wrap(Widget map) Adds the touch listener that interrupts camera follow.
viewportSize Set from layout (used for camera framing).
style, camera, primary, trackers
renderPath MarkerRenderPath.native (Android fast path), .plugin or .pending.
primaryHz Current adaptive update rate.
dispose() Does not dispose trackers.

5.9 CameraDirector / CameraFollowConfig #

CameraMode Behaviour
free The library never moves the camera.
followNorthUp Follows the vehicle, north up.
followHeadingUp Follows the vehicle, map rotated to its heading, tilted.
overview Keeps the vehicle, remaining route and stops in view. Re-fits when needed.
CameraDirector member Description
CameraDirector({CameraFollowConfig config})
mode, lastFollowMode, isFollowing, cameraPosition State. It's a ChangeNotifier, so you can rebuild on mode changes.
setMode(CameraMode) Switch, with an animated transition.
recenter() Back to the last follow mode.
showOverview() Same as setMode(CameraMode.overview).
changes Stream<CameraModeChange> (from, to, reason: api / userGesture / autoRecenter).
dispose()

Touching the map switches to free (if interruptOnGesture).

CameraFollowConfig field Default Meaning
initialMode followNorthUp Mode when the map appears.
zoom 17 Follow zoom.
northUpTilt 0 Degrees.
headingUpTilt 45 Degrees.
northUpVehicleY 0.5 Vehicle's vertical screen position (0 = top, 1 = bottom).
headingUpVehicleY 0.68 Same, in heading-up mode.
maxUpdateHz 30 Follow camera updates per second.
headingUpSmoothing 600 ms Map rotation smoothing.
headingUpMaxTurnRate 60 Degrees per second.
transitionDuration 700 ms Recenter / mode-switch animation.
overviewPadding 72 Logical px.
overviewRefitInterval 8 s Minimum time between overview re-fits.
interruptOnGesture true Touch switches to free.
autoRecenterAfter null Return to follow after this idle time.

5.10 TrackingStyle and friends #

TrackingStyle({RouteStyle route, StopMarkerStyle stops, VehicleMarkerStyle vehicle}). Presets: TrackingStyle.standard() and TrackingStyle.minimal() (thin line, no casing or completed part).

RouteStyle field Default Meaning
visible true Draw the route.
remainingColor #1A73E8 Colour of the part still to travel.
remainingWidth 6 Width in screen px.
casingColor #0D47A1 Outline colour; null disables it.
casingWidth 9
showCompleted true Grey out the travelled part.
completedColor #9AA0A6
completedWidth 6
dashed false Dashed remaining route (no casing).
dashLength 18
gapLength 12
StopMarkerStyle field Default Meaning
visible true StopMarkerStyle.hidden() hides all stops.
pickupIcon, destinationIcon, waypointIcon null BitmapDescriptor overrides.
pickupHue, destinationHue, waypointHue green / red / orange Default pin hues.
builder null Marker? Function(RouteStop stop, Marker defaultMarker). Return a replacement (e.g. defaultMarker.copyWith(...)) or null to hide.
VehicleMarkerStyle field Default Meaning
zIndex 10 Primary vehicles.
secondaryZIndex 9
secondaryAlpha 0.85
consumeTapEvents true Plugin path only.

Polyline styling is limited to what google_maps_flutter exposes: solid colour, width, dash pattern, joints, caps. Gradients and per-segment colours are not available.

5.11 TrackingMapStyles #

Constant Effect
TrackingMapStyles.clean Hides POIs (shops, attractions, schools…), transit, road-shield icons, parcels and neighbourhood labels. Keeps street names and parks.
TrackingMapStyles.muted clean plus desaturated base colours.

Use these with mapStyle: (or GoogleMap.style:). Combine with buildingsEnabled: false.

5.12 TrackerConfig #

Presets:

  • TrackerConfig.balanced() (default): visual delay about 0.6–3 s.
  • .smooth(): about 1–4 s, softest motion.
  • .responsive(): about 0.25–1.5 s, closest to real time, relies more on prediction.

Each takes {VehicleProfile profile}. Change fields with copyWith(...).

Field Default
motion MotionConfig()
prediction PredictionConfig()
matching MatchingConfig()
validation ValidationConfig()
buffer BufferConfig()
profile VehicleProfile.car Use VehicleType.x.profile.
processNoiseAcceleration 1.5 m/s² Kalman process noise.

MotionConfig

Field Default
minRenderDelay / maxRenderDelay / initialRenderDelay 600 ms / 3 s / 1.2 s
renderDelayMargin 250 ms (added to the p90 sample interval)
correctionDuration / maxCorrectionDuration 800 ms / 5 s
maxCatchUpSpeed 8 m/s (extra speed allowed while blending out an error)
teleportThreshold 250 m (larger errors snap)
fadeOnTeleport true
backwardCreepSpeed 0.6 m/s (absorbs overshoot without visible reversing)
headingSmoothing 250 ms
maxTurnRate 180 °/s
headingLookaheadTime / minHeadingLookahead / maxHeadingLookahead 800 ms / 5 m / 25 m

PredictionConfig

Field Default
maxDuration / maxDistance / speedHalfLife (off route) 3 s / 60 m / 1.5 s
onRouteMaxDuration / onRouteMaxDistance / onRouteSpeedHalfLife 8 s / 150 m / 5 s
predictingAfter 1 s (shorter extrapolation still counts as tracking)
staleAfter / lostAfter 10 s / 60 s

MatchingConfig

Field Default
minSearchRadius / maxSearchRadius / searchRadiusAccuracyFactor 25 m / 150 m / 3
offRouteMinDistance / offRouteAccuracyFactor 40 m / 2.5 (off-route distance = max(min, factor × accuracy))
offRouteConfirmSamples / offRouteConfirmDuration 3 / 5 s (both required)
rejoinConfirmSamples 2
minBacktrack 20 m
headingWeight / progressWeight / backwardWeight 4 / 1 / 2

ValidationConfig

Field Default
defaultAccuracy / maxAccuracy 15 m / 100 m
allowNullIsland false
futureTolerance 5 s
outlierSpeedFactor / relocationConfirmSamples 1.5 / 3
minSpeedForHeading 2 m/s
stationarySpeed / stationaryReleaseSpeed / stationaryDwell 0.8 m/s / 1.5 m/s / 2 s
clockOffsetWindow 2 min

BufferConfig: maxSamples 512, maxAge 10 min (live mode; history keeps all).

VehicleProfile (maxSpeed m/s, maxAcceleration m/s²):

Profile maxSpeed maxAcceleration
bicycle 12 1.5
motorcycle 40 4
autoRickshaw 20 2
eRickshaw 12 1.5
car 55 4
bus 30 1.5

Custom profiles: VehicleProfile(maxSpeed: .., maxAcceleration: ..).

5.13 TrackingStatus, phases, diagnostics #

TrackingStatus field Description
phase TrackingPhase (below).
routeStatus RouteStatus: noRoute, onRoute, deviating, offRoute, rejoining.
freshness Age of the newest real sample (clock-offset corrected).
confidence 0..1, indicative (freshness × accuracy × match).
lastAuthoritativeSample The newest real sample (never a predicted one).
renderDelay Current visual latency.
diagnostics TrackingDiagnostics: accepted, rejected (per RejectionReason), rejectedTotal, relocations, reordered.
isStale phase is stale or lost. Show "location delayed".
TrackingPhase Meaning
idle Not started.
awaitingFirstFix No valid sample yet.
tracking Fresh data.
predicting Briefly extrapolating past the newest sample.
stale Overdue; prediction exhausted; vehicle held.
lost No data for lostAfter.
paused Paused by the app.
ended History playback finished.

RejectionReason: invalidCoordinate, nullIsland, lowAccuracy, duplicate, late, implausibleJump.

5.14 Events #

tracker.events emits a sealed TrackingEvent:

Event Fields When
PhaseChanged previous, current Phase changes.
RouteStatusChanged previous, current, isOffRoute Route relation changes. Request a new route when isOffRoute.
SampleRejected sample, reason A sample was rejected.
VehicleTeleported — The vehicle snapped (relocation, huge correction, seek).
PlaybackEnded — History reached its end.
SourceError error, stackTrace Your bound stream errored.
VehicleTapped — Marker tapped (plugin path only; not on the Android native path).

5.15 RenderedVehicleState #

position, heading, speed, mode (interpolating / extrapolating / holding), renderTime, progress (m along route), routeGeneration, onRoute, alpha, correcting, snapped, predictionExhausted.

It's a visual estimate only. Don't store it or upload it as the real location.

5.16 PlaybackController (history) #

play(), pause(), seek(Duration), setSpeed(double) (0.25–64×), isPlaying, speed, position, duration, positionSeconds. It's a ChangeNotifier, so you can drive UI with ListenableBuilder.

5.17 Simulation #

SimulationProfile (all optional):

Field Default
cruiseSpeed 11 m/s
acceleration 1.5 m/s²
cornerSlowdown true
stops [SimulatedStop(distanceMeters, dwell)]
startDwell —
sampleInterval 2 s
intervalJitter 0 (0..1)
gpsNoise 4 m
accuracy 8 m
dropoutProbability 0
duplicateProbability 0
latency / latencyJitter 250 ms / 150 ms
includeHeading / includeSpeed true / true
clockSkew 0
seed 42

RouteSimulator(RouteIndex, SimulationProfile, {DateTime? startTime}) exposes duration, truthAt(t) (ground truth) and deliveries() (samples with delivery times), for your own tests.

5.18 Utilities and pure-Dart core #

  • Geo: GeoPoint, distanceMeters, initialBearing, destinationPoint, shortestAngleDelta, normalizeDegrees, LocalFrame.
  • Polyline: PolylineCodec.decode/encode (precision 1–7).
  • Route: RouteIndex.build(points) gives length, pointAt(s), smoothedBearing(s), candidates(...), subPath(s0, s1).
  • Conversions: GeoPoint.toLatLng() and LatLng.toGeoPoint().
  • Icons: MarkerIconFactory.instance (resolve, resolvePng, clear).
  • Engine: import 'package:gmaps_vehicle_tracker/core.dart' gives the engine (TrackingEngine) without Flutter or Google Maps, for tests or server-side replays.

6. How the motion works #

  1. Validate and align time. Samples are checked, de-duplicated and re-ordered. Device clock skew and network delay are estimated (minimum-delay window) so data age is accurate.
  2. Match to route. Candidates near the sample are scored by distance, heading agreement, progress continuity and backward motion, within a window around the previous progress. This handles loops, parallel roads and hairpins. Leaving the route needs confirmation (3 samples and 5 s), after which the vehicle is shown at its true position.
  3. Filter. A constant-velocity Kalman filter runs along the route, or in 2-D off route. Stop detection uses reported speed or a regression over recent fixes, so GPS jitter at pickup doesn't move the marker.
  4. Render. At now − renderDelay, progress is interpolated with monotone cubic Hermite, so it never reverses or overshoots. Heading is the route tangent with a speed-scaled look-ahead, smoothed with a turn-rate cap.
  5. Predict. Past the newest sample, speed decays: up to 8 s / 150 m on route, 3 s / 60 m off route. The vehicle then holds and the phase becomes stale.
  6. Correct. New data that disagrees with what is shown is blended out with a speed cap. Very large errors snap with a short fade.

7. Rendering and performance #

  • Vehicle motion never rebuilds the GoogleMap widget.
  • Android: a small native component creates vehicle markers directly on the plugin's native map and only moves or rotates them. That's one platform message per frame for all vehicles, with no icon re-upload, which avoids the plugin's per-update icon re-decode that causes flicker.
  • Other platforms: plugin markers are updated directly through the platform interface.
  • Updates are skipped when the movement is under a third of a pixel. Primary vehicles update at up to 60 Hz, adapting down to 30 / 20 Hz if frames get slow; secondary vehicles at 15 Hz.
  • Route polylines are sent only when the route changes, plus the completed part at ≤ 2 Hz.
  • The ticker sleeps when nothing moves and stops in the background.
  • Measured on an Android 16 phone with 8 moving vehicles and camera follow: p50 5 ms, p90 9 ms frame time, 2 % janky frames.

8. Limitations #

  • Road shape: the route must come from a router. Without a route, motion between samples is unconstrained.
  • Polylines: no gradients or per-segment colours (plugin limitation).
  • Markers: no live widgets or animated images; SVG must be rasterized.
  • Taps: vehicle tap events aren't available on the Android native path.
  • Bicycle art: not bundled yet; the motorcycle image is used at 0.85×.
  • iOS: not yet device-verified; it uses the plugin marker path, which may flicker under frequent updates.

9. Costs, terms, privacy #

  • No billable calls from the library. Mobile map loads (Maps SDK) are free and unlimited. Routes API calls made by your app are billed: Essentials 10k/month free; traffic-aware routing is Pro; two-wheeler routing is Enterprise.
  • Attribution: don't cover the Google logo; use padding.
  • Caching: Google's terms limit how long route coordinates may be cached. Fetch routes when needed rather than shipping them.
  • Privacy: all data stays in memory in bounded buffers. Nothing is persisted or transmitted. reset() and dispose() clear everything.

10. Adding or replacing built-in art #

Art lives in assets/raw_images/ (sources) and assets/vehicles/ (generated 1x–4x PNGs).

  1. Put a top-down source image in assets/raw_images/. Any orientation works. It can have real transparency, a baked-in checkerboard, or a solid black background.
  2. Add a line to the table in tool/export_icons/export_all.dart:
    _Art('bicycle_red', '$_raw/My Bicycle.png', 34, 'checkerboard'), // body length dp, background mode
    
  3. Run dart run tool/export_icons/export_all.dart (or pass names to export only those). This writes the PNGs and regenerates lib/src/flutter/icons/vehicle_art_sizes.g.dart.
  4. Add the enum value in VehicleArt (lib/src/flutter/icons/vehicle_icon.dart), e.g. bicycleRed('bicycle_red', VehicleType.bicycle), and update the type's default if wanted.
  5. Run flutter test (test/flutter/vehicle_art_test.dart checks files, sizes and proportions). tool/export_icons/contact_sheet.dart renders a QA sheet.

11. Troubleshooting #

Symptom Fix
Car drives over buildings Your route geometry isn't road geometry. Use a router's high-quality polyline.
Vehicle lags behind Use TrackerConfig.responsive(), or send samples more often.
Vehicle stops and jumps Samples are too sparse for the prediction limits. Raise PredictionConfig.onRouteMaxDuration, or send samples more often.
"Location delayed" too early or late Tune PredictionConfig.staleAfter / lostAfter.
Marker jitters at stops Send speed and accuracy with samples.
Camera fights the user It shouldn't; touch switches to free. Make sure the map is wrapped (TrackingMap does it; with TrackingLayer use layer.wrap).
Marker blinking on iOS Lower TrackingLayer.maxPrimaryHz (native iOS path pending).

12. Development #

  • flutter test: unit tests plus scenario replays (noisy, irregular, delayed, reordered, duplicated and dropped GPS against ground truth, with road-adherence, smoothness, reversing and stop-jitter metrics).
  • example/: Live tracking, History replay, Fleet & full map control, Built-in vehicle gallery. Run it with:
    flutter run --dart-define-from-file=secrets.json
    
    secrets.json is {"MAPS_API_KEY": "..."} and is not committed. It enables real road routes from the Routes API; without it the demo uses an approximate offline route.
  • tool/: icon exporter, contact sheet, image inspection (dev only; excluded by .pubignore).
1
likes
150
points
0
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Smooth, road-aware live vehicle tracking on Google Maps: route matching, bounded prediction, camera follow, built-in vehicle markers and an Uber-style vehicle selector.

Repository (GitHub)
View/report issues

Topics

#google-maps #maps #tracking #location #animation

License

MIT (license)

Dependencies

flutter, google_maps_flutter, google_maps_flutter_platform_interface, meta

More

Packages that depend on gmaps_vehicle_tracker

Packages that implement gmaps_vehicle_tracker