expo-osm-sdk
Version:
OpenStreetMap component for React Native with Expo
547 lines • 21.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const react_native_1 = require("react-native");
// Dynamic import to handle when MapLibre isn't available
let maplibregl = null;
const loadMapLibre = async () => {
try {
if (typeof window !== 'undefined') {
maplibregl = (await Promise.resolve().then(() => __importStar(require('maplibre-gl')))).default;
// Load CSS dynamically using DOM manipulation
if (!document.querySelector('link[href*="maplibre-gl.css"]')) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css';
document.head.appendChild(link);
}
}
return true;
}
catch (error) {
console.warn('MapLibre GL JS not available:', error);
return false;
}
};
/**
* MapLibre GL JS implementation for OSMView on web
*
* Simple implementation focusing on:
* - Base map rendering
* - Zoom controls
* - Basic layer switching
*/
const MapLibreOSMView = (0, react_1.forwardRef)((props, ref) => {
const { style, initialCenter = { latitude: 22.5726, longitude: 88.3639 }, // Default to Kolkata
initialZoom = 10, onMapReady, onPress, onRegionChange, tileServerUrl, ...restProps } = props;
const mapContainer = (0, react_1.useRef)(null);
const map = (0, react_1.useRef)(null);
const [mapLoaded, setMapLoaded] = (0, react_1.useState)(false);
const [currentLayer, setCurrentLayer] = (0, react_1.useState)('osm');
const [mapLibreReady, setMapLibreReady] = (0, react_1.useState)(false);
// Load MapLibre on mount
(0, react_1.useEffect)(() => {
loadMapLibre().then(setMapLibreReady);
}, []);
// Initialize map when MapLibre is ready
(0, react_1.useEffect)(() => {
if (!mapLibreReady || !maplibregl || !mapContainer.current || map.current)
return;
console.log('🗺️ Initializing MapLibre map...');
try {
// Create map instance
map.current = new maplibregl.Map({
container: mapContainer.current,
center: [initialCenter.longitude, initialCenter.latitude],
zoom: initialZoom,
style: getMapStyle(currentLayer, tileServerUrl),
attributionControl: true,
});
// Map load event
map.current.on('load', () => {
console.log('🗺️ MapLibre map loaded');
setMapLoaded(true);
onMapReady?.();
});
// Click events
map.current.on('click', (e) => {
if (onPress) {
onPress({
latitude: e.lngLat.lat,
longitude: e.lngLat.lng,
});
}
});
// Region change events
map.current.on('moveend', () => {
if (onRegionChange && map.current) {
const center = map.current.getCenter();
const zoom = map.current.getZoom();
onRegionChange({
latitude: center.lat,
longitude: center.lng,
latitudeDelta: 0.01, // Approximation
longitudeDelta: 0.01, // Approximation
});
}
});
}
catch (error) {
console.error('❌ Failed to initialize MapLibre map:', error);
}
// Cleanup
return () => {
if (map.current) {
map.current.remove();
map.current = null;
}
};
}, [mapLibreReady, initialCenter, initialZoom, onMapReady, onPress, onRegionChange, tileServerUrl]);
// Update map style when layer changes
(0, react_1.useEffect)(() => {
if (map.current && mapLoaded) {
const newStyle = getMapStyle(currentLayer, tileServerUrl);
map.current.setStyle(newStyle);
}
}, [currentLayer, tileServerUrl, mapLoaded]);
// Implement OSMViewRef methods
(0, react_1.useImperativeHandle)(ref, () => ({
// Zoom methods
zoomIn: async () => {
if (map.current) {
map.current.zoomIn();
}
},
zoomOut: async () => {
if (map.current) {
map.current.zoomOut();
}
},
setZoom: async (zoom) => {
if (map.current) {
map.current.setZoom(zoom);
}
},
// Camera methods
animateToLocation: async (latitude, longitude, zoom) => {
if (map.current) {
map.current.flyTo({
center: [longitude, latitude],
zoom: zoom || map.current.getZoom(),
essential: true,
});
}
},
animateToRegion: async (region, duration) => {
if (map.current) {
map.current.flyTo({
center: [region.longitude, region.latitude],
zoom: calculateZoomFromRegion(region),
duration: duration || 1000,
});
}
},
fitToMarkers: async () => {
console.log('MapLibre: fitToMarkers - not implemented yet');
},
// Camera orientation methods
setPitch: async (pitch) => {
if (map.current) {
map.current.setPitch(pitch);
}
},
setBearing: async (bearing) => {
if (map.current) {
map.current.setBearing(bearing);
}
},
getPitch: async () => {
return map.current?.getPitch() || 0;
},
getBearing: async () => {
return map.current?.getBearing() || 0;
},
animateCamera: async (options) => {
if (map.current) {
map.current.flyTo({
center: options.latitude && options.longitude ? [options.longitude, options.latitude] : undefined,
zoom: options.zoom,
pitch: options.pitch,
bearing: options.bearing,
duration: options.duration,
});
}
},
// Location methods - basic implementations
getCurrentLocation: async () => {
console.log('MapLibre: getCurrentLocation - returning center');
if (map.current) {
const center = map.current.getCenter();
return { latitude: center.lat, longitude: center.lng };
}
return initialCenter;
},
startLocationTracking: async () => {
console.log('MapLibre: startLocationTracking - not implemented yet');
},
stopLocationTracking: async () => {
console.log('MapLibre: stopLocationTracking - not implemented yet');
},
waitForLocation: async () => {
return initialCenter;
},
// Marker methods - placeholders for now
addMarker: async () => { console.log('MapLibre: addMarker - not implemented yet'); },
removeMarker: async () => { console.log('MapLibre: removeMarker - not implemented yet'); },
updateMarker: async () => { console.log('MapLibre: updateMarker - not implemented yet'); },
animateMarker: async () => { console.log('MapLibre: animateMarker - not implemented yet'); },
showInfoWindow: async () => { console.log('MapLibre: showInfoWindow - not implemented yet'); },
hideInfoWindow: async () => { console.log('MapLibre: hideInfoWindow - not implemented yet'); },
// Overlay methods - implemented for route display
addPolyline: async (polylineConfig) => {
if (!map.current)
return;
const { id, coordinates, strokeColor = '#007AFF', strokeWidth = 5, strokeOpacity = 0.8 } = polylineConfig;
// Convert coordinates to GeoJSON format
const geojsonData = {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: coordinates.map((coord) => [coord.longitude, coord.latitude])
}
};
// Add source if it doesn't exist
if (!map.current.getSource(id)) {
map.current.addSource(id, {
type: 'geojson',
data: geojsonData
});
// Add layer for the route line
map.current.addLayer({
id: id,
type: 'line',
source: id,
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': strokeColor,
'line-width': strokeWidth,
'line-opacity': strokeOpacity
}
});
}
else {
// Update existing source
map.current.getSource(id).setData(geojsonData);
}
console.log('MapLibre: Added polyline with', coordinates.length, 'coordinates');
},
removePolyline: async (polylineId) => {
if (!map.current)
return;
if (map.current.getLayer(polylineId)) {
map.current.removeLayer(polylineId);
}
if (map.current.getSource(polylineId)) {
map.current.removeSource(polylineId);
}
console.log('MapLibre: Removed polyline', polylineId);
},
updatePolyline: async (polylineConfig) => {
if (!map.current)
return;
// Remove and re-add for update
await ref.current?.removePolyline(polylineConfig.id);
await ref.current?.addPolyline(polylineConfig);
},
addPolygon: async () => { console.log('MapLibre: addPolygon - not implemented yet'); },
removePolygon: async () => { console.log('MapLibre: removePolygon - not implemented yet'); },
updatePolygon: async () => { console.log('MapLibre: updatePolygon - not implemented yet'); },
addCircle: async () => { console.log('MapLibre: addCircle - not implemented yet'); },
removeCircle: async () => { console.log('MapLibre: removeCircle - not implemented yet'); },
updateCircle: async () => { console.log('MapLibre: updateCircle - not implemented yet'); },
addOverlay: async () => { console.log('MapLibre: addOverlay - not implemented yet'); },
removeOverlay: async () => { console.log('MapLibre: removeOverlay - not implemented yet'); },
updateOverlay: async () => { console.log('MapLibre: updateOverlay - not implemented yet'); },
// Utility methods
coordinateForPoint: async () => {
return initialCenter;
},
pointForCoordinate: async () => {
return { x: 0, y: 0 };
},
takeSnapshot: async () => {
console.log('MapLibre: takeSnapshot - not implemented yet');
return '';
},
isViewReady: async () => {
return mapLoaded;
},
// Route display methods for OSRM integration
displayRoute: async (coordinates, options = {}) => {
if (!map.current)
return;
const { color = '#007AFF', width = 5, opacity = 0.8 } = options;
const routeId = 'current-route';
// Use the addPolyline method we just implemented
await ref.current?.addPolyline({
id: routeId,
coordinates,
strokeColor: color,
strokeWidth: width,
strokeOpacity: opacity
});
console.log('MapLibre: Displayed route with', coordinates.length, 'coordinates');
},
clearRoute: async () => {
if (!map.current)
return;
const routeId = 'current-route';
await ref.current?.removePolyline(routeId);
console.log('MapLibre: Cleared route');
},
fitRouteInView: async (coordinates, padding = 50) => {
if (!map.current || !coordinates.length)
return;
// Calculate bounds from coordinates
const bounds = coordinates.reduce((bounds, coord) => {
return bounds.extend([coord.longitude, coord.latitude]);
}, new maplibregl.LngLatBounds());
// Fit the map to the route bounds
map.current.fitBounds(bounds, {
padding: padding,
maxZoom: 16
});
console.log('MapLibre: Fitted route in view with', coordinates.length, 'coordinates');
},
}), [mapLoaded, initialCenter]);
// Toggle layer function
const toggleLayer = () => {
setCurrentLayer(prev => prev === 'osm' ? 'satellite' : 'osm');
};
// Handle zoom in
const handleZoomIn = () => {
if (map.current) {
map.current.zoomIn();
}
};
// Handle zoom out
const handleZoomOut = () => {
if (map.current) {
map.current.zoomOut();
}
};
if (!mapLibreReady) {
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.container, style], children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.loadingContainer, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.loadingText, children: "\uD83D\uDDFA\uFE0F Loading MapLibre..." }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.loadingSubtext, children: "Make sure you have installed maplibre-gl: npm install maplibre-gl" })] }) }));
}
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, style], children: [(0, jsx_runtime_1.jsx)("div", { ref: mapContainer, style: { width: '100%', height: '100%' } }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.controls, children: [(0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.layerButton, onPress: toggleLayer, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.buttonText, children: currentLayer === 'osm' ? '🗺️ OSM' : '📡 Satellite' }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.zoomControls, children: [(0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.zoomButton, onPress: handleZoomIn, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.zoomButtonText, children: "+" }) }), (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.zoomButton, onPress: handleZoomOut, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.zoomButtonText, children: "\u2212" }) })] })] }), mapLoaded && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.statusIndicator, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.statusText, children: "\uD83D\uDFE2 MapLibre Ready" }) }))] }));
});
// Helper function to get map style
const getMapStyle = (layer, customTileUrl) => {
if (customTileUrl) {
return {
version: 8,
sources: {
'custom-tiles': {
type: 'raster',
tiles: [customTileUrl],
tileSize: 256,
attribution: 'Custom tiles',
},
},
layers: [
{
id: 'custom-layer',
type: 'raster',
source: 'custom-tiles',
},
],
};
}
if (layer === 'satellite') {
return {
version: 8,
sources: {
'satellite': {
type: 'raster',
tiles: [
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
],
tileSize: 256,
attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',
},
},
layers: [
{
id: 'satellite-layer',
type: 'raster',
source: 'satellite',
},
],
};
}
// Default OSM style
return {
version: 8,
sources: {
'osm': {
type: 'raster',
tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '© OpenStreetMap contributors',
},
},
layers: [
{
id: 'osm-layer',
type: 'raster',
source: 'osm',
},
],
};
};
// Helper function to calculate zoom from region
const calculateZoomFromRegion = (region) => {
// Simple approximation - can be improved
const latDelta = region.latitudeDelta;
const lngDelta = region.longitudeDelta;
const delta = Math.max(latDelta, lngDelta);
if (delta > 10)
return 3;
if (delta > 5)
return 5;
if (delta > 2)
return 7;
if (delta > 1)
return 9;
if (delta > 0.5)
return 11;
if (delta > 0.1)
return 13;
return 15;
};
const styles = react_native_1.StyleSheet.create({
container: {
flex: 1,
position: 'relative',
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f8fafc',
padding: 20,
},
loadingText: {
fontSize: 18,
fontWeight: '600',
color: '#2d3748',
marginBottom: 8,
},
loadingSubtext: {
fontSize: 14,
color: '#718096',
textAlign: 'center',
lineHeight: 20,
},
controls: {
position: 'absolute',
top: 16,
right: 16,
gap: 8,
zIndex: 1000,
},
layerButton: {
backgroundColor: '#ffffff',
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
buttonText: {
fontSize: 14,
fontWeight: '600',
color: '#2d3748',
},
zoomControls: {
backgroundColor: '#ffffff',
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
overflow: 'hidden',
},
zoomButton: {
width: 40,
height: 40,
justifyContent: 'center',
alignItems: 'center',
borderBottomWidth: 0.5,
borderBottomColor: '#e2e8f0',
},
zoomButtonText: {
fontSize: 20,
fontWeight: 'bold',
color: '#4a90e2',
},
statusIndicator: {
position: 'absolute',
bottom: 16,
left: 16,
backgroundColor: 'rgba(255, 255, 255, 0.9)',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 4,
},
statusText: {
fontSize: 12,
color: '#2d3748',
},
});
// Set display name for debugging
MapLibreOSMView.displayName = 'MapLibreOSMView';
exports.default = MapLibreOSMView;
//# sourceMappingURL=OSMView.maplibre.web.js.map