Skip to content

nico33t/react-native-maps-fleets

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,891 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-native-maps-fleets

A production-ready fleet map wrapper around react-native-maps with native clustering and patch-based updates.

Install

yarn add react-native-maps-fleets react-native-maps
cd ios && pod install

Google Maps API key

Android (required): add a Google Maps API key to your AndroidManifest.xml:

<meta-data
  android:name="com.google.android.geo.API_KEY"
  android:value="YOUR_API_KEY" />

Expo config plugin (optional):

// app.json or app.config.js
{
  "expo": {
    "plugins": [
      [
        "react-native-maps-fleets/plugin/withFleetMaps",
        {
          "androidApiKey": "YOUR_API_KEY",
          "iosApiKey": "YOUR_IOS_KEY",
          "streetViewApiKey": "YOUR_STREET_VIEW_KEY"
        }
      ]
    ]
  }
}

iOS: add GMSApiKey to Info.plist for Google Maps on iOS.

Usage

import React from 'react';
import {FleetMapView, Cluster, NormalMarker, AdvancedMarker} from 'react-native-maps-fleets';

const features = [
  {
    id: 'truck-1',
    coordinate: {latitude: 37.78, longitude: -122.41},
    type: 'vehicle',
    clusterId: 'main',
    heading: 120,
    zIndex: 10,
  },
];

export default function FleetScreen() {
  return (
    <FleetMapView
      style={{flex: 1}}
      initialRegion={{
        latitude: 37.78,
        longitude: -122.41,
        latitudeDelta: 0.1,
        longitudeDelta: 0.1,
      }}
      features={features}
      selectedId={null}
      onSelect={id => console.log('select', id)}
      clusterIdDefault="main">
      <Cluster id="main">
        <NormalMarker type="vehicle" icon="car" rotatable zIndex={10} />
        <NormalMarker type="arrow" icon="arrow" rotatable zIndex={20} />
        <NormalMarker type="avatar" size={64} zIndex={30} />
      </Cluster>

      <Cluster id="secondary">
        <NormalMarker type="poi" icon="pin" zIndex={5} />
      </Cluster>

      <AdvancedMarker coordinate={{latitude: 37.78, longitude: -122.41}}>
        <SelectedVehicle />
      </AdvancedMarker>
    </FleetMapView>
  );
}

Performance Mode

Performance Mode is a set of optimizations for fleet-scale maps (200–1000+ moving devices), ensuring smooth movement and reduced CPU/GPU usage.

Why we never use iconView for mass markers

When rendering hundreds of markers, using iconView (UIView-based markers) leads to severe performance degradation and visual artifacts:

  • GPU Texture Pressure: Each iconView requires a snapshot that is stored as a GPU texture. 300+ live views can exceed GPU memory or cause texture thrashing.
  • Snapshot Corruption: Rapid movement or zoom changes during view snapshotting can cause "stretched" or falsified visuals (e.g., random red blocks).
  • Main Thread Blockage: React Native and the Map SDK must manage the lifecycle of hundreds of UIViews on the main thread.

Solution: In Performance Mode, we use pre-rendered marker.icon bitmaps. These are generated once, cached in an LRU cache, and reused across all markers with the same visual state.

Advanced Optimizations

  • Size Buckets: To maximize cache hits, marker sizes are quantized into a few "buckets" (e.g., small, medium, large).
  • Off-Main Processing: Image decoding, resizing, and marker composition (drawing borders, pins, etc.) happen on background threads. Only the final assignment to the marker happens on the main thread.
  • In-Flight Deduplication: If multiple markers request the same avatar URL simultaneously, only one download/process task is started.
  • Stable Z-Index: Marker layering is updated only when necessary (creation or selection change), eliminating per-tick overhead.
  • Out-of-Viewport Cadence: Markers outside the viewport remain visible but update their positions much less frequently (e.g., every 2.5s) to save CPU.

Features

  • Hot Path (Positions): Throttled and interpolated native updates.
  • Cold Path (Visuals): Efficient batch updates for selection and icon changes.
  • Authoritative Native Registry: Ensures 1 ID maps to exactly 1 native marker.
  • Viewport Virtualization: Skips processing for markers outside the camera view + buffer.
  • Zoom-driven Aggressive Clustering: Automatically hides/clusters markers at low zoom levels.
  • Cap Visible Singles: Limits the number of single markers in the viewport to prioritize performance.
  • Performance Cluster: Native clustering for high-density marker sets (200–1000+), automatically grouping background markers while keeping the selected marker as a distinct, single entity.

How to Enable

You can enable it via props or by calling the imperative method:

<FleetMapView
  performanceModeEnabled={true}
  performanceConfig={{
    performanceCluster: true,
    performanceClusterConfig: {
      clusterZoomThreshold: 12,
      clusterDensityThreshold: 250,
    },
    // ...
  }}
/>

Or imperatively:

mapRef.current.setPerformanceMode(true, { performanceCluster: true });

Performance Cluster Behavior

When performanceCluster is active:

  1. Zoom Threshold: Markers are clustered when the zoom level is below clusterZoomThreshold.
  2. Density Threshold: If the number of visible markers exceeds clusterDensityThreshold, clustering activates even if zoom is high.
  3. Selected Marker Exclusion: The currently selected marker is NEVER clustered. It remains a single, high-fidelity marker (arrow/avatar) regardless of zoom or density.
  4. Hysteresis & Throttling: Clustering updates are throttled (default 350ms) to avoid CPU spikes during movement).

Configuration Options

Option Default Description
performanceCluster false Enable/disable native performance clustering.
clusterZoomThreshold 12 Zoom level below which background markers are clustered.
clusterDensityThreshold 250 Number of markers above which clustering is forced.
clusterUpdateThrottleMs 350 Throttling interval for native clustering logic.
alwaysShowSelected true If true, selected marker is always excluded from clusters.

Troubleshooting

  • Ghost Markers: Ensure you are using unique IDs. Performance mode enforces a strict 1-to-1 registry.
  • Arrows not moving: Ensure the marker type is set correctly and heading is provided in the position update.

Performance tips

  • Prefer features + patchPositions updates (patches are computed automatically in JS).
  • Use selectedId to promote one feature out of the clustered set.
  • Keep marker types stable and reuse marker icons.
  • Avoid re-creating the features array when not needed.

Native Architecture

Both platforms implement a full-featured FleetMarkerEngine with identical subsystems:

Component Purpose
FleetMarkerEngine Main engine coordinating all subsystems
MarkerStore Thread-safe O(1) marker state storage
MarkerPool Object pooling for zero-alloc scrolling
VisibilityIndex 3-zone viewport culling with hysteresis
ClusterGrid O(N) spatial-hash clustering
MarkerRegistry 1-to-1 ID-to-native-marker mapping
SelectionController Selection state management
UpdateScheduler Budget-limited tick execution
IconCache LRU icon cache (Android: LruCache, iOS: NSCache)
  • Android: Kotlin, Google Maps SDK, Handler-based animation
  • iOS: Swift, Google Maps SDK, CADisplayLink 60fps animation, GCD background processing

See API.md for the full API reference.

Limitations

  • Advanced markers are rendered in React; avoid heavy layouts.

Roadmap

  • Custom cluster styling hooks.
  • Optional map state persistence.

License

MIT. Includes copyright from Airbnb and Nicola Tomassini.

About

Fleet-grade extension for react-native-maps. Native clustering, zero clipping on Android, smooth selection, and high-performance rendering. Built for real-time fleet tracking apps with full iOS/Android parity and JS-customizable native markers

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

 

Packages

 
 
 

Contributors