react-native-maps-fleets
v0.2.0
Published
Fleet-oriented native clustering wrapper for react-native-maps
Downloads
133
Maintainers
Readme
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-mapscd ios && pod installGoogle 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
iconViewrequires 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:
- Zoom Threshold: Markers are clustered when the zoom level is below
clusterZoomThreshold. - Density Threshold: If the number of visible markers exceeds
clusterDensityThreshold, clustering activates even if zoom is high. - Selected Marker Exclusion: The currently selected marker is NEVER clustered. It remains a single, high-fidelity marker (arrow/avatar) regardless of zoom or density.
- 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
typeis set correctly andheadingis provided in the position update.
Performance tips
- Prefer
features+patchPositionsupdates (patches are computed automatically in JS). - Use
selectedIdto promote one feature out of the clustered set. - Keep marker types stable and reuse marker icons.
- Avoid re-creating the
featuresarray 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.
