UNPKG

rs-react-native-map-clustering

Version:

React Native Map Clustering both for Android and iOS with TypeScript support

213 lines (212 loc) 8.58 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.returnMarkerStyle = exports.generateSpiral = exports.markerToGeoJSONFeature = exports.returnMapZoom = exports.calculateBBox = exports.isMarker = void 0; const react_native_1 = require("react-native"); const geo_viewport_1 = __importDefault(require("@mapbox/geo-viewport")); // ---------------------------------------------------------------------- const { width, height } = react_native_1.Dimensions.get('window'); const isMarker = (child) => { return !!(child && child.props && child.props.coordinate && child.props.cluster !== false); }; exports.isMarker = isMarker; const calculateBBox = (region) => { let lngD; if (region.longitudeDelta < 0) lngD = region.longitudeDelta + 360; else lngD = region.longitudeDelta; return [ region.longitude - lngD, // westLng - min lng region.latitude - region.latitudeDelta, // southLat - min lat region.longitude + lngD, // eastLng - max lng region.latitude + region.latitudeDelta // northLat - max lat ]; }; exports.calculateBBox = calculateBBox; const returnMapZoom = (region, bBox, minZoom) => { const viewport = region.longitudeDelta >= 40 ? { zoom: minZoom } : geo_viewport_1.default.viewport(bBox, [width, height]); return viewport.zoom; }; exports.returnMapZoom = returnMapZoom; const markerToGeoJSONFeature = (marker, index) => { // Avoid object spread for better performance const props = _removeChildrenFromProps(marker.props); return { type: 'Feature', geometry: { coordinates: [marker.props.coordinate.longitude, marker.props.coordinate.latitude], type: 'Point' }, properties: { point_count: 0, index, ...props } }; }; exports.markerToGeoJSONFeature = markerToGeoJSONFeature; // Pre-calculate cosine and sine values for common angles for faster spiral generation const ANGLE_CACHE = {}; for (let i = 0; i < 200; i += 0.25) { const angle = 0.25 * (i * 0.5); ANGLE_CACHE[angle.toFixed(2)] = { cos: Math.cos(angle), sin: Math.sin(angle) }; } const generateSpiral = (marker, clusterChildren, markers, index) => { const { properties, geometry } = marker; const count = properties.point_count; const centerLocation = geometry.coordinates; // Performance optimization for extremely large clusters if (count > 500) { // For extremely large clusters, just generate a sample of locations // to prevent browser slowdown const sampleSize = 75; // Fixed sample size for giant clusters const sampleStep = Math.floor(count / sampleSize); // Pre-calculate everything needed for the spiral const centerPoint = { latitude: centerLocation[1], longitude: centerLocation[0] }; // Use higher density for fewer points so they spread out more const spiralDensity = 0.0006; const result = []; // Sample evenly through the cluster children for (let i = 0; i < sampleSize; i++) { const childIndex = i * sampleStep; if (!clusterChildren[childIndex]) continue; const angle = 0.25 * (i * 0.5); // Use cached trig values when possible (huge performance boost) const angleKey = angle.toFixed(2); const { cos: cosAngle, sin: sinAngle } = ANGLE_CACHE[angleKey] || { cos: Math.cos(angle), sin: Math.sin(angle) }; if (clusterChildren[childIndex] && clusterChildren[childIndex].properties && typeof clusterChildren[childIndex].properties.index === 'number') { result.push({ index: clusterChildren[childIndex].properties.index, longitude: centerLocation[0] + spiralDensity * angle * sinAngle, latitude: centerLocation[1] + spiralDensity * angle * cosAngle, centerPoint }); } } return result; } // For very large clusters, limit the number of rendered spider legs for performance const maxSpiderPoints = count > 200 ? 75 : count > 100 ? 50 : count; const res = []; let angle = 0; // Avoid unnecessary calculations by skipping start index computation if it's not needed let start = 0; if (index > 0) { // Fast start index calculation with caching if (index < markers.length / 2) { // For markers in the first half, iterate forward for (let i = 0; i < index; i++) { start += markers[i].properties.point_count || 0; } } else { // For markers in the second half, use sum difference optimization start = 0; for (let i = 0; i < markers.length; i++) { if (i < index) { start += markers[i].properties.point_count || 0; } } } } // Pre-calculate centerPoint for reuse const centerPoint = { latitude: centerLocation[1], longitude: centerLocation[0] }; // Adaptive spiral density based on cluster size const spiralDensity = count > 100 ? 0.0004 : count > 50 ? 0.0003 : 0.00025; // Calculate step size once const step = count > maxSpiderPoints ? Math.floor(count / maxSpiderPoints) : 1; const positions = Math.min(count, maxSpiderPoints); // Use object pooling to reduce GC pressure const point = { latitude: 0, longitude: 0 }; // Batch creation of all markers for (let i = 0; i < positions; i++) { const actualIndex = i * step; angle = 0.25 * (actualIndex * 0.5); // Use cached trig values when possible (huge performance boost) const angleKey = angle.toFixed(2); const cached = ANGLE_CACHE[angleKey]; const cosAngle = cached ? cached.cos : Math.cos(angle); const sinAngle = cached ? cached.sin : Math.sin(angle); point.latitude = centerLocation[1] + spiralDensity * angle * cosAngle; point.longitude = centerLocation[0] + spiralDensity * angle * sinAngle; const childIndex = actualIndex + start; if (clusterChildren[childIndex] && clusterChildren[childIndex].properties && typeof clusterChildren[childIndex].properties.index === 'number') { res.push({ index: clusterChildren[childIndex].properties.index, longitude: point.longitude, latitude: point.latitude, centerPoint }); } } return res; }; exports.generateSpiral = generateSpiral; // Create a lookup table for marker styles for common cluster sizes const MARKER_STYLES = { 4: { width: 54, height: 54, size: 40, fontSize: 16 }, 8: { width: 60, height: 60, size: 46, fontSize: 17 }, 10: { width: 66, height: 66, size: 50, fontSize: 17 }, 15: { width: 72, height: 72, size: 54, fontSize: 18 }, 25: { width: 78, height: 78, size: 58, fontSize: 19 }, 50: { width: 84, height: 84, size: 64, fontSize: 20 }, 100: { width: 90, height: 90, size: 70, fontSize: 22 } }; // Default style for small clusters const DEFAULT_STYLE = { width: 48, height: 48, size: 36, fontSize: 15 }; /** * Optimized marker style calculation that uses a lookup table for common values * to avoid repetitive condition checks for better performance with large datasets */ const returnMarkerStyle = (points) => { // Check common sizes first if (MARKER_STYLES[points]) { return MARKER_STYLES[points]; } // For very large clusters, cap the size if (points > 100) { return MARKER_STYLES[100]; } // For intermediate values, find the closest lower bound if (points > 50) return MARKER_STYLES[50]; if (points > 25) return MARKER_STYLES[25]; if (points > 15) return MARKER_STYLES[15]; if (points > 10) return MARKER_STYLES[10]; if (points > 8) return MARKER_STYLES[8]; if (points > 4) return MARKER_STYLES[4]; // Default style for small clusters return DEFAULT_STYLE; }; exports.returnMarkerStyle = returnMarkerStyle; const _removeChildrenFromProps = (props) => { const { children, ...newProps } = props; return newProps; };