UNPKG

@chauffleet/expo-custom-map

Version:

Open source custom map library for Expo/React Native. Use your own tiles without Google Maps, Mapbox, or API keys. Created by ChaufFleet.

212 lines 10.2 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 }); exports.CustomMapView = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = require("react"); const react_native_1 = require("react-native"); const react_native_reanimated_1 = __importStar(require("react-native-reanimated")); const react_native_gesture_handler_1 = require("react-native-gesture-handler"); const TileLayer_1 = require("./components/TileLayer"); const { width: screenWidth, height: screenHeight } = react_native_1.Dimensions.get('window'); // Constantes optimisées pour la fluidité parfaite Google Maps const MIN_ZOOM = 1; const MAX_ZOOM = 18; const DECAY_RATE = 0.998; const VELOCITY_SCALE = 0.8; /** * CustomMapView - Version ultra-optimisée pour une fluidité Google Maps * * Principes clés : * 1. Toutes les animations restent sur l'UI thread * 2. Minimal runOnJS() calls * 3. Séparation claire entre gesture transforms et region state * 4. TileLayer optimisé avec memoization agressive */ const CustomMapView = ({ initialRegion = { latitude: 48.8566, longitude: 2.3522, latitudeDelta: 0.0922, longitudeDelta: 0.0421, }, onRegionChange, onRegionChangeComplete, style, tileUrlTemplate = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', children, }) => { // Région de base - change uniquement quand les gestes se terminent const baseRegion = (0, react_1.useRef)(initialRegion); const isGesturing = (0, react_1.useRef)(false); // Valeurs animées - PURE UI THREAD const translateX = (0, react_native_reanimated_1.useSharedValue)(0); const translateY = (0, react_native_reanimated_1.useSharedValue)(0); const scale = (0, react_native_reanimated_1.useSharedValue)(1); // Point focal pour le zoom (centre de l'écran par défaut) const focalX = (0, react_native_reanimated_1.useSharedValue)(screenWidth / 2); const focalY = (0, react_native_reanimated_1.useSharedValue)(screenHeight / 2); // Fonction de conversion géographique optimisée (côté JS) const calculateRegionFromTransforms = (0, react_1.useCallback)((deltaX, deltaY, scaleChange, centerX = screenWidth / 2, centerY = screenHeight / 2) => { const base = baseRegion.current; // Calcul du déplacement en degrés const pixelsPerDegreeLat = screenHeight / base.latitudeDelta; const pixelsPerDegreeLon = screenWidth / base.longitudeDelta; const latChange = -deltaY / pixelsPerDegreeLat; const lonChange = -deltaX / pixelsPerDegreeLon; // Nouveau centre après déplacement let newLat = base.latitude + latChange; let newLon = base.longitude + lonChange; // Calcul du nouveau zoom const newLatDelta = base.latitudeDelta / scaleChange; const newLonDelta = base.longitudeDelta / scaleChange; // Ajustement du centre pour le point focal du zoom if (scaleChange !== 1) { const focalOffsetX = centerX - screenWidth / 2; const focalOffsetY = centerY - screenHeight / 2; const focalLatOffset = -focalOffsetY / pixelsPerDegreeLat; const focalLonOffset = -focalOffsetX / pixelsPerDegreeLon; newLat += focalLatOffset * (1 - 1 / scaleChange); newLon += focalLonOffset * (1 - 1 / scaleChange); } // Contraintes newLat = Math.max(-85, Math.min(85, newLat)); const maxDelta = 180; const minDelta = 0.0001; return { latitude: newLat, longitude: newLon, latitudeDelta: Math.max(minDelta, Math.min(maxDelta, newLatDelta)), longitudeDelta: Math.max(minDelta, Math.min(maxDelta, newLonDelta)), }; }, []); // Mise à jour région - uniquement quand nécessaire const updateRegionState = (0, react_1.useCallback)((newRegion) => { baseRegion.current = newRegion; onRegionChange?.(newRegion); }, [onRegionChange]); const finalizeRegion = (0, react_1.useCallback)((finalRegion) => { baseRegion.current = finalRegion; onRegionChangeComplete?.(finalRegion); }, [onRegionChangeComplete]); // Geste Pan - Ultra optimisé const panGesture = react_native_gesture_handler_1.Gesture.Pan() .minDistance(1) .onBegin(() => { isGesturing.current = true; (0, react_native_reanimated_1.cancelAnimation)(translateX); (0, react_native_reanimated_1.cancelAnimation)(translateY); }) .onChange((event) => { // PURE UI THREAD - Aucun runOnJS ici ! translateX.value = event.translationX; translateY.value = event.translationY; }) .onEnd((event) => { const hasVelocity = Math.abs(event.velocityX) > 300 || Math.abs(event.velocityY) > 300; if (hasVelocity) { // Inertie fluide translateX.value = (0, react_native_reanimated_1.withDecay)({ velocity: event.velocityX * VELOCITY_SCALE, deceleration: DECAY_RATE, rubberBandEffect: true, clamp: [-screenWidth * 3, screenWidth * 3], }); translateY.value = (0, react_native_reanimated_1.withDecay)({ velocity: event.velocityY * VELOCITY_SCALE, deceleration: DECAY_RATE, rubberBandEffect: true, clamp: [-screenHeight * 3, screenHeight * 3], }, (finished) => { if (finished) { // Une seule fois à la fin (0, react_native_reanimated_1.runOnJS)(() => { const finalRegion = calculateRegionFromTransforms(translateX.value, translateY.value, scale.value); // Reset des transforms translateX.value = 0; translateY.value = 0; scale.value = 1; isGesturing.current = false; finalizeRegion(finalRegion); })(); } }); } else { // Pas d'inertie - finalisation immédiate (0, react_native_reanimated_1.runOnJS)(() => { const finalRegion = calculateRegionFromTransforms(translateX.value, translateY.value, scale.value); translateX.value = 0; translateY.value = 0; scale.value = 1; isGesturing.current = false; finalizeRegion(finalRegion); })(); } }); // Geste Pinch - Ultra optimisé avec focal point const pinchGesture = react_native_gesture_handler_1.Gesture.Pinch() .onBegin(() => { isGesturing.current = true; (0, react_native_reanimated_1.cancelAnimation)(scale); }) .onUpdate((event) => { // PURE UI THREAD scale.value = Math.max(0.5, Math.min(4, event.scale)); focalX.value = event.focalX; focalY.value = event.focalY; }) .onEnd(() => { (0, react_native_reanimated_1.runOnJS)(() => { const finalRegion = calculateRegionFromTransforms(translateX.value, translateY.value, scale.value, focalX.value, focalY.value); translateX.value = 0; translateY.value = 0; scale.value = 1; focalX.value = screenWidth / 2; focalY.value = screenHeight / 2; isGesturing.current = false; finalizeRegion(finalRegion); })(); }); // Gestes simultanés const composedGesture = react_native_gesture_handler_1.Gesture.Simultaneous(panGesture, pinchGesture); // Style animé - PURE UI THREAD const animatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => { return { transform: [ { translateX: translateX.value }, { translateY: translateY.value }, { scale: scale.value }, ], }; }, []); return ((0, jsx_runtime_1.jsx)(react_native_gesture_handler_1.GestureHandlerRootView, { style: [{ flex: 1 }, style], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: { flex: 1, overflow: 'hidden' }, children: (0, jsx_runtime_1.jsx)(react_native_gesture_handler_1.GestureDetector, { gesture: composedGesture, children: (0, jsx_runtime_1.jsxs)(react_native_reanimated_1.default.View, { style: [{ flex: 1 }, animatedStyle], children: [(0, jsx_runtime_1.jsx)(TileLayer_1.TileLayer, { region: baseRegion.current, tileUrlTemplate: tileUrlTemplate, screenWidth: screenWidth, screenHeight: screenHeight, isGesturing: isGesturing.current }), children] }) }) }) })); }; exports.CustomMapView = CustomMapView; exports.default = exports.CustomMapView; //# sourceMappingURL=CustomMapView.js.map