Package Exports
- @chauffleet/expo-custom-map
- @chauffleet/expo-custom-map/lib/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@chauffleet/expo-custom-map) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@chauffleet/expo-custom-map
Open source custom map library for Expo/React Native, created by Chauffleet for its mobile app. Purpose: Easily use fully custom maps in your app, without Google Maps, Mapbox, or any API keys. No hidden providersβyour tiles, your rules!
High-performance map library for Expo/React Native with custom tile support. πΊοΈβ‘
Why this project?
- Developed by Chauffleet for its mobile application.
- 100% open source, MIT licensed.
- Lets you use your own map tiles (OpenStreetMap, MapTiler, CartoDB, etc.)βno Google Maps, no Mapbox, no API keys required.
- No hidden providers: Unlike
react-native-maps
and similar libraries, this does not use Google Maps or Mapbox under the hood. - Perfect for custom, private, or offline maps.
π Features
- ποΈ High Performance: Native rendering with Skia, 60fps guaranteed
- π― Custom Tiles: Support for any tile provider (OpenStreetMap, Mapbox, etc.)
- π± Optimized for Mobile: Smooth gestures, efficient memory usage
- π Offline Support: Download and cache tiles for offline usage
- πͺ Clustering: Automatic marker clustering for better performance
- π§ Navigation: Built-in routing and turn-by-turn navigation
- π¨ Fully Customizable: Custom markers, overlays, and styling
- π Performance Monitoring: Built-in performance statistics
π¦ Installation
npm install @chauffleet/expo-custom-map
Dependencies
npx expo install react-native-reanimated react-native-gesture-handler @shopify/react-native-skia @react-native-async-storage/async-storage
π οΈ Setup
1. Metro Configuration
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.resolver.assetExts.push('bin', 'txt', 'jpg', 'png', 'json', 'mp4', 'ttf', 'otf', 'xml');
module.exports = config;
2. Babel Configuration
// babel.config.js
module.exports = {
presets: ['babel-preset-expo'],
plugins: ['react-native-reanimated/plugin'], // Must be last
};
π― Quick Start
import React from 'react';
import { View } from 'react-native';
import { CustomMapView } from '@chauffleet/expo-custom-map';
export default function App() {
return (
<View style={{ flex: 1 }}>
<CustomMapView
style={{ flex: 1 }}
center={[2.3522, 48.8566]} // Paris [longitude, latitude]
zoom={15}
tileUrlTemplate="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
markers={[
{
coordinate: [2.3522, 48.8566],
title: "Paris",
children: <CustomMarker />
}
]}
onRegionChange={(region) => console.log(region)}
/>
</View>
);
}
π API Reference
CustomMapView Props
Prop | Type | Default | Description |
---|---|---|---|
center |
[number, number] |
Required | Map center [longitude, latitude] |
zoom |
number |
Required | Initial zoom level |
tileUrlTemplate |
string |
Required | Tile URL template with {x}, {y}, {z} placeholders |
style |
ViewStyle |
{} |
Style for the map container |
markers |
MarkerProps[] |
[] |
Array of markers to display |
onRegionChange |
(region: MapRegion) => void |
undefined |
Called when map region changes |
onMapPress |
(coordinate: [number, number]) => void |
undefined |
Called when map is pressed |
minZoom |
number |
1 |
Minimum zoom level |
maxZoom |
number |
18 |
Maximum zoom level |
enableClustering |
boolean |
false |
Enable automatic marker clustering |
showUserLocation |
boolean |
false |
Show user's current location |
followUserLocation |
boolean |
false |
Follow user's location automatically |
MarkerProps
Prop | Type | Default | Description |
---|---|---|---|
coordinate |
[number, number] |
Required | Marker position [longitude, latitude] |
children |
ReactNode |
Default marker | Custom marker component |
title |
string |
undefined |
Marker title |
description |
string |
undefined |
Marker description |
onPress |
() => void |
undefined |
Called when marker is pressed |
draggable |
boolean |
false |
Enable marker dragging |
π Tile Providers
OpenStreetMap (Free)
tileUrlTemplate="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
MapTiler
tileUrlTemplate="https://api.maptiler.com/maps/streets/{z}/{x}/{y}.png?key=YOUR_KEY"
CartoDB
tileUrlTemplate="https://basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png"
Stamen Terrain
tileUrlTemplate="https://stamen-tiles.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png"
π¨ Advanced Usage
Custom Markers
import { CustomMapView } from '@chauffleet/expo-custom-map';
import { View, Text } from 'react-native';
const CustomMarker = ({ title }) => (
<View style={{
backgroundColor: 'red',
padding: 8,
borderRadius: 8
}}>
<Text style={{ color: 'white', fontWeight: 'bold' }}>
{title}
</Text>
</View>
);
<CustomMapView
markers={[
{
coordinate: [2.3522, 48.8566],
children: <CustomMarker title="Custom Pin" />
}
]}
/>
Clustering
<CustomMapView
enableClustering={true}
clusterRadius={50}
markers={largeMarkerArray}
onClusterPress={(cluster) => {
console.log(`Cluster with ${cluster.pointCount} markers`);
}}
/>
Offline Maps
import { useOfflineMap } from '@chauffleet/expo-custom-map';
const OfflineMapExample = () => {
const { downloadRegion, isDownloading } = useOfflineMap();
const handleDownload = async () => {
await downloadRegion({
bounds: { north: 48.9, south: 48.8, east: 2.4, west: 2.3 },
minZoom: 10,
maxZoom: 16
});
};
return (
<CustomMapView
tileUrlTemplate="offline://{z}/{x}/{y}"
offlineTileProvider={(z, x, y) => getCachedTile(z, x, y)}
/>
);
};
Navigation & Routing
import { calculateRoute } from '@chauffleet/expo-custom-map';
const NavigationExample = () => {
const [route, setRoute] = useState([]);
const getDirections = async () => {
const result = await calculateRoute(
[2.3522, 48.8566], // Start
[2.2945, 48.8584], // End
{ profile: 'driving' }
);
setRoute(result.coordinates);
};
return (
<CustomMapView
polylines={[{
coordinates: route,
strokeColor: '#007AFF',
strokeWidth: 5
}]}
/>
);
};
π§ Hooks & Utilities
useMapPerformance
import { useMapPerformance } from '@chauffleet/expo-custom-map';
const MapWithStats = () => {
const { stats, cache, preloader } = useMapPerformance();
return (
<View>
<Text>FPS: {stats.fps}</Text>
<Text>Cache Hit Rate: {stats.cacheHitRate}%</Text>
<CustomMapView ... />
</View>
);
};
useOptimizedGestures
import { useOptimizedGestures } from '@chauffleet/expo-custom-map';
const OptimizedMap = () => {
const { throttledRegionChange } = useOptimizedGestures((region) => {
console.log('Region changed:', region);
});
return (
<CustomMapView
onRegionChange={throttledRegionChange}
/>
);
};
π Performance Comparison
Feature | WebView | @chauffleet/expo-custom-map | Improvement |
---|---|---|---|
FPS | 30-45 | 55-60 | +67% |
Gesture Response | 50-100ms | 16-33ms | -70% |
Memory Usage | 80-120MB | 40-60MB | -50% |
Cold Start | 2-4s | 0.5-1s | -75% |
π§ͺ Testing
npm run test
npm run test:watch
π Building
npm run build
npm run build:watch
π± Example App
Run the example app:
cd example
npm install
expo start
π€ Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature
) - Commit your changes (
git commit -m 'Add some AmazingFeature'
) - Push to the branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
π Changelog
See CHANGELOG.md for details.
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π Acknowledgments
- MapLibre for inspiration
- React Native Skia for rendering
- OpenStreetMap for free tile data
π Support
- π Documentation
- π Issues
- π¬ Discussions
Made with β€οΈ by Chauffleet