Try Live
Add Docs
Rankings
Pricing
Docs
Install
Theme
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Mapbox React Native
https://github.com/rnmapbox/maps
Admin
A Mapbox react native module for creating custom maps
Tokens:
74,581
Snippets:
473
Trust Score:
8.5
Update:
5 months ago
Context
Skills
Chat
Benchmark
94.8
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# React Native Mapbox Maps SDK (@rnmapbox/maps) ## Introduction The React Native Mapbox Maps SDK is a community-supported, open-source library that enables developers to integrate high-performance, interactive maps into React Native applications using the Mapbox Maps SDK for iOS and Android. This library provides a comprehensive set of React components that wrap Mapbox's native mapping functionality, offering declarative APIs for map rendering, camera control, data sources, layers, annotations, and offline map management. The SDK supports both the legacy architecture and React Native's new architecture (Fabric/TurboModules). The library is designed to handle complex mapping scenarios including vector and raster data visualization, custom styling with the Mapbox Style Specification, real-time user location tracking, animated markers and shapes, 3D terrain rendering, clustering, and offline map downloads. It provides imperative APIs for advanced camera movements, querying rendered features, and managing map state, while maintaining excellent performance through native implementations. The SDK supports React Native 0.69+ and requires a Mapbox access token for operation. ## Core APIs and Components ### MapView Component - Basic Map Rendering MapView is the primary container component that renders a Mapbox map and handles user interactions. ```tsx import React, { useRef } from 'react'; import { StyleSheet, View } from 'react-native'; import Mapbox, { MapView, Camera } from '@rnmapbox/maps'; Mapbox.setAccessToken('pk.eyJ1IjoibXl1c2VybmFtZSIsImEiOiJjbGV4YW1wbGUifQ.example'); const BasicMap = () => { const mapRef = useRef<MapView>(null); const handleMapPress = async (feature: GeoJSON.Feature) => { const { geometry } = feature; console.log('Pressed at:', geometry.coordinates); // Query features at press location const point = geometry.coordinates as [number, number]; const renderedFeatures = await mapRef.current?.queryRenderedFeaturesAtPoint( point, ['==', 'type', 'building'], ['building-layer'] ); console.log('Features at point:', renderedFeatures); }; const handleLongPress = (feature: GeoJSON.Feature) => { console.log('Long pressed at:', feature.geometry.coordinates); }; return ( <View style={styles.page}> <MapView ref={mapRef} style={styles.map} styleURL="mapbox://styles/mapbox/streets-v12" zoomEnabled={true} scrollEnabled={true} pitchEnabled={true} rotateEnabled={true} compassEnabled={true} scaleBarEnabled={true} attributionEnabled={true} onPress={handleMapPress} onLongPress={handleLongPress} onMapIdle={(state) => { console.log('Map idle at zoom:', state.properties.zoom); console.log('Center:', state.properties.center); }} onCameraChanged={(state) => { console.log('Camera changed:', state.properties); }} > <Camera defaultSettings={{ centerCoordinate: [-74.006, 40.7128], // NYC zoomLevel: 12, pitch: 45, heading: 0, }} /> </MapView> </View> ); }; const styles = StyleSheet.create({ page: { flex: 1 }, map: { flex: 1 }, }); export default BasicMap; ``` ### Camera Component - Viewport Control Camera controls the map's perspective including center position, zoom level, pitch, heading, and viewport padding. ```tsx import React, { useRef, useEffect } from 'react'; import { View, Button, StyleSheet } from 'react-native'; import { MapView, Camera, type CameraRef } from '@rnmapbox/maps'; const CameraControl = () => { const cameraRef = useRef<CameraRef>(null); // Programmatic camera control const flyToNewYork = () => { cameraRef.current?.flyTo([-74.006, 40.7128], 2000); }; const moveToParis = () => { cameraRef.current?.moveTo([2.3522, 48.8566], 1000); }; const zoomIn = () => { cameraRef.current?.zoomTo(16, 500); }; const fitBounds = () => { cameraRef.current?.fitBounds( [-73.9, 40.8], // Northeast [-74.1, 40.6], // Southwest [50, 50, 50, 50], // Padding [top, right, bottom, left] 1000 // Duration ); }; const animateCamera = () => { cameraRef.current?.setCamera({ centerCoordinate: [-122.4194, 37.7749], // San Francisco zoomLevel: 14, pitch: 60, heading: 45, animationDuration: 3000, animationMode: 'flyTo', }); }; // Auto-follow user location useEffect(() => { // Camera will track user location after mount }, []); return ( <View style={styles.container}> <MapView style={styles.map}> <Camera ref={cameraRef} defaultSettings={{ centerCoordinate: [-74.006, 40.7128], zoomLevel: 11, }} // Follow user location followUserLocation={false} followUserMode="compass" followZoomLevel={15} followPitch={45} // Min/max zoom constraints minZoomLevel={8} maxZoomLevel={18} // Boundary constraints maxBounds={{ ne: [-73.5, 41.0], sw: [-74.5, 40.0], }} /> </MapView> <View style={styles.controls}> <Button title="Fly to NYC" onPress={flyToNewYork} /> <Button title="Move to Paris" onPress={moveToParis} /> <Button title="Zoom In" onPress={zoomIn} /> <Button title="Fit Bounds" onPress={fitBounds} /> <Button title="Animate Camera" onPress={animateCamera} /> </View> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1 }, map: { flex: 1 }, controls: { position: 'absolute', bottom: 20, left: 20, right: 20, flexDirection: 'row', justifyContent: 'space-around', }, }); export default CameraControl; ``` ### ShapeSource and Layers - Vector Data Visualization ShapeSource provides GeoJSON data to map layers for rendering points, lines, and polygons with customizable styles. ```tsx import React, { useState } from 'react'; import { View, StyleSheet } from 'react-native'; import { MapView, Camera, ShapeSource, CircleLayer, LineLayer, FillLayer, SymbolLayer, } from '@rnmapbox/maps'; import type { FeatureCollection } from 'geojson'; const VectorDataMap = () => { const [selectedFeatureId, setSelectedFeatureId] = useState<string | null>(null); // GeoJSON FeatureCollection with multiple geometries const geojsonData: FeatureCollection = { type: 'FeatureCollection', features: [ { type: 'Feature', id: 'point-1', geometry: { type: 'Point', coordinates: [-74.006, 40.7128], }, properties: { name: 'New York', population: 8336817, type: 'city', }, }, { type: 'Feature', id: 'line-1', geometry: { type: 'LineString', coordinates: [ [-74.006, 40.7128], [-73.935242, 40.730610], [-73.982536, 40.748817], ], }, properties: { name: 'Route 1', distance: 5.2, }, }, { type: 'Feature', id: 'polygon-1', geometry: { type: 'Polygon', coordinates: [[ [-74.0, 40.7], [-73.9, 40.7], [-73.9, 40.8], [-74.0, 40.8], [-74.0, 40.7], ]], }, properties: { name: 'Zone A', area: 100, }, }, ], }; // Clustered points example const clusterData: FeatureCollection = { type: 'FeatureCollection', features: Array.from({ length: 100 }, (_, i) => ({ type: 'Feature' as const, id: `cluster-point-${i}`, geometry: { type: 'Point' as const, coordinates: [ -74.006 + (Math.random() - 0.5) * 0.1, 40.7128 + (Math.random() - 0.5) * 0.1, ], }, properties: { count: Math.floor(Math.random() * 100), }, })), }; const handleShapePress = (event: any) => { const { features } = event; if (features.length > 0) { const feature = features[0]; console.log('Pressed feature:', feature.properties.name); setSelectedFeatureId(feature.id); } }; return ( <View style={styles.page}> <MapView style={styles.map}> <Camera defaultSettings={{ centerCoordinate: [-74.006, 40.7128], zoomLevel: 11, }} /> {/* Non-clustered shapes */} <ShapeSource id="main-source" shape={geojsonData} onPress={handleShapePress} hitbox={{ width: 20, height: 20 }} > {/* Render polygons */} <FillLayer id="polygon-fill" filter={['==', ['geometry-type'], 'Polygon']} style={{ fillColor: [ 'case', ['==', ['id'], selectedFeatureId], '#ff0000', // Selected color '#00ff00', // Default color ], fillOpacity: 0.5, fillOutlineColor: '#000000', }} /> {/* Render lines */} <LineLayer id="line-layer" filter={['==', ['geometry-type'], 'LineString']} style={{ lineColor: '#0000ff', lineWidth: 4, lineCap: 'round', lineJoin: 'round', }} /> {/* Render points */} <CircleLayer id="point-layer" filter={['==', ['geometry-type'], 'Point']} style={{ circleRadius: [ 'interpolate', ['linear'], ['get', 'population'], 0, 5, 10000000, 20, ], circleColor: [ 'case', ['==', ['id'], selectedFeatureId], '#ff0000', '#0080ff', ], circleStrokeColor: '#ffffff', circleStrokeWidth: 2, }} /> {/* Render labels */} <SymbolLayer id="label-layer" filter={['==', ['geometry-type'], 'Point']} style={{ textField: ['get', 'name'], textSize: 14, textColor: '#000000', textHaloColor: '#ffffff', textHaloWidth: 2, textOffset: [0, 2], }} /> </ShapeSource> {/* Clustered points */} <ShapeSource id="cluster-source" shape={clusterData} cluster={true} clusterRadius={50} clusterMaxZoomLevel={14} clusterProperties={{ sum: [['+', ['accumulated'], ['get', 'sum']], ['get', 'count']], }} > {/* Render clusters */} <CircleLayer id="cluster-circle" filter={['has', 'point_count']} style={{ circleRadius: [ 'step', ['get', 'point_count'], 20, // radius for count < 10 10, 30, // radius for count >= 10 30, 40, // radius for count >= 30 ], circleColor: [ 'step', ['get', 'point_count'], '#51bbd6', 10, '#f1f075', 30, '#f28cb1', ], }} /> {/* Cluster count labels */} <SymbolLayer id="cluster-count" filter={['has', 'point_count']} style={{ textField: ['get', 'point_count_abbreviated'], textSize: 12, textColor: '#ffffff', }} /> {/* Unclustered points */} <CircleLayer id="unclustered-point" filter={['!', ['has', 'point_count']]} style={{ circleRadius: 8, circleColor: '#11b4da', }} /> </ShapeSource> </MapView> </View> ); }; const styles = StyleSheet.create({ page: { flex: 1 }, map: { flex: 1 }, }); export default VectorDataMap; ``` ### PointAnnotation - Interactive Markers PointAnnotation renders custom React Native views as map markers with interaction callbacks. ```tsx import React, { useRef, useState } from 'react'; import { View, Text, StyleSheet, Image } from 'react-native'; import { MapView, Camera, PointAnnotation, Callout, } from '@rnmapbox/maps'; const MarkerMap = () => { const [markers, setMarkers] = useState([ { id: 'marker-1', coordinate: [-74.006, 40.7128], title: 'NYC' }, { id: 'marker-2', coordinate: [-73.935242, 40.730610], title: 'Queens' }, ]); const pointAnnotationRef = useRef<PointAnnotation>(null); const handleMarkerDrag = (feature: any) => { console.log('Dragging to:', feature.geometry.coordinates); }; const handleMarkerDragEnd = (feature: any) => { const { id, geometry } = feature; console.log('Drag ended for', id, 'at', geometry.coordinates); // Update marker position setMarkers(prev => prev.map(m => m.id === id ? { ...m, coordinate: geometry.coordinates } : m ) ); }; return ( <View style={styles.page}> <MapView style={styles.map} onPress={(feature) => { // Add new marker on map press const coords = feature.geometry.coordinates as [number, number]; const newMarker = { id: `marker-${Date.now()}`, coordinate: coords, title: `Point ${markers.length + 1}`, }; setMarkers([...markers, newMarker]); }} > <Camera defaultSettings={{ centerCoordinate: [-74.006, 40.7128], zoomLevel: 12, }} /> {markers.map((marker, index) => ( <PointAnnotation key={marker.id} id={marker.id} coordinate={marker.coordinate} title={marker.title} snippet={`Lat: ${marker.coordinate[1].toFixed(4)}, Lon: ${marker.coordinate[0].toFixed(4)}`} draggable={true} selected={index === 0} anchor={{ x: 0.5, y: 1 }} // Anchor at bottom center onSelected={(feature) => { console.log('Selected:', feature.properties?.title); }} onDeselected={(feature) => { console.log('Deselected:', feature.properties?.title); }} onDrag={handleMarkerDrag} onDragStart={(feature) => { console.log('Started dragging:', feature.id); }} onDragEnd={handleMarkerDragEnd} ref={index === 0 ? pointAnnotationRef : null} > {/* Custom marker view */} <View style={styles.annotationContainer}> <View style={styles.annotationFill}> <Text style={styles.annotationText}>{index + 1}</Text> </View> </View> {/* Optional callout */} <Callout title={marker.title}> <View style={styles.calloutContainer}> <Text style={styles.calloutTitle}>{marker.title}</Text> <Text style={styles.calloutDescription}> Coordinates: {marker.coordinate[1].toFixed(4)}, {marker.coordinate[0].toFixed(4)} </Text> </View> </Callout> </PointAnnotation> ))} {/* Marker with remote image */} <PointAnnotation id="image-marker" coordinate={[-73.98, 40.75]} title="Image Marker" > <View style={styles.annotationContainer}> <Image source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }} style={{ width: 40, height: 40 }} onLoad={() => pointAnnotationRef.current?.refresh()} fadeDuration={0} // Prevent animation artifacts /> </View> </PointAnnotation> </MapView> </View> ); }; const styles = StyleSheet.create({ page: { flex: 1 }, map: { flex: 1 }, annotationContainer: { alignItems: 'center', justifyContent: 'center', width: 30, height: 30, }, annotationFill: { width: 30, height: 30, borderRadius: 15, backgroundColor: '#3887be', borderColor: '#fff', borderWidth: 2, alignItems: 'center', justifyContent: 'center', }, annotationText: { color: '#fff', fontWeight: 'bold', }, calloutContainer: { padding: 10, maxWidth: 200, }, calloutTitle: { fontSize: 16, fontWeight: 'bold', marginBottom: 5, }, calloutDescription: { fontSize: 12, color: '#666', }, }); export default MarkerMap; ``` ### LocationManager - User Location Tracking LocationManager provides access to device location with continuous updates and customizable tracking parameters. ```tsx import React, { useEffect, useState } from 'react'; import { View, Text, StyleSheet, Platform, PermissionsAndroid } from 'react-native'; import Mapbox, { MapView, Camera, LocationPuck, UserLocation, } from '@rnmapbox/maps'; import { locationManager, type Location } from '@rnmapbox/maps'; const LocationTracking = () => { const [location, setLocation] = useState<Location | null>(null); const [following, setFollowing] = useState(true); useEffect(() => { // Request permissions (Android) const requestLocationPermission = async () => { if (Platform.OS === 'android') { const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, { title: 'Location Permission', message: 'This app needs access to your location', buttonPositive: 'OK', } ); return granted === PermissionsAndroid.RESULTS.GRANTED; } return true; }; // Set up location tracking const setupLocation = async () => { const hasPermission = await requestLocationPermission(); if (!hasPermission) { console.log('Location permission denied'); return; } // Configure location manager locationManager.setMinDisplacement(10); // Update every 10 meters locationManager.setRequestsAlwaysUse(false); // iOS: use "when in use" locationManager.setLocationEventThrottle(1000); // Update max every 1 second (iOS) // Add listener for location updates const handleLocationUpdate = (location: Location) => { console.log('Location update:', location.coords); setLocation(location); }; locationManager.addListener(handleLocationUpdate); // Get last known location const lastKnown = await locationManager.getLastKnownLocation(); if (lastKnown) { setLocation(lastKnown); } }; setupLocation(); // Cleanup return () => { locationManager.removeAllListeners(); }; }, []); return ( <View style={styles.page}> <MapView style={styles.map}> <Camera followUserLocation={following} followUserMode="compass" followZoomLevel={16} followPitch={45} /> {/* Modern location puck (v10+) */} <LocationPuck puckBearingEnabled={true} puckBearing="heading" pulsing={{ isEnabled: true }} /> {/* Legacy user location (pre-v10) */} <UserLocation visible={true} showsUserHeadingIndicator={true} minDisplacement={10} onUpdate={(location) => { console.log('UserLocation update:', location); }} /> </MapView> {/* Display location info */} <View style={styles.infoPanel}> {location ? ( <> <Text style={styles.infoText}> Lat: {location.coords.latitude.toFixed(6)} </Text> <Text style={styles.infoText}> Lon: {location.coords.longitude.toFixed(6)} </Text> <Text style={styles.infoText}> Accuracy: {location.coords.accuracy?.toFixed(1)}m </Text> <Text style={styles.infoText}> Speed: {location.coords.speed?.toFixed(1)}m/s </Text> <Text style={styles.infoText}> Heading: {location.coords.heading?.toFixed(0)}° </Text> <Text style={styles.infoText}> Altitude: {location.coords.altitude?.toFixed(1)}m </Text> </> ) : ( <Text style={styles.infoText}>Waiting for location...</Text> )} </View> </View> ); }; const styles = StyleSheet.create({ page: { flex: 1 }, map: { flex: 1 }, infoPanel: { position: 'absolute', top: 50, left: 10, right: 10, backgroundColor: 'rgba(255, 255, 255, 0.9)', padding: 15, borderRadius: 10, }, infoText: { fontSize: 14, marginBottom: 5, }, }); export default LocationTracking; ``` ### OfflineManager - Download and Manage Offline Maps OfflineManager enables downloading map regions for offline use with progress tracking and pack management. ```tsx import React, { useState, useEffect } from 'react'; import { View, Text, Button, StyleSheet, Alert } from 'react-native'; import Mapbox, { offlineManager, type OfflinePack } from '@rnmapbox/maps'; const OfflineMapDownload = () => { const [offlinePacks, setOfflinePacks] = useState<OfflinePack[]>([]); const [downloadProgress, setDownloadProgress] = useState<number>(0); const [isDownloading, setIsDownloading] = useState(false); useEffect(() => { // Load existing offline packs const loadPacks = async () => { try { const packs = await offlineManager.getPacks(); setOfflinePacks(packs); console.log('Loaded offline packs:', packs.length); } catch (error) { console.error('Error loading offline packs:', error); } }; loadPacks(); return () => { // Cleanup any active subscriptions offlineManager.unsubscribe('new-york-pack'); }; }, []); const downloadOfflineRegion = async () => { try { setIsDownloading(true); setDownloadProgress(0); // Progress listener const progressListener = (pack: OfflinePack, status: any) => { console.log('Download progress:', status.percentage); setDownloadProgress(status.percentage); if (status.state === Mapbox.OfflinePackDownloadState.Complete) { Alert.alert('Success', 'Offline map downloaded!'); setIsDownloading(false); loadPacksAgain(); } }; // Error listener const errorListener = (pack: OfflinePack, error: any) => { console.error('Download error:', error); Alert.alert('Error', `Failed to download: ${error.message}`); setIsDownloading(false); }; // Create offline pack await offlineManager.createPack( { name: 'new-york-pack', styleURL: Mapbox.StyleURL.Street, minZoom: 10, maxZoom: 16, bounds: [ [-74.1, 40.6], // Southwest [lon, lat] [-73.9, 40.9], // Northeast [lon, lat] ], metadata: { region: 'New York City', createdAt: new Date().toISOString(), }, }, progressListener, errorListener ); console.log('Offline pack creation started'); } catch (error: any) { console.error('Error creating offline pack:', error); Alert.alert('Error', error.message); setIsDownloading(false); } }; const loadPacksAgain = async () => { const packs = await offlineManager.getPacks(); setOfflinePacks(packs); }; const resumeDownload = async (packName: string) => { try { const progressListener = (pack: OfflinePack, status: any) => { console.log('Resume progress:', status.percentage); setDownloadProgress(status.percentage); }; const errorListener = (pack: OfflinePack, error: any) => { console.error('Resume error:', error); }; await offlineManager.subscribe(packName, progressListener, errorListener); // Get the pack and resume const pack = await offlineManager.getPack(packName); if (pack) { await pack.resume(); setIsDownloading(true); } } catch (error) { console.error('Error resuming download:', error); } }; const pauseDownload = async (packName: string) => { try { const pack = await offlineManager.getPack(packName); if (pack) { await pack.pause(); setIsDownloading(false); } } catch (error) { console.error('Error pausing download:', error); } }; const deleteOfflinePack = async (packName: string) => { try { await offlineManager.deletePack(packName); Alert.alert('Success', 'Offline pack deleted'); loadPacksAgain(); } catch (error) { console.error('Error deleting pack:', error); } }; const invalidatePack = async (packName: string) => { try { await offlineManager.invalidatePack(packName); Alert.alert('Success', 'Pack invalidated and will refresh'); } catch (error) { console.error('Error invalidating pack:', error); } }; const resetDatabase = async () => { Alert.alert( 'Confirm', 'Delete all offline packs?', [ { text: 'Cancel', style: 'cancel' }, { text: 'Delete', style: 'destructive', onPress: async () => { await offlineManager.resetDatabase(); setOfflinePacks([]); Alert.alert('Success', 'All offline packs deleted'); }, }, ] ); }; const setMaxCacheSize = async () => { try { // Set max cache size to 50MB await offlineManager.setMaximumAmbientCacheSize(50 * 1024 * 1024); Alert.alert('Success', 'Cache size limit set to 50MB'); } catch (error) { console.error('Error setting cache size:', error); } }; return ( <View style={styles.container}> <Text style={styles.title}>Offline Maps Manager</Text> <View style={styles.section}> <Button title={isDownloading ? `Downloading ${downloadProgress.toFixed(0)}%` : 'Download NYC Map'} onPress={downloadOfflineRegion} disabled={isDownloading} /> {isDownloading && ( <View style={styles.progressBar}> <View style={[styles.progressFill, { width: `${downloadProgress}%` }]} /> </View> )} </View> <View style={styles.section}> <Text style={styles.sectionTitle}>Downloaded Packs: {offlinePacks.length}</Text> {offlinePacks.map((pack, index) => ( <View key={index} style={styles.packItem}> <Text style={styles.packName}>{pack.name}</Text> <Text style={styles.packInfo}> Bounds: {JSON.stringify(pack.bounds)} </Text> <View style={styles.packButtons}> <Button title="Resume" onPress={() => resumeDownload(pack.name)} /> <Button title="Pause" onPress={() => pauseDownload(pack.name)} /> <Button title="Refresh" onPress={() => invalidatePack(pack.name)} /> <Button title="Delete" onPress={() => deleteOfflinePack(pack.name)} color="red" /> </View> </View> ))} </View> <View style={styles.section}> <Button title="Set Cache Limit (50MB)" onPress={setMaxCacheSize} /> <Button title="Reset Database" onPress={resetDatabase} color="red" /> </View> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, padding: 20, }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20, }, section: { marginBottom: 20, }, sectionTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 10, }, progressBar: { height: 10, backgroundColor: '#e0e0e0', borderRadius: 5, marginTop: 10, overflow: 'hidden', }, progressFill: { height: '100%', backgroundColor: '#4CAF50', }, packItem: { padding: 10, backgroundColor: '#f5f5f5', borderRadius: 5, marginBottom: 10, }, packName: { fontSize: 16, fontWeight: 'bold', }, packInfo: { fontSize: 12, color: '#666', marginTop: 5, }, packButtons: { flexDirection: 'row', justifyContent: 'space-around', marginTop: 10, }, }); export default OfflineMapDownload; ``` ### Map Querying and Feature Interaction Query rendered features and interact with map data programmatically. ```tsx import React, { useRef, useState } from 'react'; import { View, Text, StyleSheet, Button } from 'react-native'; import { MapView, Camera, ShapeSource, FillLayer, } from '@rnmapbox/maps'; import type { Position } from 'geojson'; const MapQuerying = () => { const mapRef = useRef<MapView>(null); const [queryResult, setQueryResult] = useState<string>(''); // Query features at a point const queryAtPoint = async () => { try { const coordinate: Position = [-74.006, 40.7128]; const features = await mapRef.current?.queryRenderedFeaturesAtPoint( coordinate, ['==', ['get', 'type'], 'building'], // Filter expression ['building-layer'] // Layer IDs ); setQueryResult(`Found ${features?.features.length || 0} buildings at point`); console.log('Features:', features); } catch (error) { console.error('Query error:', error); } }; // Query features in a rectangle const queryInRect = async () => { try { const bbox: [number, number, number, number] = [50, 50, 200, 200]; // [x1, y1, x2, y2] in pixels const features = await mapRef.current?.queryRenderedFeaturesInRect( bbox, [], // No filter null // All layers ); setQueryResult(`Found ${features?.features.length || 0} features in rectangle`); } catch (error) { console.error('Query error:', error); } }; // Query source features const querySourceFeatures = async () => { try { const features = await mapRef.current?.querySourceFeatures( 'my-source', ['==', ['get', 'category'], 'restaurant'], ['source-layer-name'] ); setQueryResult(`Found ${features?.features.length || 0} restaurants in source`); } catch (error) { console.error('Query error:', error); } }; // Get visible bounds const getVisibleBounds = async () => { try { const bounds = await mapRef.current?.getVisibleBounds(); if (bounds) { const [ne, sw] = bounds; setQueryResult( `Visible bounds:\nNE: [${ne[0].toFixed(4)}, ${ne[1].toFixed(4)}]\nSW: [${sw[0].toFixed(4)}, ${sw[1].toFixed(4)}]` ); } } catch (error) { console.error('Error getting bounds:', error); } }; // Get map center and zoom const getMapState = async () => { try { const center = await mapRef.current?.getCenter(); const zoom = await mapRef.current?.getZoom(); setQueryResult( `Center: [${center?.[0].toFixed(4)}, ${center?.[1].toFixed(4)}]\nZoom: ${zoom?.toFixed(2)}` ); } catch (error) { console.error('Error getting map state:', error); } }; // Convert coordinate to screen point const coordinateToPoint = async () => { try { const coordinate: Position = [-74.006, 40.7128]; const point = await mapRef.current?.getPointInView(coordinate); setQueryResult(`Coordinate [-74.006, 40.7128]\nmaps to screen point [${point?.[0].toFixed(0)}, ${point?.[1].toFixed(0)}]`); } catch (error) { console.error('Error converting coordinate:', error); } }; // Convert screen point to coordinate const pointToCoordinate = async () => { try { const point: Position = [100, 100]; // Screen coordinates const coordinate = await mapRef.current?.getCoordinateFromView(point); setQueryResult(`Screen point [100, 100]\nmaps to coordinate [${coordinate?.[0].toFixed(4)}, ${coordinate?.[1].toFixed(4)}]`); } catch (error) { console.error('Error converting point:', error); } }; // Set feature state (v10+) const setFeatureState = async () => { try { await mapRef.current?.setFeatureState( 'feature-id-123', { selected: true, hover: false }, 'my-source', 'my-source-layer' ); setQueryResult('Feature state updated'); } catch (error) { console.error('Error setting feature state:', error); } }; // Get feature state (v10+) const getFeatureState = async () => { try { const state = await mapRef.current?.getFeatureState( 'feature-id-123', 'my-source', 'my-source-layer' ); setQueryResult(`Feature state: ${JSON.stringify(state, null, 2)}`); } catch (error) { console.error('Error getting feature state:', error); } }; // Query terrain elevation (v10+) const queryElevation = async () => { try { const coordinate: Position = [-74.006, 40.7128]; const elevation = await mapRef.current?.queryTerrainElevation(coordinate); setQueryResult(`Elevation at coordinate: ${elevation?.toFixed(2)}m`); } catch (error) { console.error('Error querying elevation:', error); } }; return ( <View style={styles.container}> <MapView ref={mapRef} style={styles.map}> <Camera defaultSettings={{ centerCoordinate: [-74.006, 40.7128], zoomLevel: 12, }} /> <ShapeSource id="my-source" shape={{ type: 'Feature', geometry: { type: 'Polygon', coordinates: [[ [-74.0, 40.7], [-73.9, 40.7], [-73.9, 40.8], [-74.0, 40.8], [-74.0, 40.7], ]], }, properties: { category: 'restaurant' }, }} > <FillLayer id="building-layer" style={{ fillColor: '#088', fillOpacity: 0.5 }} /> </ShapeSource> </MapView> <View style={styles.controls}> <Text style={styles.result}>{queryResult}</Text> <Button title="Query at Point" onPress={queryAtPoint} /> <Button title="Query in Rectangle" onPress={queryInRect} /> <Button title="Query Source" onPress={querySourceFeatures} /> <Button title="Get Visible Bounds" onPress={getVisibleBounds} /> <Button title="Get Map State" onPress={getMapState} /> <Button title="Coord to Point" onPress={coordinateToPoint} /> <Button title="Point to Coord" onPress={pointToCoordinate} /> <Button title="Set Feature State" onPress={setFeatureState} /> <Button title="Get Feature State" onPress={getFeatureState} /> <Button title="Query Elevation" onPress={queryElevation} /> </View> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1 }, map: { flex: 1 }, controls: { position: 'absolute', bottom: 20, left: 20, right: 20, backgroundColor: 'rgba(255, 255, 255, 0.95)', padding: 10, borderRadius: 10, }, result: { fontSize: 12, marginBottom: 10, minHeight: 40, }, }); export default MapQuerying; ``` ## Summary and Integration Patterns The React Native Mapbox Maps SDK provides a comprehensive solution for building sophisticated mapping applications in React Native. The library follows React patterns with declarative components for most use cases (MapView, Camera, ShapeSource, Layers) while exposing imperative APIs through refs for advanced operations (camera movements, feature queries, location tracking). The SDK efficiently bridges React Native to native Mapbox implementations on iOS and Android, providing near-native performance for rendering complex vector maps, handling thousands of features, and supporting smooth animations. Common integration patterns include: (1) Using MapView as the root container with Camera for viewport control, (2) Combining ShapeSource with multiple layer components to visualize different data types with distinct styles, (3) Implementing interactive features with PointAnnotation for custom marker views or SymbolLayer for high-performance static icons, (4) Integrating LocationManager for real-time user tracking with camera following, (5) Using OfflineManager to download regions for offline access with progress tracking, (6) Querying rendered features for hit detection and interaction, and (7) Applying data-driven styling with expressions for dynamic visualizations based on feature properties. The SDK supports both v10 and v11 of Mapbox SDKs, handles both old and new React Native architectures, and provides TypeScript definitions for type-safe development.