UNPKG

expo-osm-sdk

Version:

OpenStreetMap component for React Native with Expo

547 lines 21.4 kB
"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