/** * Map anchor utilities for positioning glassmorphism cards * relative to map coordinates */ /** * Convert lat/lng coordinates to screen pixel position * @param {Object} map - Mapbox map instance * @param {number} lng - Longitude * @param {number} lat - Latitude * @returns {Object} {x, y} screen coordinates */ export const lngLatToScreen = (map, lng, lat) => { if (!map) return { x: 0, y: 0 }; const point = map.project([lng, lat]); return { x: point.x, y: point.y }; }; /** * Adjust card position to prevent overflow off screen edges * @param {Object} position - {x, y} screen position * @param {Object} cardDimensions - {width, height} of card * @param {Object} viewport - {width, height} of viewport * @returns {Object} Adjusted {x, y, placement} position */ export const adjustCardPosition = (position, cardDimensions, viewport) => { const PADDING = 20; const OFFSET_Y = -20; // Offset above the point let x = position.x; let y = position.y + OFFSET_Y; let placement = 'top'; // 'top', 'bottom', 'left', 'right' // Check right edge if (x + cardDimensions.width + PADDING > viewport.width) { x = viewport.width - cardDimensions.width - PADDING; placement = 'left'; } // Check left edge if (x < PADDING) { x = PADDING; placement = 'right'; } // Check top edge if (y < PADDING) { y = position.y + 40; // Position below point instead placement = 'bottom'; } // Check bottom edge if (y + cardDimensions.height + PADDING > viewport.height) { y = viewport.height - cardDimensions.height - PADDING; } return { x, y, placement }; }; /** * Calculate anchor line path (optional visual connection) * @param {Object} cardPosition - Card {x, y} position * @param {Object} pointPosition - Point {x, y} position * @returns {string} SVG path string */ export const getAnchorLinePath = (cardPosition, pointPosition) => { const controlPointOffset = 20; // Create a curved line from point to card return `M ${pointPosition.x} ${pointPosition.y} Q ${pointPosition.x} ${pointPosition.y - controlPointOffset}, ${cardPosition.x + 20} ${cardPosition.y}`; }; /** * Check if position is within viewport bounds * @param {Object} position - {x, y} position * @param {Object} viewport - {width, height} viewport dimensions * @returns {boolean} True if within bounds */ export const isInViewport = (position, viewport) => { return ( position.x >= 0 && position.x <= viewport.width && position.y >= 0 && position.y <= viewport.height ); }; /** * Smoothly interpolate between two positions * @param {Object} from - Starting {x, y} position * @param {Object} to - Target {x, y} position * @param {number} progress - Progress from 0 to 1 * @returns {Object} Interpolated {x, y} position */ export const interpolatePosition = (from, to, progress) => { return { x: from.x + (to.x - from.x) * progress, y: from.y + (to.y - from.y) * progress }; };