locate-the-news / frontend / src / utils / mapAnchorUtils.js
mapAnchorUtils.js
Raw
/**
 * 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
    };
};