{"version":3,"file":"index.node.mjs","names":[],"sources":["../../src/calculator/ElevationFunctions.ts","../../src/calculator/ElevationCalculator.ts","../../src/types.ts","../../src/utils/Logger.ts","../../src/utils/index.ts","../../src/utils/Constants.ts","../../src/utils/Distance.ts","../../src/utils/Vector3D.ts","../../src/utils/EcefConverter.ts","../../src/utils/DouglasPeucker.ts","../../src/utils/ElevationSmoother.ts","../../src/calculator/Reactive.ts","../../src/calculator/BatchCalculator.ts","../../src/tile/cache/ReentrantLock.ts","../../src/tile/cache/Cache.ts","../../src/tile/cache/index.ts","../../src/tile/fetcher/TileLoader.ts","../../src/tile/fetcher/nodejs/NodeJsTile.ts","../../src/tile/fetcher/nodejs/NodeJsTileFetcher.ts","../../src/tile/TileManager.ts","../../src/tile/Tile.ts","../../src/tile/index.ts","../../src/ElevationProvider.ts","../../src/utils/ElevationGain.ts"],"sourcesContent":["import { Coordinates, Pixel, TileCoordinates, TileCoordinatesFloat } from '../types';\n\n// ========================================================================\n// INTERNAL FUNCTIONS (exported for tests)\n// ========================================================================\n\n/**\n * Convert degrees to radians\n */\nexport function degToRad(degrees: number): number {\n    return (degrees * Math.PI) / 180;\n}\n\n/**\n * Validate latitude is within Web Mercator bounds\n */\nexport function isValidLatitude(lat: number): boolean {\n    return lat >= -85.0511 && lat <= 85.0511;\n}\n\n/**\n * Validate longitude is within valid range\n */\nexport function isValidLongitude(lon: number): boolean {\n    return lon >= -180 && lon <= 180;\n}\n\n/**\n * Validate zoom level is within supported range\n */\nexport function isValidZoomLevel(zoom: number): boolean {\n    return Number.isInteger(zoom) && zoom >= 0 && zoom <= 15;\n}\n\nexport function normalizePixel(pixel: Pixel, tileSize: number): Pixel {\n    let { x, y } = pixel;\n    const tile = pixel.tile;\n    let tileX = tile.x;\n    let tileY = tile.y;\n    const z = tile.z;\n\n    if (x < 0) {\n        x += tileSize;\n        tileX -= 1;\n    }\n    if (x >= tileSize) {\n        x -= tileSize;\n        tileX += 1;\n    }\n    if (y < 0) {\n        y += tileSize;\n        tileY -= 1;\n    }\n    if (y >= tileSize) {\n        y -= tileSize;\n        tileY += 1;\n    }\n\n    const maxTile = Math.pow(2, z) - 1;\n    tileX = Math.max(0, Math.min(maxTile, tileX));\n    tileY = Math.max(0, Math.min(maxTile, tileY));\n\n    return { tile: { z, x: tileX, y: tileY }, x, y };\n}\n\nexport function toTileCoordinatesFloat(coords: Coordinates, z: number): TileCoordinatesFloat {\n    // Input validation\n    if (!isValidLatitude(coords.latitude)) {\n        throw new Error(\n            `Invalid latitude: ${coords.latitude}. Must be between -85.0511 and 85.0511`\n        );\n    }\n\n    if (!isValidLongitude(coords.longitude)) {\n        throw new Error(`Invalid longitude: ${coords.longitude}. Must be between -180 and 180`);\n    }\n\n    if (!isValidZoomLevel(z)) {\n        throw new Error(`Invalid zoom level: ${z}. Must be between 0 and 15`);\n    }\n\n    // Web Mercator projection calculation\n    const lat = degToRad(coords.latitude);\n    const n = Math.pow(2, z);\n    const xFloat = ((coords.longitude + 180) / 360) * n;\n    const yFloat = ((1 - Math.log(Math.tan(lat) + 1 / Math.cos(lat)) / Math.PI) / 2) * n;\n\n    let x = Math.floor(xFloat);\n    let y = Math.floor(yFloat);\n\n    // Clamp tile coordinates to valid range for the zoom level\n    const maxTile = n - 1;\n    x = Math.max(0, Math.min(maxTile, x));\n    y = Math.max(0, Math.min(maxTile, y));\n\n    return {\n        x,\n        y,\n        xFloat,\n        yFloat,\n        z,\n    };\n}\n\nexport function toTileCoordinates(coords: Coordinates, z: number): TileCoordinates {\n    const tile = toTileCoordinatesFloat(coords, z);\n    return {\n        x: tile.x,\n        y: tile.y,\n        z: tile.z,\n    };\n}\n\n/**\n * Convert WGS84 coordinates to Web Mercator tile pixel coordinates\n * @param coords - WGS84 latitude/longitude coordinates\n * @param z - Zoom level (0-15)\n * @returns Pixel coordinates within the appropriate tile\n */\nexport function toPixel(coords: Coordinates, z: number, tileSize: number): Pixel {\n    const tile = toTileCoordinatesFloat(coords, z);\n\n    // Calculate pixel coordinates within the tile\n    const x = Math.floor((tile.xFloat - tile.x) * tileSize);\n    const y = Math.floor((tile.yFloat - tile.y) * tileSize);\n\n    return {\n        tile: {\n            z,\n            x: tile.x,\n            y: tile.y,\n        },\n        x: Math.max(0, Math.min(tileSize - 1, x)),\n        y: Math.max(0, Math.min(tileSize - 1, y)),\n    };\n}\n","import { toPixel, normalizePixel } from './ElevationFunctions';\nimport type { TileManager } from '../tile';\nimport type { Pixel, Coordinates } from '../types';\n\nexport class ElevationCalculator {\n    private readonly tileManager: TileManager;\n    private readonly tileSize: number;\n\n    constructor(tileManager: TileManager, tileSize: number = 256) {\n        this.tileManager = tileManager;\n        this.tileSize = tileSize;\n    }\n\n    // ========================================================================\n    // PUBLIC API - ELEVATION CALCULATIONS\n    // ========================================================================\n\n    public async getElevation(\n        coords: Coordinates,\n        zoomLevel: number,\n        interpolation: boolean = true\n    ): Promise<number> {\n        try {\n            if (interpolation) {\n                return await this.getInterpolatedElevationInternal(coords, zoomLevel);\n            } else {\n                const pixel = toPixel(coords, zoomLevel, this.tileSize);\n                return await this.getElevationFromPixel(pixel);\n            }\n        } catch (error) {\n            if (error instanceof Error) {\n                throw new Error(`Failed to get elevation: ${error.message}`, { cause: error });\n            }\n            throw new Error('Failed to get elevation: Unknown error', { cause: error });\n        }\n    }\n\n    // ========================================================================\n    // PRIVATE - HELPER METHODS\n    // ========================================================================\n\n    private async getInterpolatedElevationInternal(\n        coords: Coordinates,\n        zoomLevel: number\n    ): Promise<number> {\n        const pixel = toPixel(coords, zoomLevel, this.tileSize);\n        const pixelFloat = {\n            tile: pixel.tile,\n            x: pixel.x,\n            y: pixel.y,\n        };\n        const x0 = Math.floor(pixelFloat.x);\n        const y0 = Math.floor(pixelFloat.y);\n        const x1 = x0 + 1;\n        const y1 = y0 + 1;\n\n        const dx = pixelFloat.x - x0;\n        const dy = pixelFloat.y - y0;\n\n        const p00 = await this.getElevationFromPixel(\n            normalizePixel({ tile: pixelFloat.tile, x: x0, y: y0 }, this.tileSize)\n        );\n        const p10 = await this.getElevationFromPixel(\n            normalizePixel({ tile: pixelFloat.tile, x: x1, y: y0 }, this.tileSize)\n        );\n        const p01 = await this.getElevationFromPixel(\n            normalizePixel({ tile: pixelFloat.tile, x: x0, y: y1 }, this.tileSize)\n        );\n        const p11 = await this.getElevationFromPixel(\n            normalizePixel({ tile: pixelFloat.tile, x: x1, y: y1 }, this.tileSize)\n        );\n\n        const top = p00 * (1 - dx) + p10 * dx;\n        const bottom = p01 * (1 - dx) + p11 * dx;\n        return top * (1 - dy) + bottom * dy;\n    }\n\n    /**\n     * Get elevation for a specific pixel (internal helper)\n     */\n    private async getElevationFromPixel(pixel: Pixel): Promise<number> {\n        let cachedTile = this.tileManager.getTileDirect(pixel.tile);\n        if (!cachedTile) {\n            cachedTile = await this.tileManager.getTile(pixel.tile);\n        }\n        return cachedTile.getElevation(pixel);\n    }\n}\n","/**\n * Geographic coordinates in WGS84\n */\nexport interface Coordinates {\n    readonly latitude: number;\n    readonly longitude: number;\n    elevation?: number;\n}\n\nexport interface CoordinatesElevation extends Coordinates {\n    elevation: number;\n}\n\nexport function asCoordinatesElevation(coordinates: Coordinates): CoordinatesElevation {\n    return { ...coordinates, elevation: coordinates.elevation ?? 0 };\n}\n\n/**\n * Tile coordinates in Web Mercator projection\n */\nexport interface TileCoordinates {\n    readonly x: number;\n    readonly y: number;\n    readonly z: number;\n}\n\nexport interface TileCoordinatesFloat {\n    readonly x: number;\n    readonly y: number;\n    readonly xFloat: number;\n    readonly yFloat: number;\n    readonly z: number;\n}\n\nexport interface Pixel {\n    readonly tile: TileCoordinates;\n    readonly x: number;\n    readonly y: number;\n}\n\n/**\n * RGB color values from terrain tile\n */\nexport interface RGBColor {\n    readonly red: number;\n    readonly green: number;\n    readonly blue: number;\n}\n\n/**\n * Configuration options for ElevationProvider\n */\nexport interface ElevationProviderConfig {\n    /**\n     * Tile zoom level (0-15, default: 12)\n     * Higher zoom = better resolution but more tiles\n     */\n    readonly zoomLevel?: number;\n\n    /**\n     * Maximum number of tiles to keep in memory cache\n     * Default: 100\n     */\n    readonly cacheSize?: number;\n\n    /**\n     * Custom tile URL template\n     * Default: AWS S3 Terrarium tiles\n     */\n    readonly tileUrlTemplate?: string;\n\n    /**\n     * Tile size in pixels (default: 256)\n     * Use 512 for providers like mapterhorn.com\n     */\n    readonly tileSize?: number;\n\n    /**\n     * Attribution for the tile data source\n     */\n    readonly attribution?: Attribution;\n}\n\n/**\n * Attribution information for elevation data\n */\nexport interface Attribution {\n    readonly text: string;\n    readonly url?: string;\n}\n\n/**\n * Options for Douglas-Peucker filtering of elevation profiles\n */\nexport interface FilterOptions {\n    /**\n     * Maximum allowed perpendicular distance from simplified line in meters\n     * Default: 10 meters\n     */\n    readonly tolerance?: number;\n\n    /**\n     * Elevation exaggeration factor for ECEF coordinate conversion\n     * Higher values emphasize elevation differences more\n     * Default: 3\n     */\n    readonly zExaggeration?: number;\n\n    /**\n     * Whether filtering is enabled\n     * Default: false\n     */\n    readonly enabled?: boolean;\n}\n\n/**\n * Options for distance-based elevation smoothing\n */\nexport interface SmoothingOptions {\n    /**\n     * Smoothing window size in meters\n     * Points within this distance will be weighted and averaged\n     * Default: 50 meters\n     */\n    readonly windowSize?: number;\n\n    /**\n     * Whether smoothing is enabled\n     * Default: false\n     */\n    readonly enabled?: boolean;\n}\n\n/**\n * 3D vector in ECEF (Earth-Centered, Earth-Fixed) coordinates\n */\nexport interface Vector3D {\n    readonly x: number;\n    readonly y: number;\n    readonly z: number;\n}\n\n/**\n * Options for getElevation method\n */\nexport interface GetElevationOptions {\n    /**\n     * Use bilinear interpolation for smoother results\n     * Default: true\n     */\n    readonly interpolation?: boolean;\n}\n\n/**\n * Options for setElevations method\n */\nexport interface SetElevationsOptions {\n    /**\n     * Use bilinear interpolation for smoother results\n     * Default: true\n     */\n    readonly interpolation?: boolean;\n}\n\n/**\n * Options for getElevationsAlong method\n */\nexport interface GetElevationsAlongOptions {\n    /**\n     * Distance between elevation points in meters\n     * Default: 10\n     */\n    readonly step?: number;\n\n    readonly minDistance?: number;\n\n    /**\n     * Use bilinear interpolation for smoother results\n     * Default: true\n     */\n    readonly interpolation?: boolean;\n\n    /**\n     * Optional distance-based smoothing options\n     */\n    readonly smoothingOptions?: SmoothingOptions;\n\n    /**\n     * Optional Douglas-Peucker filtering options\n     */\n    readonly filterOptions?: FilterOptions;\n}\n\n/**\n * Named (threshold, smoothing) pairs for {@link ElevationGainOptions}.\n *\n * - `raw`: no dead band, no smoothing, the plain sum of positive deltas (0 m / 0 m)\n * - `barometric`: Strava's threshold for a barometric altimeter (2 m / 15 m)\n * - `dem`: for DEM-derived elevation such as this library's, and the default (3 m / 30 m)\n * - `gps`: Strava's threshold for a GPS-only trace (10 m / 50 m)\n */\nexport type ElevationGainPreset = 'raw' | 'barometric' | 'dem' | 'gps';\n\n/**\n * How to measure cumulative ascent and descent.\n *\n * Cumulative ascent is a property of a route *and* a measurement scale, so a preset pairs a dead\n * band with a smoothing window. Setting `thresholdM` or `smoothWindowM` overrides that half of the\n * preset.\n */\nexport interface ElevationGainOptions {\n    /**\n     * Named scale to measure at\n     * Default: 'dem'\n     */\n    readonly preset?: ElevationGainPreset;\n\n    /**\n     * Hysteresis dead band in meters: a climb or descent counts only once the profile has\n     * reversed by at least this much. 0 disables it.\n     * Default: the preset's threshold\n     */\n    readonly thresholdM?: number;\n\n    /**\n     * Triangular-kernel half-width in meters, applied to a private copy of the profile.\n     * 0 disables it.\n     * Default: the preset's window\n     */\n    readonly smoothWindowM?: number;\n}\n\n/**\n * What {@link ElevationGainOptions} measured.\n */\nexport interface ElevationGainResult {\n    /** Cumulative ascent in meters, >= 0 */\n    readonly gainM: number;\n\n    /** Cumulative descent in meters, >= 0 */\n    readonly lossM: number;\n\n    /**\n     * Unfiltered sum of positive deltas on the same smoothed profile. Compared with `gainM`, it\n     * isolates what the dead band did from what the smoothing did.\n     */\n    readonly rawGainM: number;\n\n    /** Unfiltered sum of negative deltas on the same smoothed profile, as a positive number */\n    readonly rawLossM: number;\n\n    /** Dead band actually applied, in meters */\n    readonly thresholdM: number;\n\n    /** Smoothing half-width actually applied, in meters */\n    readonly smoothWindowM: number;\n\n    /**\n     * Number of legs banked, climbs plus descents. A diagnostic: a route with 3 real climbs that\n     * reports 400 legs has a threshold too small for its noise.\n     */\n    readonly legCount: number;\n}\n","/**\n * Lightweight logger for development debugging\n * All logging is completely removed in production builds via __DEV__ constant\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\ntype LogFunction = (message?: any, ...optionalParams: any[]) => void;\n\ntype LogLevelConfig = 'error' | 'warn' | 'info' | 'debug' | 'trace';\ntype LevelsConfig = {\n    default: LogLevelConfig;\n    [namespacePrefix: string]: LogLevelConfig;\n};\nimport jsonLevels from './levels.json' with { type: 'json' };\nconst levels = jsonLevels as LevelsConfig;\n\nexport type LogLevelValue = 0 | 1 | 2 | 3 | 4;\nexport class LogLevel {\n    static readonly ERROR: LogLevelValue = 0;\n    static readonly WARN: LogLevelValue = 1;\n    static readonly INFO: LogLevelValue = 2;\n    static readonly DEBUG: LogLevelValue = 3;\n    static readonly TRACE: LogLevelValue = 4;\n}\n\nconst levelWeights: Record<LogLevelConfig, LogLevelValue> = {\n    error: LogLevel.ERROR,\n    warn: LogLevel.WARN,\n    info: LogLevel.INFO,\n    debug: LogLevel.DEBUG,\n    trace: LogLevel.TRACE,\n};\n\nconst levelLabel: Record<LogLevelValue, string> = {\n    0: 'ERROR',\n    1: 'WARN',\n    2: 'INFO',\n    3: 'DEBUG',\n    4: 'TRACE',\n};\n\nconst levelLog: Record<LogLevelValue, LogFunction> = {\n    0: console.error,\n    1: console.error,\n    2: console.log,\n    3: console.log,\n    4: console.log,\n};\n\n/**\n * Logger class that provides conditional logging based on build environment\n * In production builds, all logging code is eliminated by the bundler\n *\n * Matches the full console API signatures for compatibility with printf-style formatting\n * and all console features\n */\nclass Logger {\n    private namespace: string;\n    private level: LogLevelValue;\n\n    constructor(namespace: string) {\n        this.namespace = namespace;\n        if (__DEV__) {\n            let matchedLevel: LogLevelConfig = levels.default;\n            let longestMatch = 0;\n\n            for (const [prefix, level] of Object.entries(levels)) {\n                if (\n                    prefix !== 'default' &&\n                    namespace.startsWith(prefix) &&\n                    prefix.length > longestMatch\n                ) {\n                    matchedLevel = level;\n                    longestMatch = prefix.length;\n                }\n            }\n\n            this.level = levelWeights[matchedLevel];\n        } else {\n            this.level = levelWeights.warn;\n        }\n    }\n\n    private shouldLog(level: LogLevelValue): boolean {\n        return level <= this.level;\n    }\n\n    private doLog(level: LogLevelValue, message?: any, ...optionalParams: any[]) {\n        const prefix = `[${this.namespace}:${levelLabel[level]}]`;\n        if (typeof message === 'string') {\n            // Preserve printf-style formatting by combining prefix with format string\n            levelLog[level](`${prefix} ${message}`, ...optionalParams);\n        } else {\n            // No formatting needed, use regular logging\n            levelLog[level](prefix, message, ...optionalParams);\n        }\n    }\n\n    private log(level: LogLevelValue, message?: any, ...optionalParams: any[]) {\n        if (this.shouldLog(level)) {\n            this.doLog(level, message, ...optionalParams);\n        }\n    }\n\n    /**\n     * Log debug information (verbose output for development)\n     * Supports printf-style formatting: logger.debug('Value: %s, Count: %d', value, count)\n     */\n    trace(message?: any, ...optionalParams: any[]): void {\n        if (__DEV__) {\n            this.log(LogLevel.TRACE, message, ...optionalParams);\n        }\n    }\n\n    /**\n     * Log debug information (verbose output for development)\n     * Supports printf-style formatting: logger.debug('Value: %s, Count: %d', value, count)\n     */\n    debug(message?: any, ...optionalParams: any[]): void {\n        if (__DEV__) {\n            this.log(LogLevel.DEBUG, message, ...optionalParams);\n        }\n    }\n\n    /**\n     * Log general information\n     * Supports printf-style formatting: logger.info('User %s logged in', username)\n     */\n    info(message?: any, ...optionalParams: any[]): void {\n        if (__DEV__) {\n            this.log(LogLevel.INFO, message, ...optionalParams);\n        }\n    }\n\n    /**\n     * Log warnings\n     * Supports printf-style formatting: logger.warn('Timeout after %dms', timeout)\n     */\n    warn(message?: any, ...optionalParams: any[]): void {\n        this.log(LogLevel.WARN, message, ...optionalParams);\n    }\n\n    /**\n     * Log errors\n     * Supports printf-style formatting: logger.error('Failed to load %s: %o', file, error)\n     */\n    error(message?: any, ...optionalParams: any[]): void {\n        this.log(LogLevel.ERROR, message, ...optionalParams);\n    }\n\n    private getTimeLabel(level: LogLevelValue, label: string): string {\n        return `[${this.namespace}:${levelLabel[level]}] ${label}`;\n    }\n\n    private doTime(level: LogLevelValue, label: string): void {\n        console.time(this.getTimeLabel(level, label));\n    }\n\n    private doTimeEnd(level: LogLevelValue, label: string): void {\n        console.timeEnd(this.getTimeLabel(level, label));\n    }\n\n    /**\n     * Log with timing information\n     * Useful for performance debugging\n     */\n    timeLevel(level: LogLevelValue, label: string): void {\n        if (__DEV__) {\n            if (this.shouldLog(level)) {\n                this.doTime(level, label);\n            }\n        }\n    }\n\n    /**\n     * End timing and log duration\n     */\n    timeEndLevel(level: LogLevelValue, label: string): void {\n        if (__DEV__) {\n            if (this.shouldLog(level)) {\n                this.doTimeEnd(level, label);\n            }\n        }\n    }\n\n    /**\n     * Log with timing information\n     * Useful for performance debugging\n     */\n    time(label: string): void {\n        this.doTime(LogLevel.INFO, label);\n    }\n\n    /**\n     * End timing and log duration\n     */\n    timeEnd(label: string): void {\n        this.doTimeEnd(LogLevel.INFO, label);\n    }\n\n    private logDir(level: LogLevelValue, message?: any, obj?: any, options?: any) {\n        this.doLog(level, 'DIR %s', message);\n        console.dir(obj, options);\n    }\n\n    /**\n     * Display an interactive list of object properties\n     * Useful for exploring complex objects in development\n     * @param obj - The object to inspect\n     * @param options - Optional display options\n     */\n    dirLevel(level: LogLevelValue, message?: any, obj?: any, options?: any): void {\n        if (__DEV__) {\n            if (this.shouldLog(level)) {\n                this.logDir(level, message, obj, options);\n            }\n        }\n    }\n\n    /**\n     * Display an interactive list of object properties\n     * Useful for exploring complex objects in development\n     * @param obj - The object to inspect\n     * @param options - Optional display options\n     */\n    dir(message?: any, obj?: any, options?: any): void {\n        this.logDir(LogLevel.INFO, message, obj, options);\n    }\n\n    /**\n     * Clear the console\n     */\n    clear(): void {\n        console.clear();\n    }\n}\n\n/**\n * Create a logger instance with a specific namespace\n * @param namespace - The namespace for this logger (e.g., 'Cache', 'TileFetcher')\n * @returns A new Logger instance with the specified namespace\n * @example\n * const logger = createLogger('MyModule');\n * logger.debug('Module initialized');\n */\nexport const createLogger = (namespace: string): Logger => new Logger(namespace);\n\nexport { Logger };\n","export { createLogger, Logger, LogLevel } from './Logger';\n","/**\n * Earth and mathematical constants used throughout the elevation library\n */\nexport const EARTH_CONSTANTS = {\n    /** Semi-major axis in meters (WGS84 ellipsoid) */\n    SEMI_MAJOR_AXIS: 6378137.0,\n    /** Mean radius in meters (used for distance calculations) */\n    MEAN_RADIUS: 6371000,\n    /** First eccentricity squared (WGS84 ellipsoid) */\n    FIRST_ECCENTRICITY_SQUARED: 0.00669437999014,\n} as const;\n\n/**\n * Mathematical constants\n */\nexport const MATH_CONSTANTS = {\n    /** Degrees to radians conversion factor */\n    DEG_TO_RAD: Math.PI / 180,\n    /** Radians to degrees conversion factor */\n    RAD_TO_DEG: 180 / Math.PI,\n} as const;\n\n/**\n * Algorithm constants\n */\nexport const ALGORITHM_CONSTANTS = {\n    /** Minimum points needed for smoothing operations */\n    MIN_SMOOTHING_POINTS: 3,\n} as const;\n","import { Coordinates } from '../types';\nimport { EARTH_CONSTANTS, MATH_CONSTANTS } from './Constants';\nimport { Vector3D } from './Vector3D';\n\n/**\n * Distance calculation utilities for geographic and 3D coordinates\n */\nexport class Distance {\n    /**\n     * Calculate great circle distance between two geographic coordinates using Haversine formula\n     * @param coord1 - First coordinate\n     * @param coord2 - Second coordinate\n     * @returns Distance in meters\n     */\n    public static haversine(coord1: Coordinates, coord2: Coordinates): number {\n        const lat1Rad = coord1.latitude * MATH_CONSTANTS.DEG_TO_RAD;\n        const lat2Rad = coord2.latitude * MATH_CONSTANTS.DEG_TO_RAD;\n        const deltaLat = (coord2.latitude - coord1.latitude) * MATH_CONSTANTS.DEG_TO_RAD;\n        const deltaLon = (coord2.longitude - coord1.longitude) * MATH_CONSTANTS.DEG_TO_RAD;\n\n        const a =\n            Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +\n            Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);\n\n        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n        return EARTH_CONSTANTS.MEAN_RADIUS * c;\n    }\n\n    /**\n     * Calculate Euclidean distance between two 3D points\n     * @param point1 - First 3D point\n     * @param point2 - Second 3D point\n     * @returns Distance in meters\n     */\n    public static euclidean3D(point1: Vector3D, point2: Vector3D): number {\n        const dx = point1.x - point2.x;\n        const dy = point1.y - point2.y;\n        const dz = point1.z - point2.z;\n        return Math.sqrt(dx * dx + dy * dy + dz * dz);\n    }\n\n    /**\n     * Calculate perpendicular distance from a point to a line segment in 3D space\n     * @param point - Point to measure from\n     * @param segmentStart - Start point of line segment\n     * @param segmentEnd - End point of line segment\n     * @returns Perpendicular distance in meters\n     */\n    public static pointToSegment3D(\n        point: Vector3D,\n        segmentStart: Vector3D,\n        segmentEnd: Vector3D\n    ): number {\n        const segmentVector = segmentEnd.subtract(segmentStart);\n        const pointVector = point.subtract(segmentStart);\n\n        // Check if segment has zero length\n        const segmentLengthSquared = segmentVector.dot(segmentVector);\n        if (segmentLengthSquared === 0) {\n            return Distance.euclidean3D(point, segmentStart);\n        }\n\n        // Calculate parameter t for closest point on segment\n        const t = Math.max(0, Math.min(1, pointVector.dot(segmentVector) / segmentLengthSquared));\n\n        // Calculate closest point on segment\n        const closestPoint = segmentStart.add(segmentVector.multiply(t));\n\n        // Return distance to closest point\n        return Distance.euclidean3D(point, closestPoint);\n    }\n\n    /**\n     * Calculate cumulative distances along a path of coordinates\n     * @param points - Array of coordinates\n     * @returns Array of cumulative distances in meters\n     */\n    public static cumulativeDistances(points: Coordinates[]): number[] {\n        const distances: number[] = [0];\n\n        for (let i = 1; i < points.length; i++) {\n            const segmentDistance = Distance.haversine(points[i - 1], points[i]);\n            distances.push(distances[i - 1] + segmentDistance);\n        }\n\n        return distances;\n    }\n\n    /**\n     * Calculate total distance along a path of coordinates\n     * @param points - Array of coordinates\n     * @returns Total distance in meters\n     */\n    public static totalPathDistance(points: Coordinates[]): number {\n        if (points.length < 2) {\n            return 0;\n        }\n\n        let totalDistance = 0;\n        for (let i = 1; i < points.length; i++) {\n            totalDistance += Distance.haversine(points[i - 1], points[i]);\n        }\n\n        return totalDistance;\n    }\n}\n","/**\n * 3D Vector class for ECEF coordinate operations\n */\nexport class Vector3D {\n    constructor(\n        public readonly x: number,\n        public readonly y: number,\n        public readonly z: number\n    ) {}\n\n    /**\n     * Calculate Euclidean distance between two vectors\n     */\n    public distanceTo(other: Vector3D): number {\n        const dx = this.x - other.x;\n        const dy = this.y - other.y;\n        const dz = this.z - other.z;\n        return Math.hypot(dx, dy, dz);\n    }\n\n    /**\n     * Subtract two vectors\n     */\n    public subtract(other: Vector3D): Vector3D {\n        return new Vector3D(this.x - other.x, this.y - other.y, this.z - other.z);\n    }\n\n    /**\n     * Add two vectors\n     */\n    public add(other: Vector3D): Vector3D {\n        return new Vector3D(this.x + other.x, this.y + other.y, this.z + other.z);\n    }\n\n    /**\n     * Multiply vector by scalar\n     */\n    public multiply(scalar: number): Vector3D {\n        return new Vector3D(this.x * scalar, this.y * scalar, this.z * scalar);\n    }\n\n    /**\n     * Calculate dot product with another vector\n     */\n    public dot(other: Vector3D): number {\n        return this.x * other.x + this.y * other.y + this.z * other.z;\n    }\n\n    /**\n     * Calculate cross product with another vector\n     */\n    public cross(other: Vector3D): Vector3D {\n        return new Vector3D(\n            this.y * other.z - this.z * other.y,\n            this.z * other.x - this.x * other.z,\n            this.x * other.y - this.y * other.x\n        );\n    }\n\n    /**\n     * Calculate the magnitude (length) of the vector\n     */\n    public magnitude(): number {\n        return Math.hypot(this.x, this.y, this.z);\n    }\n\n    /**\n     * Normalize the vector to unit length\n     */\n    public normalize(): Vector3D {\n        const mag = this.magnitude();\n        if (mag === 0) {\n            return new Vector3D(0, 0, 0);\n        }\n        return this.multiply(1 / mag);\n    }\n\n    /**\n     * Calculate the shortest distance from this point to a line segment\n     */\n    public distanceToSegment(segmentStart: Vector3D, segmentEnd: Vector3D): number {\n        const segmentVector = segmentEnd.subtract(segmentStart);\n        const segmentLength = segmentVector.magnitude();\n\n        // Handle degenerate case where segment has zero length\n        if (segmentLength === 0) {\n            return this.distanceTo(segmentStart);\n        }\n\n        // Vector from segment start to this point\n        const pointVector = this.subtract(segmentStart);\n\n        // Project point vector onto segment vector\n        const projection = pointVector.dot(segmentVector) / (segmentLength * segmentLength);\n\n        // Clamp projection to segment bounds [0, 1]\n        const clampedProjection = Math.max(0, Math.min(1, projection));\n\n        // Find closest point on segment\n        const closestPoint = segmentStart.add(segmentVector.multiply(clampedProjection));\n\n        // Return distance to closest point\n        return this.distanceTo(closestPoint);\n    }\n}\n","import { Coordinates } from '../types';\nimport { EARTH_CONSTANTS } from './Constants';\nimport { Vector3D } from './Vector3D';\n\n/**\n * ECEF (Earth-Centered, Earth-Fixed) coordinate converter\n * Converts WGS84 coordinates (lat/lon/elevation) to ECEF Cartesian coordinates\n */\nexport class EcefConverter {\n    /**\n     * Convert WGS84 coordinates to ECEF coordinates with optional elevation exaggeration\n     * @param coordinates - Geographic coordinates with elevation\n     * @param zExaggeration - Elevation exaggeration factor (default: 3)\n     * @returns ECEF coordinates as Vector3D\n     */\n    public static toEcef(coordinates: Coordinates, zExaggeration: number = 3): Vector3D {\n        // Convert degrees to radians\n        const latRad = (coordinates.latitude * Math.PI) / 180;\n        const lonRad = (coordinates.longitude * Math.PI) / 180;\n\n        // Apply elevation exaggeration\n        const elevationExaggerated = zExaggeration * (coordinates.elevation || 0);\n\n        // Calculate prime vertical radius of curvature\n        const sinLat = Math.sin(latRad);\n        const n =\n            EARTH_CONSTANTS.SEMI_MAJOR_AXIS /\n            Math.sqrt(1 - EARTH_CONSTANTS.FIRST_ECCENTRICITY_SQUARED * sinLat * sinLat);\n\n        // Calculate ECEF coordinates\n        const cosLat = Math.cos(latRad);\n        const cosLon = Math.cos(lonRad);\n        const sinLon = Math.sin(lonRad);\n\n        const x = (n + elevationExaggerated) * cosLat * cosLon;\n        const y = (n + elevationExaggerated) * cosLat * sinLon;\n        const z =\n            (n * (1 - EARTH_CONSTANTS.FIRST_ECCENTRICITY_SQUARED) + elevationExaggerated) * sinLat;\n\n        return new Vector3D(x, y, z);\n    }\n\n    /**\n     * Convert multiple coordinates to ECEF vectors\n     * @param coordinates - Array of geographic coordinates with elevation\n     * @param zExaggeration - Elevation exaggeration factor (default: 3)\n     * @returns Array of ECEF coordinates as Vector3D\n     */\n    public static convertBatch(coordinates: Coordinates[], zExaggeration: number = 3): Vector3D[] {\n        return coordinates.map(coord => this.toEcef(coord, zExaggeration));\n    }\n}\n","import { CoordinatesElevation } from '../types';\nimport { EcefConverter } from './EcefConverter';\nimport { createLogger, Logger, LogLevel } from './Logger';\n\nconst logger: Logger = createLogger('utils/DouglasPeucker');\n\n/**\n * 3D Douglas-Peucker algorithm implementation for elevation profile simplification\n * Uses ECEF coordinates for true 3D distance calculations\n */\nexport class DouglasPeucker {\n    /**\n     * Simplify a path using the Douglas-Peucker algorithm in 3D space\n     * @param points - Array of coordinates with elevation\n     * @param tolerance - Maximum allowed distance from simplified line in meters\n     * @param zExaggeration - Elevation exaggeration factor for ECEF conversion (default: 3)\n     * @returns Simplified array of coordinates\n     */\n    public static simplify(\n        points: CoordinatesElevation[],\n        tolerance: number,\n        zExaggeration: number = 3\n    ): CoordinatesElevation[] {\n        logger.info('simplify %s', points.length);\n        if (points.length <= 2) {\n            logger.warn('too small');\n            return [...points]; // Return copy to avoid mutation\n        }\n        logger.timeLevel(LogLevel.INFO, 'simplify');\n\n        const lastIndex = points.length - 1;\n        const simplified: CoordinatesElevation[] = [];\n\n        // Always include first point\n        simplified.push(points[0]);\n\n        // Recursively simplify the path\n        const intermediatePoints = this.simplifyRecursive(\n            points,\n            0,\n            lastIndex,\n            tolerance,\n            zExaggeration\n        );\n        simplified.push(...intermediatePoints);\n\n        // Always include last point\n        simplified.push(points[lastIndex]);\n\n        logger.timeEndLevel(LogLevel.INFO, 'simplify');\n        logger.debug('simplified -> %s', simplified.length);\n        return simplified;\n    }\n\n    /**\n     * Recursive step of the Douglas-Peucker algorithm\n     * @param points - Array of all points\n     * @param firstIndex - Index of first point in current segment\n     * @param lastIndex - Index of last point in current segment\n     * @param tolerance - Maximum allowed distance in meters\n     * @param zExaggeration - Elevation exaggeration factor\n     * @returns Array of points to include in simplified path\n     */\n    private static simplifyRecursive(\n        points: CoordinatesElevation[],\n        firstIndex: number,\n        lastIndex: number,\n        tolerance: number,\n        zExaggeration: number\n    ): CoordinatesElevation[] {\n        let maxDistance = 0;\n        let maxIndex = -1;\n        const result: CoordinatesElevation[] = [];\n\n        // Convert segment endpoints to ECEF\n        const firstEcef = EcefConverter.toEcef(points[firstIndex], zExaggeration);\n        const lastEcef = EcefConverter.toEcef(points[lastIndex], zExaggeration);\n\n        // Find the point with maximum perpendicular distance to the line segment\n        for (let i = firstIndex + 1; i < lastIndex; i++) {\n            const pointEcef = EcefConverter.toEcef(points[i], zExaggeration);\n            const distance = pointEcef.distanceToSegment(firstEcef, lastEcef);\n\n            if (distance > maxDistance) {\n                maxDistance = distance;\n                maxIndex = i;\n            }\n        }\n\n        // If the maximum distance is greater than tolerance, split the segment\n        if (maxDistance > tolerance && maxIndex !== -1) {\n            // Recursively simplify the first sub-segment\n            if (maxIndex - firstIndex > 1) {\n                const leftSegment = this.simplifyRecursive(\n                    points,\n                    firstIndex,\n                    maxIndex,\n                    tolerance,\n                    zExaggeration\n                );\n                result.push(...leftSegment);\n            }\n\n            // Include the point with maximum distance\n            result.push(points[maxIndex]);\n\n            // Recursively simplify the second sub-segment\n            if (lastIndex - maxIndex > 1) {\n                const rightSegment = this.simplifyRecursive(\n                    points,\n                    maxIndex,\n                    lastIndex,\n                    tolerance,\n                    zExaggeration\n                );\n                result.push(...rightSegment);\n            }\n        }\n\n        return result;\n    }\n}\n","import { CoordinatesElevation } from '../types';\nimport { ALGORITHM_CONSTANTS } from './Constants';\nimport { Distance } from './Distance';\nimport { createLogger, Logger, LogLevel } from './Logger';\n\nconst logger: Logger = createLogger('utils/ElevationSmoother');\n\n/**\n * Distance-based elevation smoothing using weighted triangular kernel\n * Based on the algorithm from Java SmoothService\n */\nexport class ElevationSmoother {\n    /**\n     * Apply distance-based smoothing to elevation data\n     * @param points - Array of coordinates with elevation\n     * @param windowSize - Smoothing window in meters (default: 50)\n     * @returns Smoothed elevation data\n     */\n    public static smooth(\n        points: CoordinatesElevation[],\n        windowSize: number = 50\n    ): CoordinatesElevation[] {\n        logger.debug('smooth %s', points.length);\n        // Validate inputs\n        if (points.length < ALGORITHM_CONSTANTS.MIN_SMOOTHING_POINTS) {\n            logger.debug('too small');\n            return points; // Not enough points to smooth\n        }\n\n        if (windowSize <= 0) {\n            throw new Error(`Invalid window size: ${windowSize}. Must be positive`);\n        }\n        logger.timeLevel(LogLevel.INFO, 'smooth');\n\n        const distances = Distance.cumulativeDistances(points);\n        const smoothed = ElevationSmoother.smoothProfile(\n            distances,\n            points.map(p => p.elevation),\n            windowSize\n        );\n        const smoothedPoints = points.map((point, i) => ({ ...point, elevation: smoothed[i] }));\n        logger.timeEndLevel(LogLevel.INFO, 'smooth');\n\n        return smoothedPoints;\n    }\n\n    /**\n     * The kernel of {@link smooth}, on flat arrays.\n     *\n     * `distances` must be non-decreasing (cumulative meters along the path) and the same length as\n     * `elevations`. The window is a **half-width** applied on each side, so a `windowSize` of 150\n     * spans 300 m of path, the extreme members carrying a weight of ~0.\n     *\n     * Exists as its own entry point so callers that already hold a profile as arrays do not have\n     * to allocate one {@link CoordinatesElevation} per point.\n     *\n     * Unlike {@link smooth}, which throws, a `windowSize` that is not strictly positive means\n     * \"do not smooth\" and returns a copy of `elevations`. So do fewer than\n     * `ALGORITHM_CONSTANTS.MIN_SMOOTHING_POINTS` points.\n     *\n     * @param distances - Cumulative distance of each point in meters\n     * @param elevations - Elevation of each point in meters\n     * @param windowSize - Kernel half-width in meters\n     * @returns A new array of smoothed elevations\n     */\n    public static smoothProfile(\n        distances: ArrayLike<number>,\n        elevations: ArrayLike<number>,\n        windowSize: number\n    ): number[] {\n        if (distances.length !== elevations.length) {\n            throw new Error(\n                `distances (${distances.length}) and elevations (${elevations.length}) must have the same length`\n            );\n        }\n        const n = elevations.length;\n        if (n < ALGORITHM_CONSTANTS.MIN_SMOOTHING_POINTS || !(windowSize > 0)) {\n            return Array.from(elevations);\n        }\n\n        const out = new Array<number>(n);\n        // The window bounds are monotone in `i`, so two cursors sweep the profile once between\n        // them instead of being re-searched from `i` for every point.\n        let startIndex = 0;\n        let endIndex = 0;\n        for (let i = 0; i < n; i++) {\n            const current = distances[i];\n            while (current - distances[startIndex] > windowSize) {\n                startIndex++;\n            }\n            if (endIndex < i) {\n                endIndex = i;\n            }\n            while (endIndex < n - 1 && distances[endIndex + 1] - current <= windowSize) {\n                endIndex++;\n            }\n\n            // Triangular kernel: weight = 1 - (distance / windowSize). The current point always\n            // has weight 1, so totalWeight > 0.\n            let totalWeight = 0;\n            let weightedSum = 0;\n            for (let j = startIndex; j <= endIndex; j++) {\n                const weight = 1 - Math.abs(distances[j] - current) / windowSize;\n                totalWeight += weight;\n                weightedSum += elevations[j] * weight;\n            }\n            out[i] = weightedSum / totalWeight;\n        }\n        return out;\n    }\n}\n","// reactive.ts\nexport type AsyncConsumer<T> = (item: T) => Promise<void>;\n\ntype Inflight = Promise<{ ok: true } | { ok: false; error: unknown }>;\n\nexport class Flux {\n    private static async wrap(p: Promise<void>): Inflight {\n        try {\n            await p;\n            return { ok: true as const };\n        } catch (e) {\n            return { ok: false as const, error: e };\n        }\n    }\n\n    private static async getFirst(inflight: Inflight[]): Promise<void> {\n        const { idx, res } = await Promise.race(\n            inflight.map((it, idx) => it.then(res => ({ idx, res })))\n        );\n        inflight.splice(idx, 1);\n        if (res.ok) {\n            return;\n        } else {\n            throw res.error;\n        }\n    }\n\n    static async forEach<T>(\n        from: Iterable<T>,\n        fn: AsyncConsumer<T>,\n        maxParallel = 1\n    ): Promise<void> {\n        const inflight: Inflight[] = [];\n\n        for (const item of from) {\n            inflight.push(Flux.wrap(fn(item)));\n\n            if (inflight.length >= maxParallel) {\n                await Flux.getFirst(inflight);\n            }\n        }\n\n        while (inflight.length > 0) {\n            await Flux.getFirst(inflight);\n        }\n        return;\n    }\n}\n","import {\n    asCoordinatesElevation,\n    Coordinates,\n    CoordinatesElevation,\n    FilterOptions,\n    SmoothingOptions,\n    TileCoordinates,\n} from '../types';\nimport { createLogger, Logger, LogLevel } from '../utils';\nimport { Distance } from '../utils/Distance';\nimport { DouglasPeucker } from '../utils/DouglasPeucker';\nimport { ElevationSmoother } from '../utils/ElevationSmoother';\nimport { ElevationCalculator } from './ElevationCalculator';\nimport { toTileCoordinates } from './ElevationFunctions';\nimport { Flux } from './Reactive';\n\nconst logger: Logger = createLogger('calculator/BatchCalculator');\n\nexport class BatchCalculator {\n    private readonly elevationCalculator: ElevationCalculator;\n\n    constructor(elevationCalculator: ElevationCalculator) {\n        this.elevationCalculator = elevationCalculator;\n    }\n\n    public async setElevations(\n        coordinates: Iterable<Coordinates>,\n        zoomLevel: number,\n        interpolation: boolean\n    ): Promise<void> {\n        logger.debug('setElevations');\n        const pointsPerTile: Record<string, Coordinates[]> = {};\n        const tileCoordinatesMap: Map<string, TileCoordinates> = new Map();\n\n        logger.timeLevel(LogLevel.DEBUG, 'points-per-tile');\n        // Helper function to create a unique key for tile coordinates\n        const tileKey = (tile: TileCoordinates): string => `${tile.z}/${tile.x}/${tile.y}`;\n\n        // Populate pointsPerTile by grouping coordinates by their tile\n        for (const point of coordinates) {\n            const tile = toTileCoordinates(point, zoomLevel);\n            const key = tileKey(tile);\n\n            let array = pointsPerTile[key];\n            if (!array) {\n                array = [];\n                pointsPerTile[key] = array;\n                tileCoordinatesMap.set(key, tile);\n            }\n            array.push(point);\n        }\n        const tiles = Array.from(tileCoordinatesMap.values());\n        logger.timeEndLevel(LogLevel.DEBUG, 'points-per-tile');\n\n        logger.timeLevel(LogLevel.DEBUG, 'get-elevations');\n        await Flux.forEach(\n            tiles,\n            async tile => {\n                const key = tileKey(tile);\n                logger.timeLevel(LogLevel.DEBUG, 'get-elevations-' + key);\n                const points: Coordinates[] = pointsPerTile[key];\n                for (const point of points) {\n                    point.elevation = await this.elevationCalculator.getElevation(\n                        point,\n                        zoomLevel,\n                        interpolation\n                    );\n                }\n                logger.timeEndLevel(LogLevel.DEBUG, 'get-elevations-' + key);\n            },\n            10\n        );\n        logger.timeEndLevel(LogLevel.DEBUG, 'get-elevations');\n    }\n\n    /**\n     * Get elevations along a path defined by multiple coordinates\n     * @param path - Array of coordinates defining the path\n     * @param zoomLevel - Tile zoom level (0-15)\n     * @param step - Distance between elevation points in meters\n     * @param interpolation - Use bilinear interpolation for smoother results\n     * @param smoothingOptions - Optional distance-based smoothing options\n     * @param filterOptions - Optional filtering options using Douglas-Peucker algorithm\n     */\n    public async getElevationsAlong(\n        path: Coordinates[],\n        zoomLevel: number,\n        step: number,\n        minDistance: number,\n        interpolation: boolean,\n        smoothingOptions?: SmoothingOptions,\n        filterOptions?: FilterOptions\n    ): Promise<CoordinatesElevation[]> {\n        const pathTimer = 'path-elevations';\n        logger.timeLevel(LogLevel.INFO, pathTimer);\n\n        logger.info(\n            'Path processing started - waypoints: %d, step: %dm, zoom: %d, interpolation: %s',\n            path.length,\n            step,\n            zoomLevel,\n            interpolation\n        );\n\n        // Validate inputs\n        if (path.length < 2) {\n            logger.error('Path validation failed - insufficient waypoints: %d', path.length);\n            throw new Error('Path must contain at least 2 coordinates');\n        }\n        if (step <= 1) {\n            logger.error('Path validation failed - step too small: %dm', step);\n            throw new Error(`Step is too small: ${step} meters`);\n        }\n\n        // Generate coordinates along the entire path\n        let coordinates = Array.from(this.generateCoordinatesAlong(path, step, minDistance));\n        logger.debug('Generated %d coordinates along path', coordinates.length);\n\n        // Get elevations for all coordinates\n        logger.debug('Fetching elevations for generated coordinates');\n        await this.setElevations(coordinates, zoomLevel, interpolation);\n\n        logger.debug('Combined coordinates with elevations - points: %d', coordinates.length);\n\n        // Apply smoothing if explicitly enabled\n        if (smoothingOptions?.enabled === true && coordinates.length >= 3) {\n            const windowSize = smoothingOptions.windowSize ?? 50;\n            const originalCount = coordinates.length;\n\n            logger.debug('Applying elevation smoothing - windowSize: %dm', windowSize);\n            const smoothTimer = 'smoothing';\n            logger.timeLevel(LogLevel.DEBUG, smoothTimer);\n\n            coordinates = ElevationSmoother.smooth(coordinates, windowSize);\n\n            logger.timeEndLevel(LogLevel.DEBUG, smoothTimer);\n            logger.debug(\n                'Smoothing completed - points: %d → %d',\n                originalCount,\n                coordinates.length\n            );\n        } else if (smoothingOptions?.enabled === true) {\n            logger.debug(\n                'Smoothing skipped - insufficient points: %d (minimum: 3)',\n                coordinates.length\n            );\n        }\n\n        // Apply filtering if explicitly enabled\n        if (filterOptions?.enabled === true && coordinates.length > 2) {\n            const tolerance = filterOptions?.tolerance ?? 10;\n            const zExaggeration = filterOptions?.zExaggeration ?? 3;\n            const originalCount = coordinates.length;\n\n            logger.debug(\n                'Applying Douglas-Peucker filtering - tolerance: %d, zExaggeration: %d',\n                tolerance,\n                zExaggeration\n            );\n            const filterTimer = 'filtering';\n            logger.timeLevel(LogLevel.DEBUG, filterTimer);\n\n            const filtered = DouglasPeucker.simplify(coordinates, tolerance, zExaggeration);\n\n            logger.timeEndLevel(LogLevel.DEBUG, filterTimer);\n            logger.debug(\n                'Filtering completed - points: %d → %d (%f % reduction)',\n                originalCount,\n                filtered.length,\n                (((originalCount - filtered.length) / originalCount) * 100).toFixed(1)\n            );\n\n            logger.timeEndLevel(LogLevel.INFO, pathTimer);\n            logger.info(\n                'Path processing completed - waypoints: %d, final points: %d, smoothed: %s, filtered: %s',\n                path.length,\n                filtered.length,\n                smoothingOptions?.enabled,\n                filterOptions?.enabled\n            );\n\n            return filtered;\n        } else if (filterOptions?.enabled === true) {\n            logger.debug(\n                'Filtering skipped - insufficient points: %d (minimum: 3)',\n                coordinates.length\n            );\n        }\n\n        logger.timeEndLevel(LogLevel.INFO, pathTimer);\n        logger.info(\n            'Path processing completed - waypoints: %d, final points: %d, smoothed: %s, filtered: %s',\n            path.length,\n            coordinates.length,\n            smoothingOptions?.enabled,\n            filterOptions?.enabled\n        );\n\n        return coordinates;\n    }\n\n    /**\n     * Generate coordinates along a path with multiple waypoints\n     * @param path - Array of coordinates defining the path\n     * @param step - Distance between points in meters\n     */\n    private *generateCoordinatesAlong(\n        path: Coordinates[],\n        step: number,\n        minDistance: number\n    ): Generator<CoordinatesElevation, void, unknown> {\n        if (path.length === 0) {\n            logger.debug('Path generation skipped - insufficient waypoints: %d', path.length);\n            return;\n        }\n        logger.debug('Generating coordinates - waypoints: %d, step: %dm', path.length, step);\n\n        const coordGenTimer = 'coordinate-generation';\n        logger.timeLevel(LogLevel.DEBUG, coordGenTimer);\n\n        // Yield the first point\n        yield asCoordinatesElevation(path[0]);\n        let totalGenerated = 1;\n        let skippedSegments = 0;\n\n        for (let i = 0; i < path.length - 1; i++) {\n            const segmentDistance = Distance.haversine(path[i], path[i + 1]);\n\n            // Skip very short segments (< 1 meter)\n            if (segmentDistance < minDistance) {\n                skippedSegments++;\n                logger.debug(\n                    'Segment %d skipped - distance too short: %.2fm (minimum: %.2fm)',\n                    i + 1,\n                    segmentDistance,\n                    minDistance\n                );\n                continue;\n            }\n\n            // Generate intermediate points for this segment\n            // Skip the first point (already yielded from previous segment)\n            let isFirst = true;\n            for (const coord of this.generateCoordinatesBetween(path[i], path[i + 1], step)) {\n                if (isFirst) {\n                    isFirst = false;\n                    continue; // Skip the first point to avoid duplicates\n                }\n                yield coord;\n                totalGenerated++;\n            }\n        }\n        logger.timeEndLevel(LogLevel.DEBUG, coordGenTimer);\n\n        if (skippedSegments > 0) {\n            logger.debug(\n                'Path generation completed - generated: %d points, skipped segments: %d',\n                totalGenerated,\n                skippedSegments\n            );\n        } else {\n            logger.debug('Path generation completed - generated: %d points', totalGenerated);\n        }\n    }\n\n    /**\n     * Generate coordinates between two points at regular intervals\n     * @param coordinate1 - Start coordinate\n     * @param coordinate2 - End coordinate\n     * @param step - Distance between points in meters\n     */\n    private *generateCoordinatesBetween(\n        coordinate1: Coordinates,\n        coordinate2: Coordinates,\n        step: number\n    ): Generator<CoordinatesElevation, void, unknown> {\n        const distance = Distance.haversine(coordinate1, coordinate2);\n\n        // Always yield the start point\n        yield asCoordinatesElevation(coordinate1);\n\n        if (distance <= step) {\n            // If distance is less than step, just yield the end point\n            yield asCoordinatesElevation(coordinate2);\n            return;\n        }\n\n        // Calculate number of intermediate points\n        const numSteps = Math.floor(distance / step);\n\n        // Linear interpolation in lat/lng space (not great circle)\n        const latDiff = coordinate2.latitude - coordinate1.latitude;\n        const lonDiff = coordinate2.longitude - coordinate1.longitude;\n\n        for (let i = 1; i <= numSteps; i++) {\n            const fraction = (i * step) / distance;\n            yield {\n                latitude: coordinate1.latitude + latDiff * fraction,\n                longitude: coordinate1.longitude + lonDiff * fraction,\n                elevation: 0,\n            };\n        }\n\n        // Always yield the end point\n        yield asCoordinatesElevation(coordinate2);\n    }\n}\n","import { createLogger, Logger, LogLevel } from '../../utils';\n\nconst logger: Logger = createLogger('tile/cache/ReentrantLock');\n// ============================================================================\n// REENTRANT LOCK - Concurrency Control with Semaphore\n// ============================================================================\n\n/**\n * Reentrant lock with semaphore for limiting concurrent operations\n * Features:\n * - Request deduplication (multiple requests for same key)\n * - Concurrency limiting (max operations based on cache size)\n * - Race condition protection\n * - Proper cleanup and resource management\n */\nexport class ReentrantLock<T> {\n    private readonly locks = new Map<string, Promise<T>>();\n    private readonly maxConcurrent: number;\n    private loadingCount: number = 0;\n    private readonly waitQueue: Array<() => void> = [];\n\n    // ========================================================================\n    // CONSTRUCTOR\n    // ========================================================================\n\n    constructor(maxConcurrent: number) {\n        this.maxConcurrent = maxConcurrent;\n    }\n\n    // ========================================================================\n    // PUBLIC API\n    // ========================================================================\n\n    /**\n     * Acquire lock for key with deduplication and concurrency limiting\n     * @param key - Unique identifier for the operation\n     * @param fn - Function to execute if not already running\n     * @returns Promise resolving to the operation result\n     */\n    public async acquire(key: string, fn: () => Promise<T>): Promise<T> {\n        logger.debug(\n            '%s: Lock acquire requested (active: %d/%d, queued: %d)',\n            key,\n            this.loadingCount,\n            this.maxConcurrent,\n            this.waitQueue.length\n        );\n\n        // First check if we already have this key being loaded (deduplication)\n        if (this.locks.has(key)) {\n            logger.debug(\n                '%s: Lock deduplication - already loading, returning existing promise',\n                key\n            );\n            return this.locks.get(key)!;\n        }\n\n        // Wait for a loading slot for this new unique operation\n        await this.acquireLoadingSlot(key);\n\n        // Double-check after acquiring slot (race condition protection)\n        if (this.locks.has(key)) {\n            logger.debug(\n                '%s: Lock race condition - already loading after slot acquired, releasing slot',\n                key\n            );\n            this.releaseLoadingSlot(key);\n            return this.locks.get(key)!;\n        }\n\n        // Create the promise with proper cleanup\n        logger.debug('%s: Lock creating new promise', key);\n        const promise = (async () => {\n            try {\n                logger.debug('%s: Promise executing function', key);\n                const result = await fn();\n                logger.debug('%s: Promise resolved successfully', key);\n                return result;\n            } catch (error) {\n                logger.error('%s: Promise rejected - %o', key, error);\n                throw error;\n            } finally {\n                logger.debug('%s: Promise cleanup - removing lock and releasing slot', key);\n                this.locks.delete(key);\n                this.releaseLoadingSlot(key);\n            }\n        })();\n\n        this.locks.set(key, promise);\n        logger.debug('%s: Lock registered promise (total locks: %d)', key, this.locks.size);\n        return promise;\n    }\n\n    // ========================================================================\n    // PRIVATE - SEMAPHORE OPERATIONS\n    // ========================================================================\n\n    /**\n     * Acquire a loading slot (semaphore acquire)\n     */\n    private async acquireLoadingSlot(key: string): Promise<void> {\n        if (this.loadingCount < this.maxConcurrent) {\n            this.loadingCount++;\n            logger.debug(\n                '%s: Semaphore acquired slot immediately (%d/%d active, %d queued)',\n                key,\n                this.loadingCount,\n                this.maxConcurrent,\n                this.waitQueue.length\n            );\n            return;\n        }\n\n        logger.debug(\n            '%s: Semaphore waiting for slot (%d/%d active, %d queued)',\n            key,\n            this.loadingCount,\n            this.maxConcurrent,\n            this.waitQueue.length\n        );\n\n        logger.timeLevel(LogLevel.DEBUG, key);\n\n        // Wait until a slot becomes available\n        return new Promise<void>((resolve: () => void) => {\n            this.waitQueue.push(() => {\n                logger.timeEndLevel(LogLevel.DEBUG, key);\n                this.loadingCount++;\n                logger.debug(\n                    '%s: Semaphore acquired slot after waiting (%d/%d active, %d queued)',\n                    key,\n                    this.loadingCount,\n                    this.maxConcurrent,\n                    this.waitQueue.length\n                );\n                resolve();\n            });\n        });\n    }\n\n    /**\n     * Release a loading slot (semaphore release)\n     */\n    private releaseLoadingSlot(key: string): void {\n        if (this.waitQueue.length > 0) {\n            logger.debug(\n                '%s: Semaphore: releasing slot to waiting request (%d/%d active, %d queued)',\n                key,\n                this.loadingCount,\n                this.maxConcurrent,\n                this.waitQueue.length\n            );\n            const next = this.waitQueue.shift();\n            if (next) {\n                next(); // This will increment loadingCount in the queued resolver\n            }\n        } else {\n            this.loadingCount--;\n            logger.debug(\n                '%s: Semaphore: released slot (%d/%d active, %d queued)',\n                key,\n                this.loadingCount,\n                this.maxConcurrent,\n                this.waitQueue.length\n            );\n        }\n    }\n}\n","import { createLogger, Logger, LogLevel } from '../../utils';\nimport { ReentrantLock } from './ReentrantLock';\nconst logger: Logger = createLogger('tile/cache/Cache');\n\n// ============================================================================\n// LRU CACHE - Memory-Efficient Caching with Concurrency Control\n// ============================================================================\n\n/**\n * LRU (Least Recently Used) cache with performance optimizations and cleanup support\n * Features:\n * - O(1) LRU operations using doubly-linked list structure\n * - Automatic eviction when capacity exceeded\n * - Concurrent loading with deduplication via ReentrantLock\n * - Optional cleanup function for resource management\n * - Thread-safe operations with proper error handling\n */\nexport class Cache<K, T> {\n    private readonly maxSize: number;\n    private readonly cache: Map<string, T>;\n    private readonly keyMapper: (key: K) => string;\n    private readonly valueBuilder: (key: K) => Promise<T>;\n    private readonly cleanupFn?: (value: T) => void;\n\n    // Using LinkedList-like structure for true O(1) LRU operations\n    private readonly lruOrder: Map<string, { prev: string | null; next: string | null }>;\n    private head: string | null = null;\n    private tail: string | null = null;\n\n    // Concurrency control\n    private readonly lock: ReentrantLock<T>;\n\n    // ========================================================================\n    // CONSTRUCTOR & VALIDATION\n    // ========================================================================\n\n    constructor(\n        maxSize: number,\n        keyMapper: (key: K) => string,\n        valueBuilder: (key: K) => Promise<T>,\n        cleanupFn?: (value: T) => void\n    ) {\n        if (maxSize <= 0) {\n            throw new Error('Cache size must be greater than 0');\n        }\n\n        this.maxSize = maxSize;\n        this.keyMapper = keyMapper;\n        this.valueBuilder = valueBuilder;\n        this.cleanupFn = cleanupFn;\n        this.cache = new Map();\n        this.lruOrder = new Map();\n        this.lock = new ReentrantLock<T>(maxSize);\n    }\n\n    // ========================================================================\n    // PUBLIC API - CACHE OPERATIONS\n    // ========================================================================\n\n    public getDirect(k: K): T | undefined {\n        const key = this.keyMapper(k);\n        const cachedItem = this.cache.get(key);\n\n        if (cachedItem) {\n            this.moveToFront(key);\n            return cachedItem;\n        }\n        return undefined;\n    }\n\n    /**\n     * Get item from cache or build if not present\n     * @param k - Key to retrieve\n     * @returns Promise resolving to cached or newly built value\n     */\n    public async get(k: K): Promise<T> {\n        const key = this.keyMapper(k);\n        const cachedItem = this.cache.get(key);\n\n        if (cachedItem) {\n            this.moveToFront(key);\n            return cachedItem;\n        }\n        logger.debug('%s miss', key);\n\n        return this.lock.acquire(key, async () => {\n            const existing = this.cache.get(key);\n            if (existing) {\n                logger.debug('%s Missed at first but now OK', key);\n                this.moveToFront(key);\n                return existing;\n            }\n\n            logger.info('%s loading', key);\n            logger.timeLevel(LogLevel.INFO, key);\n            const newItem = await this.valueBuilder(k);\n            logger.info('%s loaded', key);\n            logger.timeEndLevel(LogLevel.INFO, key);\n            this.set(key, newItem);\n            return newItem;\n        });\n    }\n\n    /**\n     * Clear all cached items\n     */\n    public clear(): void {\n        logger.debug('clear');\n        // Call cleanup function for all cached items\n        if (this.cleanupFn) {\n            for (const value of this.cache.values()) {\n                this.cleanupFn(value);\n            }\n        }\n\n        this.cache.clear();\n        this.lruOrder.clear();\n        this.head = null;\n        this.tail = null;\n    }\n\n    // ========================================================================\n    // PROTECTED API - INSPECTION METHODS\n    // ========================================================================\n\n    /**\n     * Check if item exists in cache\n     * @param k - Key to check\n     * @returns True if key exists in cache\n     */\n    protected has(k: K): boolean {\n        const key = this.keyMapper(k);\n        return this.cache.has(key);\n    }\n\n    /**\n     * Get all cached keys\n     * @returns Array of all cached keys\n     */\n    protected getKeys(): string[] {\n        return Array.from(this.cache.keys());\n    }\n\n    /**\n     * Get the least recently used keys in order\n     * @param count - Maximum number of keys to return\n     * @returns Array of LRU keys from least to most recently used\n     */\n    protected getLRUKeys(count: number = 10): string[] {\n        const result: string[] = [];\n        let current = this.tail; // Start from tail (least recently used)\n\n        while (current && result.length < count) {\n            result.push(current);\n            const node = this.lruOrder.get(current);\n            current = node?.prev || null;\n        }\n\n        return result;\n    }\n\n    // ========================================================================\n    // PRIVATE - CACHE STORAGE OPERATIONS\n    // ========================================================================\n\n    /**\n     * Store item in cache with automatic eviction\n     */\n    private set(key: string, value: T): void {\n        // If cache is full, remove least recently used item\n        if (this.cache.size >= this.maxSize) {\n            this.evictLeastRecentlyUsed();\n        }\n\n        // Add new item\n        this.cache.set(key, value);\n        this.addToFront(key);\n    }\n\n    /**\n     * Remove item from cache with cleanup\n     */\n    private delete(key: string): boolean {\n        logger.debug('%s delete', key);\n        if (!this.cache.has(key)) {\n            return false;\n        }\n\n        const value = this.cache.get(key);\n        this.cache.delete(key);\n        this.removeFromLRU(key);\n\n        // Call cleanup function if provided\n        if (value && this.cleanupFn) {\n            this.cleanupFn(value);\n        }\n\n        return true;\n    }\n\n    // ========================================================================\n    // PRIVATE - LRU EVICTION OPERATIONS\n    // ========================================================================\n\n    /**\n     * Remove the least recently used item to make space\n     */\n    private evictLeastRecentlyUsed(): void {\n        if (!this.tail) {\n            return;\n        }\n\n        const lruKey = this.tail;\n        this.delete(lruKey);\n    }\n\n    // ========================================================================\n    // PRIVATE - LRU LINKED LIST OPERATIONS\n    // ========================================================================\n\n    /**\n     * Add a key to the front of the LRU list (most recently used)\n     */\n    private addToFront(key: string): void {\n        const node = { prev: null, next: this.head };\n        this.lruOrder.set(key, node);\n\n        if (this.head) {\n            const headNode = this.lruOrder.get(this.head)!;\n            headNode.prev = key;\n        } else {\n            // First node\n            this.tail = key;\n        }\n\n        this.head = key;\n    }\n\n    /**\n     * Move an existing key to the front of the LRU list\n     */\n    private moveToFront(key: string): void {\n        if (this.head === key) {\n            return; // Already at front\n        }\n\n        // Remove from current position\n        this.removeFromLRU(key);\n\n        // Add to front\n        this.addToFront(key);\n    }\n\n    /**\n     * Remove a key from the LRU doubly-linked list\n     */\n    private removeFromLRU(key: string): void {\n        const node = this.lruOrder.get(key);\n        if (!node) {\n            return;\n        }\n\n        // Update previous node's next pointer\n        if (node.prev) {\n            const prevNode = this.lruOrder.get(node.prev)!;\n            prevNode.next = node.next;\n        } else {\n            // This was the head\n            this.head = node.next;\n        }\n\n        // Update next node's prev pointer\n        if (node.next) {\n            const nextNode = this.lruOrder.get(node.next)!;\n            nextNode.prev = node.prev;\n        } else {\n            // This was the tail\n            this.tail = node.prev;\n        }\n\n        this.lruOrder.delete(key);\n    }\n}\n","export { Cache } from './Cache';\n","import { Tile } from '..';\nimport { TileCoordinates } from '../../types';\nimport { createLogger, Logger, LogLevel } from '../../utils';\nimport { TileFetcher } from './TileFetcher';\n\nconst logger: Logger = createLogger('tile/fetcher/TileLoader');\n\nexport class TileLoader {\n    constructor(\n        private tileUrlTemplate: string,\n        private tileFetcher: TileFetcher\n    ) {}\n\n    // ========================================================================\n    // PUBLIC API\n    // ========================================================================\n\n    public async loadTile(tileCoords: TileCoordinates): Promise<Tile> {\n        const tileKey = `${tileCoords.z}/${tileCoords.x}/${tileCoords.y}`;\n        const tileUrl = this.getTileUrl(tileCoords);\n\n        const fetchTimer = `fetch-${tileKey}`;\n        logger.timeLevel(LogLevel.DEBUG, fetchTimer);\n        try {\n            const tile = await this.tileFetcher.fetchTile(tileUrl);\n            logger.timeEndLevel(LogLevel.DEBUG, fetchTimer);\n            return tile;\n        } catch (error) {\n            logger.timeEndLevel(LogLevel.DEBUG, fetchTimer);\n            if (error instanceof Error) {\n                throw new Error(`Failed to fetch tile from ${tileUrl}: ${error.message}`, {\n                    cause: error,\n                });\n            }\n            throw new Error(`Failed to fetch tile from ${tileUrl}: Unknown error`, {\n                cause: error,\n            });\n        }\n    }\n\n    // ========================================================================\n    // PRIVATE\n    // ========================================================================\n\n    private getTileUrl(tileCoords: TileCoordinates): string {\n        const tileKey = `${tileCoords.z}/${tileCoords.x}/${tileCoords.y}`;\n        const fetchTimer = `fetch-${tileKey}`;\n        logger.timeLevel(LogLevel.DEBUG, fetchTimer);\n        return this.tileUrlTemplate\n            .replace('{z}', tileCoords.z.toString())\n            .replace('{x}', tileCoords.x.toString())\n            .replace('{y}', tileCoords.y.toString());\n    }\n}\n","import { ImageData } from 'canvas';\nimport { Tile } from '../..';\nimport { RGBColor } from '../../../types';\nexport class NodeTile extends Tile {\n    constructor(readonly data: ImageData) {\n        super(data.width, data.height);\n    }\n    public close() {}\n\n    /**\n     * Extract RGB values from ImageData at specific pixel position\n     * @param imageData - Image data from terrain tile\n     * @param position - Pixel coordinates within the tile\n     * @returns RGB color values for elevation decoding\n     */\n    public getRGBFromImageData(index: number): RGBColor {\n        return {\n            red: this.data.data[index],\n            green: this.data.data[index + 1],\n            blue: this.data.data[index + 2],\n            // Alpha channel (index + 3) is ignored for Terrarium encoding\n        };\n    }\n}\n","import { ImageData } from 'canvas';\nimport sharp from 'sharp';\nimport { TileFetcher } from '..';\nimport { Tile } from '../..';\nimport { NodeTile } from './NodeJsTile';\n\n// ============================================================================\n// NODE.JS TILE FETCHER - HTTP client + WebP/PNG decoder\n// ============================================================================\n\n/**\n * Node.js implementation of TileFetcher.\n *\n * Terrain tiles are decoded with sharp (libvips + libwebp), which handles WebP\n * (lossy and lossless/VP8L) as well as PNG. The `canvas` package is not used for\n * decoding: it links no libwebp and rejects every WebP with\n * `Unsupported image type`.\n *\n * IMPORTANT - Terrarium tiles are data, not pictures. Elevation is packed into\n * the raw RGB bit patterns (`red * 256 + green + blue / 256 - 32768`), so the\n * decode must stay byte-exact:\n * - no alpha premultiplication (it would rewrite R, G and B),\n * - no colour management / ICC / colourspace conversion,\n * - no resize or resample.\n * `sharp(...).raw()` guarantees this by default; nothing here may add\n * `.premultiply()`, `.toColourspace()` or `.resize()`. Getting it wrong throws\n * nothing - a one-LSB change in the red channel is silently 256 metres.\n */\nexport class NodeJsTileFetcher implements TileFetcher {\n    /**\n     * Fetch a tile image and decode it to raw RGBA pixels\n     * @param url - The URL of the tile to fetch\n     * @returns Promise<Tile> - Tile giving access to the decoded pixels\n     */\n    public async fetchTile(url: string): Promise<Tile> {\n        const response = await fetch(url);\n\n        if (!response.ok) {\n            throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n        }\n\n        const encoded = Buffer.from(await response.arrayBuffer());\n\n        // ensureAlpha(): NodeTile indexes a 4-channel buffer, so a 3-channel\n        // decode of an opaque tile would shift every pixel.\n        const { data, info } = await sharp(encoded)\n            .ensureAlpha()\n            .raw()\n            .toBuffer({ resolveWithObject: true });\n\n        if (info.premultiplied || info.channels !== 4) {\n            throw new Error(\n                `Unexpected decode for ${url}: channels=${info.channels}, premultiplied=${info.premultiplied}`\n            );\n        }\n\n        return new NodeTile(new ImageData(new Uint8ClampedArray(data), info.width, info.height));\n    }\n}\n","import { Cache } from './cache';\nimport { TileFetcher } from './fetcher/TileFetcher';\nimport { TileLoader } from './fetcher/TileLoader';\nimport { Tile } from './Tile';\nimport type { TileCoordinates } from '../types';\n\nexport class TileManager {\n    private cache: Cache<TileCoordinates, Tile> | undefined;\n\n    constructor(\n        private readonly tileUrlTemplate: string,\n        private readonly cacheSize: number\n    ) {}\n\n    public getTileDirect(tileCoords: TileCoordinates): Tile | undefined {\n        if (this.cache) {\n            return this.cache.getDirect(tileCoords);\n        }\n        return undefined;\n    }\n\n    public async getTile(tileCoords: TileCoordinates): Promise<Tile> {\n        if (this.cache) {\n            return await this.cache.get(tileCoords);\n        }\n        return Promise.reject(new Error('Cache not initialized'));\n    }\n\n    async initCache(): Promise<Cache<TileCoordinates, Tile>> {\n        if (!this.cache) {\n            let tileFetcher: TileFetcher;\n            if (__NODE__) {\n                const { NodeJsTileFetcher } = await import('./fetcher/nodejs/NodeJsTileFetcher');\n                tileFetcher = new NodeJsTileFetcher();\n            } else {\n                const { BrowserTileFetcher } = await import('./fetcher/browser/BrowserTileFetcher');\n                tileFetcher = new BrowserTileFetcher();\n            }\n            // Create cache with cleanup function to close ImageBitmaps\n            const cleanupFunction = (cachedTile: Tile) => cachedTile.close();\n\n            const tileLoader = new TileLoader(this.tileUrlTemplate, tileFetcher);\n\n            const createdCache = new Cache<TileCoordinates, Tile>(\n                this.cacheSize,\n                tileCoords => `${tileCoords.z}/${tileCoords.x}/${tileCoords.y}`,\n                tileCoords => tileLoader.loadTile(tileCoords),\n                cleanupFunction\n            );\n            this.cache = createdCache;\n            return createdCache;\n        } else {\n            return this.cache;\n        }\n    }\n}\n","import { Pixel, RGBColor } from '../types';\n\nexport abstract class Tile {\n    readonly cache: Float64Array;\n    constructor(\n        readonly width: number,\n        readonly height: number\n    ) {\n        this.cache = new Float64Array(width * height);\n        this.cache.fill(NaN);\n    }\n\n    abstract close(): void;\n    abstract getRGBFromImageData(index: number): RGBColor;\n\n    getElevation(position: Pixel): number {\n        // Input validation\n        if (position.x < 0 || position.x >= this.width) {\n            throw new Error(\n                `Invalid x position: ${position.x}. Must be between 0 and ${this.width - 1}`\n            );\n        }\n\n        if (position.y < 0 || position.y >= this.height) {\n            throw new Error(\n                `Invalid y position: ${position.y}. Must be between 0 and ${this.height - 1}`\n            );\n        }\n\n        // Calculate pixel index in RGBA array (4 bytes per pixel)\n        const index = (position.y * this.width + position.x) * 4;\n        if (isNaN(this.cache[index])) {\n            const rgb = this.getRGBFromImageData(index);\n            const elevation = this.decodeElevation(rgb);\n            this.cache[index] = elevation;\n            return elevation;\n        } else {\n            return this.cache[index];\n        }\n    }\n\n    /**\n     * Decode elevation from RGB values using Terrarium encoding\n     * Formula: elevation = (red * 256 + green + blue / 256) - 32768\n     * @param rgb - RGB color values from terrain tile pixel\n     * @returns Elevation in meters, rounded to 2 decimal places\n     */\n    decodeElevation(rgb: RGBColor): number {\n        const elevation = rgb.red * 256 + rgb.green + rgb.blue / 256 - 32768;\n        return Math.round(elevation * 100) / 100; // Round to 2 decimal places for precision\n    }\n}\n","export { TileManager } from './TileManager';\nexport { Tile } from './Tile';\n","import { ElevationCalculator, BatchCalculator } from './calculator';\nimport { TileManager } from './tile';\nimport { createLogger, Logger } from './utils';\nimport type {\n    Coordinates,\n    CoordinatesElevation,\n    ElevationProviderConfig,\n    Attribution,\n    SetElevationsOptions,\n    GetElevationsAlongOptions,\n    GetElevationOptions,\n} from './types';\n\nconst logger: Logger = createLogger('ElevationProvider');\n\n/**\n * Main API class for retrieving elevation data from geographic coordinates\n */\nexport class ElevationProvider {\n    private readonly config: Required<ElevationProviderConfig>;\n    private readonly tileManager: TileManager;\n    private readonly calculator: ElevationCalculator;\n    private readonly batchCalculator: BatchCalculator;\n\n    // ============================================================================\n    // CONSTRUCTOR & CONFIGURATION\n    // ============================================================================\n\n    constructor(config: ElevationProviderConfig = {}) {\n        this.config = {\n            zoomLevel: config.zoomLevel ?? 12,\n            cacheSize: config.cacheSize ?? 100,\n            tileUrlTemplate:\n                config.tileUrlTemplate ?? 'https://tiles.mapterhorn.com/{z}/{x}/{y}.webp',\n            tileSize: config.tileSize ?? 512,\n            attribution: config.attribution ?? {\n                text: 'Mapterhorn elevation data. See mapterhorn.com/attribution/ for details.',\n                url: 'https://mapterhorn.com/attribution/',\n            },\n        };\n        logger.dir('Config :', this.config);\n\n        this.validateConfig();\n        this.tileManager = new TileManager(this.config.tileUrlTemplate, this.config.cacheSize);\n        this.calculator = new ElevationCalculator(this.tileManager, this.config.tileSize);\n        this.batchCalculator = new BatchCalculator(this.calculator);\n    }\n\n    /**\n     * Get current configuration\n     */\n    public getConfig(): Readonly<Required<ElevationProviderConfig>> {\n        return { ...this.config };\n    }\n\n    /**\n     * Get attribution information for elevation data\n     */\n    public getAttribution(): Attribution {\n        return this.config.attribution;\n    }\n\n    // ============================================================================\n    // PUBLIC API - SINGLE COORDINATE METHODS\n    // ============================================================================\n\n    /**\n     * Get elevation at specific coordinates\n     * @param latitude - Latitude in decimal degrees\n     * @param longitude - Longitude in decimal degrees\n     * @param options - Optional parameters\n     */\n    public async getElevation(\n        latitude: number,\n        longitude: number,\n        options?: GetElevationOptions\n    ): Promise<number> {\n        await this.tileManager.initCache();\n        const interpolation = options?.interpolation ?? true;\n        const coords: Coordinates = { latitude, longitude };\n        return await this.calculator.getElevation(coords, this.config.zoomLevel, interpolation);\n    }\n\n    // ============================================================================\n    // PUBLIC API - BULK COORDINATE METHODS\n    // ============================================================================\n\n    /**\n     * Get elevations for multiple coordinates from an interable\n     * @param coordinates - Iteratable of coordinates\n     * @param options - Optional parameters\n     */\n    public async setElevations(\n        coordinates: Iterable<Coordinates>,\n        options?: SetElevationsOptions\n    ): Promise<void> {\n        await this.tileManager.initCache();\n        const interpolation = options?.interpolation ?? true;\n        await this.batchCalculator.setElevations(coordinates, this.config.zoomLevel, interpolation);\n    }\n\n    /**\n     * Get elevations along a path defined by multiple coordinates\n     * @param path - Array of coordinates defining the path\n     * @param options - Optional parameters\n     */\n    public async getElevationsAlong(\n        path: Coordinates[],\n        options?: GetElevationsAlongOptions\n    ): Promise<CoordinatesElevation[]> {\n        await this.tileManager.initCache();\n        const step = options?.step ?? 10;\n        const minDistance = options?.minDistance ?? 1;\n        const interpolation = options?.interpolation ?? true;\n        const smoothingOptions = options?.smoothingOptions;\n        const filterOptions = options?.filterOptions;\n        return this.batchCalculator.getElevationsAlong(\n            path,\n            this.config.zoomLevel,\n            step,\n            minDistance,\n            interpolation,\n            smoothingOptions,\n            filterOptions\n        );\n    }\n\n    // ============================================================================\n    // PRIVATE - VALIDATION\n    // ============================================================================\n\n    private validateConfig(): void {\n        const { zoomLevel, cacheSize } = this.config;\n\n        if (!Number.isInteger(zoomLevel) || zoomLevel < 0 || zoomLevel > 15) {\n            throw new Error(\n                `Invalid zoom level: ${zoomLevel}. Must be an integer between 0 and 15`\n            );\n        }\n\n        if (!Number.isInteger(cacheSize) || cacheSize <= 0) {\n            throw new Error(`Invalid cache size: ${cacheSize}. Must be a positive integer`);\n        }\n\n        const { tileSize } = this.config;\n        if (!Number.isInteger(tileSize) || tileSize <= 0 || (tileSize & (tileSize - 1)) !== 0) {\n            throw new Error(`Invalid tile size: ${tileSize}. Must be a positive power of 2`);\n        }\n    }\n}\n","import {\n    CoordinatesElevation,\n    ElevationGainOptions,\n    ElevationGainPreset,\n    ElevationGainResult,\n} from '../types';\nimport { Distance } from './Distance';\nimport { ElevationSmoother } from './ElevationSmoother';\n\n/**\n * Dead band and smoothing half-width, in meters, for each {@link ElevationGainPreset}.\n *\n * `dem` is the default because this library's elevation is always DEM-derived. DEM error is\n * spatially correlated rather than white: consecutive samples inside one cell interpolate the same\n * posts, so there is little point-to-point jitter for a band to remove. Strava's 10 m (`gps`) is\n * sized for GPS-altimeter noise and reports 0 m on gentle terrain with a few meters of genuine\n * undulation. 3 m matches GoldenCheetah's default for corrected elevation.\n */\nexport const ELEVATION_GAIN_PRESETS: Readonly<\n    Record<ElevationGainPreset, { readonly thresholdM: number; readonly smoothWindowM: number }>\n> = Object.freeze({\n    raw: Object.freeze({ thresholdM: 0, smoothWindowM: 0 }),\n    barometric: Object.freeze({ thresholdM: 2, smoothWindowM: 15 }),\n    dem: Object.freeze({ thresholdM: 3, smoothWindowM: 30 }),\n    gps: Object.freeze({ thresholdM: 10, smoothWindowM: 50 }),\n});\n\ninterface Banked {\n    gain: number;\n    loss: number;\n    legCount: number;\n}\n\n/**\n * Cumulative ascent and descent with a hysteresis dead band, on a profile smoothed at its own\n * scale. Port of vcyclist's `ElevationGain`.\n *\n * ## Why not the plain sum of positive deltas\n *\n * It counts every wiggle, so it grows without bound as the sampling gets finer, the coastline\n * problem. This measures a figure that is stable under resampling.\n *\n * ## Why not a per-delta filter either\n *\n * `if (dEle > threshold) gain += dEle` reports **zero** on a smooth 500 m climb sampled every\n * 2 m, since no single delta exceeds the threshold. The accumulator tracks *turning points*\n * instead: a leg is banked once, in full, when the profile reverses by the threshold, so the\n * result depends only on local extrema.\n *\n * ## Why it smooths its own copy\n *\n * A wide smoothing kernel chosen for display or gradients reads D+ systematically low, because it\n * averages away real terrain. Measure on the profile *before* such smoothing; this function\n * applies its own narrow kernel (`smoothWindowM`) to a copy and never mutates its input.\n */\nexport class ElevationGain {\n    /**\n     * Measure cumulative ascent and descent along points. Distances are computed with Haversine.\n     * @param points - Profile to measure, in path order\n     * @param options - Measurement scale (default preset: 'dem')\n     */\n    public static compute(\n        points: CoordinatesElevation[],\n        options: ElevationGainOptions = {}\n    ): ElevationGainResult {\n        return ElevationGain.computeProfile(\n            Distance.cumulativeDistances(points),\n            points.map(p => p.elevation),\n            options\n        );\n    }\n\n    /**\n     * The same measurement on flat arrays.\n     * @param distances - Cumulative distance of each point in meters, non-decreasing\n     * @param elevations - Elevation of each point in meters\n     * @param options - Measurement scale (default preset: 'dem')\n     */\n    public static computeProfile(\n        distances: ArrayLike<number>,\n        elevations: ArrayLike<number>,\n        options: ElevationGainOptions = {}\n    ): ElevationGainResult {\n        const { thresholdM, smoothWindowM } = ElevationGain.resolveOptions(options);\n        if (distances.length !== elevations.length) {\n            throw new Error(\n                `distances (${distances.length}) and elevations (${elevations.length}) must have the same length`\n            );\n        }\n\n        const profile = ElevationSmoother.smoothProfile(distances, elevations, smoothWindowM);\n\n        let rawGain = 0;\n        let rawLoss = 0;\n        for (let i = 1; i < profile.length; i++) {\n            const d = profile[i] - profile[i - 1];\n            if (d > 0) {\n                rawGain += d;\n            } else {\n                rawLoss -= d;\n            }\n        }\n\n        const banked: Banked =\n            thresholdM > 0\n                ? ElevationGain.accumulate(profile, thresholdM)\n                : { gain: rawGain, loss: rawLoss, legCount: 0 };\n\n        return {\n            gainM: banked.gain,\n            lossM: banked.loss,\n            rawGainM: rawGain,\n            rawLossM: rawLoss,\n            thresholdM,\n            smoothWindowM,\n            legCount: banked.legCount,\n        };\n    }\n\n    private static resolveOptions(options: ElevationGainOptions): {\n        thresholdM: number;\n        smoothWindowM: number;\n    } {\n        const presetName = options.preset ?? 'dem';\n        const preset = Object.prototype.hasOwnProperty.call(ELEVATION_GAIN_PRESETS, presetName)\n            ? ELEVATION_GAIN_PRESETS[presetName]\n            : undefined;\n        if (!preset) {\n            throw new Error(\n                `Unknown elevation gain preset '${presetName}': expected one of ${Object.keys(ELEVATION_GAIN_PRESETS).join(', ')}`\n            );\n        }\n        const thresholdM = options.thresholdM ?? preset.thresholdM;\n        const smoothWindowM = options.smoothWindowM ?? preset.smoothWindowM;\n        if (!(Number.isFinite(thresholdM) && thresholdM >= 0)) {\n            throw new Error(`thresholdM must be finite and >= 0, was ${thresholdM}`);\n        }\n        if (!(Number.isFinite(smoothWindowM) && smoothWindowM >= 0)) {\n            throw new Error(`smoothWindowM must be finite and >= 0, was ${smoothWindowM}`);\n        }\n        return { thresholdM, smoothWindowM };\n    }\n\n    /**\n     * The turning-point accumulator.\n     *\n     * State is a confirmed turning point `ref`, a running extremum `ext` since `ref`, and a\n     * direction. A leg `[ref, ext]` is banked when the profile retraces by `threshold` from `ext`,\n     * so the count is all-or-nothing: a bump of exactly `threshold` counts in full, one of\n     * `threshold - ε` is dropped entirely *including its matching descent*.\n     *\n     * Banked legs span consecutive confirmed turning points, so they tile the profile disjointly:\n     * a climb with twenty 1 m sub-summits banks one leg, and nothing is counted twice.\n     *\n     * The `dir === 0` prologue exists because the first leg's direction is unknown until the\n     * profile has moved `threshold` in *some* direction; tracking both extrema and their indices\n     * until then stops a route that opens with a dip from booking that dip as a climb.\n     */\n    private static accumulate(h: number[], threshold: number): Banked {\n        let gain = 0;\n        let loss = 0;\n        let legCount = 0;\n\n        let hi = h[0];\n        let lo = h[0];\n        let iHi = 0;\n        let iLo = 0;\n        let dir = 0;\n        let ref = h[0];\n        let ext = h[0];\n\n        for (let i = 1; i < h.length; i++) {\n            const e = h[i];\n            if (dir === 0) {\n                if (e > hi) {\n                    hi = e;\n                    iHi = i;\n                }\n                if (e < lo) {\n                    lo = e;\n                    iLo = i;\n                }\n                if (hi - lo >= threshold) {\n                    if (iHi > iLo) {\n                        dir = 1;\n                        ref = lo;\n                        ext = hi;\n                    } else {\n                        dir = -1;\n                        ref = hi;\n                        ext = lo;\n                    }\n                }\n            } else if (dir > 0) {\n                if (e > ext) {\n                    ext = e;\n                } else if (ext - e >= threshold) {\n                    gain += ext - ref;\n                    legCount++;\n                    ref = ext;\n                    ext = e;\n                    dir = -1;\n                }\n            } else if (e < ext) {\n                ext = e;\n            } else if (e - ext >= threshold) {\n                loss += ref - ext;\n                legCount++;\n                ref = ext;\n                ext = e;\n                dir = 1;\n            }\n        }\n\n        // Flush the leg still open at the end. Without this a route that finishes at the top of\n        // its final climb loses that whole climb. An open leg always spans at least `threshold`.\n        if (dir > 0) {\n            gain += ext - ref;\n            legCount++;\n        } else if (dir < 0) {\n            loss += ref - ext;\n            legCount++;\n        }\n\n        return { gain, loss, legCount };\n    }\n}\n"],"mappings":"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"}