expo-realtime-maps-navigation
v1.0.5
Published
JavaScript-pure React Native navigation package with Google Places + HERE Routing APIs, dual maps support, and complete customization - No native modules required!
Maintainers
Readme
expo-realtime-maps-navigation
🚗 JavaScript-Pure React Native Navigation Package
Real-time navigation with Google Places + HERE Routing APIs, dual maps support (react-native-maps/expo-maps), and complete customization - No native modules required!
✨ Features
- 🎯 JavaScript-Pure: No native modules, works with Expo Go and all React Native environments
- 🗺️ HERE Routing API: Enterprise-grade routing and turn-by-turn guidance via REST API
- 🔍 Google Places: Search, autocomplete, and geocoding with Google Places API
- 📱 Dual Maps Support: Works with react-native-maps and expo-maps
- 🔄 Background Location: Reliable tracking with Expo Location
- 🎨 Modern React APIs: Hooks, Context, TypeScript-first development
- ⚙️ Expo Compatible: Works with Expo Go, no config plugin needed
- 🌍 Multi-modal: Car, scooter, bike, pedestrian routing
- 🎵 Voice Simulation: Navigation guidance simulation for testing
- 🔋 Lightweight: Pure JavaScript implementation, minimal bundle impact
🚀 Quick Start
1. Installation
npm install expo-realtime-maps-navigation react-native-maps expo-location2. API Keys Setup
You'll need API keys from:
HERE Developer Portal (for routing):
- Visit developer.here.com
- Create a project and get your REST API key
Google Cloud Console (for places):
- Visit console.cloud.google.com
- Enable Places API and Geocoding API
- Create an API key
3. Initialize the Navigation Service
import { initializeNavigation } from 'expo-realtime-maps-navigation';
// Initialize with your API keys
await initializeNavigation({
here: 'YOUR_HERE_API_KEY',
google: 'YOUR_GOOGLE_API_KEY'
});4. Basic Usage
import React, { useRef, useEffect, useState } from 'react';
import MapView, { Polyline } from 'react-native-maps';
import {
initializeNavigation,
computeRoute,
startNavigation,
useRouteProgress,
getPolylineFromHereRoute,
TRANSPORT_MODES,
searchPlaces,
getCurrentLocation,
} from 'expo-realtime-maps-navigation';
const App = () => {
const mapRef = useRef<MapView>(null);
const [route, setRoute] = useState(null);
const progress = useRouteProgress();
useEffect(() => {
// Initialize the navigation service
initializeNavigation({
here: 'YOUR_HERE_API_KEY',
google: 'YOUR_GOOGLE_API_KEY'
});
}, []);
const startNavigationExample = async () => {
// Get current location
const currentLocation = await getCurrentLocation();
// Search for a destination
const places = await searchPlaces('restaurant near me', currentLocation.position);
// Compute route to first result
const route = await computeRoute({
origin: currentLocation.position,
destination: places[0].position,
transportMode: TRANSPORT_MODES.CAR,
});
setRoute(route);
// Start navigation with simulation
await startNavigation(route, {
simulate: true, // Use simulation for testing
speedKph: 60
});
};
return (
<MapView ref={mapRef} style={{ flex: 1 }} showsUserLocation>
{route && (
<Polyline
coordinates={getPolylineFromHereRoute(route)}
strokeWidth={6}
strokeColor="#007AFF"
/>
)}
</MapView>
);
};📚 Documentation
Core API
Initialization
import { initializeNavigation } from 'expo-realtime-maps-navigation';
// Required: Initialize with your API keys
await initializeNavigation({
here: 'YOUR_HERE_API_KEY', // HERE REST API key
google: 'YOUR_GOOGLE_API_KEY' // Google Places/Geocoding API key
});Places Search
import { searchPlaces, autocompletePlaces } from 'expo-realtime-maps-navigation';
// Search for places
const places = await searchPlaces('restaurant', currentLocation, 5000); // 5km radius
// Autocomplete (as user types)
const suggestions = await autocompletePlaces('pizza', currentLocation);
// Get place details
const placeDetails = await getPlaceDetails(place.id);Route Computation
import { computeRoute, TRANSPORT_MODES } from 'expo-realtime-maps-navigation';
const route = await computeRoute({
origin: { latitude: 37.7749, longitude: -122.4194 },
destination: { latitude: 37.7849, longitude: -122.4094 },
transportMode: TRANSPORT_MODES.CAR,
waypoints: [{ latitude: 37.7799, longitude: -122.4144 }], // Optional
avoidances: ['tolls', 'highways'], // Optional
routePreference: 'fastest', // 'fastest' | 'shortest' | 'balanced'
});Navigation Control
import { startNavigation, stopNavigation } from 'expo-realtime-maps-navigation';
// Start navigation
await startNavigation(route, {
simulate: true, // Use simulation for testing
speedKph: 60, // Simulation speed
voiceGuidance: false, // Voice simulation not implemented
});
// Stop navigation
await stopNavigation();React Hooks
useNavigationSession
import { useNavigationSession } from 'expo-realtime-maps-navigation';
const MyComponent = () => {
const navigation = useNavigationSession();
return (
<View>
<Text>Status: {navigation.isActive ? 'Navigating' : 'Idle'}</Text>
<Text>Current Route: {navigation.currentRoute?.distance}m</Text>
</View>
);
};useRouteProgress
import { useRouteProgress } from 'expo-realtime-maps-navigation';
const ProgressDisplay = () => {
const progress = useRouteProgress();
if (!progress) return null;
return (
<View>
<Text>Next: {progress.nextManeuver?.instruction}</Text>
<Text>Distance: {progress.distanceToNextManeuver}m</Text>
<Text>ETA: {progress.eta.toLocaleTimeString()}</Text>
<Text>Speed: {Math.round(progress.speed * 3.6)} km/h</Text>
</View>
);
};useLiveLocation
import { useLiveLocation } from 'expo-realtime-maps-navigation';
const LocationDisplay = () => {
const location = useLiveLocation();
return (
<Text>
Location: {location?.position.latitude.toFixed(6)},{location?.position.longitude.toFixed(6)}
(Accuracy: ±{location?.accuracy}m)
Source: {location?.source} {/* 'gps' or 'simulation' */}
</Text>
);
};Map Integration Helpers
import {
getPolylineFromHereRoute,
followUserOnMap,
fitRoute,
} from 'expo-realtime-maps-navigation';
// Convert HERE route to react-native-maps format
const coordinates = getPolylineFromHereRoute(route);
// Auto-follow user position during navigation
followUserOnMap(mapRef, currentPosition, {
bearing: currentBearing,
zoom: 16,
});
// Fit route to map view
fitRoute(mapRef, route, 50); // 50pt paddingLocation Services
import { getCurrentLocation, requestLocationPermissions } from 'expo-realtime-maps-navigation';
// Request location permissions
const status = await requestLocationPermissions();
// Get current location
const location = await getCurrentLocation({
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 10000
});
// For background location, you can also request:
// await Location.requestBackgroundPermissionsAsync()Navigation Events
import { events } from 'expo-realtime-maps-navigation';
// Listen to navigation events
events.on('navigationProgress', (progress) => {
console.log('Route progress:', progress.routeProgress);
console.log('Next maneuver:', progress.nextManeuver);
});
events.on('maneuverWarning', ({ maneuver, distance }) => {
console.log(`In ${distance}m: ${maneuver.instruction}`);
});
events.on('navigationArrived', () => {
console.log('Destination reached!');
});Complete Example
import React, { useEffect, useState } from 'react';
import { View, Button, Text } from 'react-native';
import {
initializeNavigation,
searchPlaces,
computeRoute,
startNavigation,
getCurrentLocation,
events,
TRANSPORT_MODES
} from 'expo-realtime-maps-navigation';
const NavigationExample = () => {
const [isReady, setIsReady] = useState(false);
const [progress, setProgress] = useState(null);
useEffect(() => {
// Initialize services
initializeNavigation({
here: 'YOUR_HERE_API_KEY',
google: 'YOUR_GOOGLE_API_KEY'
}).then(() => setIsReady(true));
// Listen to events
events.on('navigationProgress', setProgress);
return () => events.removeAllListeners();
}, []);
const startNavigation = async () => {
const location = await getCurrentLocation();
const places = await searchPlaces('coffee shop', location.position);
const route = await computeRoute({
origin: location.position,
destination: places[0].position,
transportMode: TRANSPORT_MODES.CAR
});
await startNavigation(route, { simulate: true });
};
return (
<View>
<Text>Ready: {isReady ? 'Yes' : 'No'}</Text>
{progress && (
<Text>Progress: {Math.round(progress.routeProgress * 100)}%</Text>
)}
<Button title="Start Navigation" onPress={startNavigation} />
</View>
);
};📋 Data Formats & API Reference
Route Object Format
interface Route {
id: string; // "here_1755383063027"
distance: number; // Distance in meters (6940)
duration: number; // Duration in seconds (1380)
polyline: string; // HERE encoded polyline
maneuvers: Maneuver[]; // Turn-by-turn instructions
summary: {
startAddress: string; // "Départ" or actual address
endAddress: string; // "Arrivée" or actual address
routeType: string; // "car", "scooter", etc.
};
sections: RouteSection[]; // Route segments (for waypoints)
}
interface Maneuver {
id: string; // "maneuver_0"
instruction: string; // "Turn left onto Rue de Rivoli"
distance: number; // Distance to maneuver in meters
duration: number; // Time to maneuver in seconds
type: string; // "start", "turn", "continue", "finish"
position?: {
latitude: number;
longitude: number;
};
roadName: string; // "Rue de Rivoli"
direction: string; // "left", "right", "straight"
severity: string; // "light", "quite", "info"
}Place Object Format
interface Place {
id: string; // Google place_id
name: string; // "Le Ju'"
address: string; // "16 Rue des Archives, 75004 Paris, France"
position: {
latitude: number; // 48.8576911
longitude: number; // 2.3548054
};
types?: string[]; // ["restaurant", "food", "establishment"]
rating?: number; // 4.2
priceLevel?: number; // 1-4 scale
phoneNumber?: string;
website?: string;
}Location Object Format
interface Location {
position: {
latitude: number; // 48.8566
longitude: number; // 2.3522
};
accuracy: number; // Accuracy in meters (10)
heading?: number; // Bearing in degrees (0-360)
speed?: number; // Speed in m/s
source: 'gps' | 'simulation'; // Location source
timestamp: Date; // When location was obtained
}Navigation Progress Format
interface NavigationProgress {
position: {
latitude: number;
longitude: number;
};
bearing: number; // Current heading in degrees
speed: number; // Speed in m/s
speedKmh: number; // Speed in km/h
accuracy: number; // GPS accuracy in meters
nextManeuver?: Maneuver; // Next turn/instruction
distanceToNextManeuver: number; // Distance in meters
distanceToDestination: number; // Remaining distance in meters
eta: Date; // Estimated time of arrival
roadName?: string; // Current road name
speedLimit?: number; // Posted speed limit
instructionText: string; // "Turn left in 200m"
routeProgress: number; // 0.0 to 1.0 (completion percentage)
deviationMeters: number; // Distance from route in meters
inTunnel: boolean; // GPS signal status
}Constants & Enums
// Transport modes
export const TRANSPORT_MODES = {
CAR: 'car',
SCOOTER: 'scooter',
BIKE: 'bike',
PEDESTRIAN: 'pedestrian'
};
// Route preferences
export const ROUTE_PREFERENCES = {
FASTEST: 'fastest',
SHORTEST: 'shortest',
BALANCED: 'balanced'
};
// Avoidances
export const AVOIDANCES = {
TOLLS: 'tolls',
HIGHWAYS: 'highways',
FERRIES: 'ferries'
};Event Types
// Navigation events
events.on('navigationProgress', (progress: NavigationProgress) => {});
events.on('navigationStarted', ({ route: Route, options: any }) => {});
events.on('navigationStopped', () => {});
events.on('navigationPaused', () => {});
events.on('navigationResumed', () => {});
events.on('navigationArrived', () => {});
// Maneuver warnings
events.on('maneuverWarning', ({
maneuver: Maneuver,
distance: number, // Distance to maneuver in meters
level: 'far' | 'near' | 'immediate' // Warning urgency
}) => {});
// Error handling
events.on('error', (error: NavigationError) => {});
events.on('navigationError', ({
type: string,
code: string,
message: string,
timestamp: Date
}) => {});Example Real API Responses
HERE Route Response (Paris to Tour Eiffel):
{
"distance": 6940,
"duration": 1380,
"maneuvers": 20,
"polyline": "BGsrgm9CgsyvE4I7kBgFvRoB_E...",
"summary": {
"startAddress": "Départ",
"endAddress": "Arrivée",
"routeType": "car"
}
}Google Places Response (Restaurant search):
{
"name": "Le Ju'",
"address": "16 Rue des Archives, 75004 Paris, France",
"position": {
"latitude": 48.8576911,
"longitude": 2.3548054
},
"rating": 4.2,
"types": ["restaurant", "food"]
}Utility Functions
// Package utilities
import { utils } from 'expo-realtime-maps-navigation';
utils.getSDKVersion(); // "1.0.0-beta (JavaScript Pure Edition)"
utils.isInitialized(); // true/false
await utils.clearCache(); // Clears routing and places cache
// Geometry helpers (available but not in main export)
import {
calculateDistance, // Haversine distance between two points
calculateBearing, // Bearing between two points
decodePolyline // Decode HERE polyline to coordinates
} from 'expo-realtime-maps-navigation/lib/services/here/routing';
const distance = calculateDistance(
{ latitude: 48.8566, longitude: 2.3522 },
{ latitude: 48.8584, longitude: 2.2945 }
); // Returns distance in metersError Handling
import { NavigationError } from 'expo-realtime-maps-navigation';
try {
await computeRoute(request);
} catch (error) {
if (error instanceof NavigationError) {
console.log('Error code:', error.code);
console.log('Error message:', error.message);
console.log('Original error:', error.originalError);
}
}
// Common error codes:
// - INITIALIZATION_FAILED
// - ROUTE_COMPUTATION_FAILED
// - NAVIGATION_START_FAILED
// - LOCATION_FAILED
// - PERMISSION_FAILED🛠️ Key Differences from Native Versions
JavaScript-Pure Implementation
This package uses web APIs instead of native SDK modules:
- HERE Routing API (REST) instead of HERE SDK
- Google Places API (REST) instead of native SDKs
- Expo Location instead of native location services
- EventEmitter for navigation events instead of native callbacks
Benefits
- ✅ Works with Expo Go
- ✅ No native module compilation
- ✅ Cross-platform compatibility
- ✅ Easier deployment and updates
- ✅ No platform-specific setup
Limitations
- ❌ No offline voice guidance (simulation only)
- ❌ Requires internet connection for routing
- ❌ No native background location optimizations
- ❌ Limited to REST API rate limits
Migration from Native Versions
// Before (native SDK)
<HereNavProvider platformKeys={...}>
// After (JavaScript-pure)
await initializeNavigation({ here: '...', google: '...' });📱 Platform Requirements
- React Native: 0.70+
- Expo SDK: 48+ (including Expo Go)
- Android: API 21+ (Android 5.0+)
- iOS: 11.0+
- Internet Connection: Required for routing and places
🔑 API Keys Setup
HERE Developer Portal
- Visit developer.here.com
- Create a new project
- Generate a REST API key
- Enable "Routing API v8"
Google Cloud Console
- Visit console.cloud.google.com
- Create a new project or select existing
- Enable "Places API" and "Geocoding API"
- Create an API key and restrict it to these APIs
🔒 Permissions
Required Dependencies
npm install expo-locationAndroid Permissions
Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />iOS Permissions
Add to ios/YourApp/Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access for navigation</string>✅ Tested & Verified
This package has been thoroughly tested with:
- ✅ HERE Routing API v8: Complete route calculation with real API responses
- ✅ Google Places API: Search, autocomplete, and geocoding functionality
- ✅ Expo Location: GPS permissions and position tracking
- ✅ Complete Integration: End-to-end navigation workflows
- ✅ Multi-platform: Node.js compatible for testing, React Native ready
All core features verified with real API keys and production-ready responses.
🐛 Troubleshooting
Common Issues
1. "API key invalid" errors
- Verify your HERE API key has Routing API v8 enabled
- Check Google API key has Places API and Geocoding API enabled
- Ensure API keys are not restricted to wrong domains/IPs
2. "Permission denied" on location
import { requestLocationPermissions } from 'expo-realtime-maps-navigation';
const status = await requestLocationPermissions();
if (status !== 'granted') {
// Handle permission denial
}3. Navigation not starting
- Check internet connection
- Verify
initializeNavigation()was called first - Ensure route computation was successful
4. Performance Issues
// Use simulation for testing to avoid GPS overhead
await startNavigation(route, {
simulate: true,
speedKph: 60
});📄 License
MIT
🤝 Contributing
We welcome contributions! Please see our Contributing Guide.
📞 Support
Made with ❤️ for the React Native community
