/* * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved. * This file is licensed to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ import { CoordinateSystem, CoordinateValue, ParsedCoordinateMatch } from "./types.js"; //#region src/components/coordinate-field/coordinate-utils.d.ts /** Epsilon for coordinate equality comparison (≈11cm precision at equator) */ declare const COORDINATE_EPSILON = 0.000001; /** * Error message constants for coordinate format conversion * @internal */ declare const COORDINATE_ERROR_MESSAGES: { readonly INVALID: "Invalid coordinate"; readonly CONVERSION_FAILED: "Conversion failed"; readonly NOT_AVAILABLE_AT_POLES: "Not available at poles"; }; /** * Result of coordinate format conversion */ interface CoordinateFormatResult { /** The formatted coordinate string or error message */ value: string; /** Whether the coordinate format is valid and can be used/copied */ isValid: boolean; } /** * Format segment values into a coordinate string suitable for @accelint/geo parsing * * Converts an array of segment values into a string format that the geo package * parsers can understand. Each format has different requirements: * * DD: [lat, lon] → "lat, lon" * DDM: [latDeg, latMin, latDir, lonDeg, lonMin, lonDir] → "latDeg° latMin' latDir, lonDeg° lonMin' lonDir" * DMS: [latDeg, latMin, latSec, latDir, lonDeg, lonMin, lonSec, lonDir] → "latDeg° latMin' latSec\" latDir, lonDeg° lonMin' lonSec\" lonDir" * MGRS: [zone, band, grid, easting, northing] → "zone+band grid easting northing" * UTM: [zone, hemisphere, easting, northing] → "zone+hemisphere easting northing" * * @param segments - Array of segment values from user input * @param format - The coordinate system format * @returns Formatted coordinate string, or null if segments are invalid */ declare function formatSegmentsToCoordinateString(segments: string[], format: CoordinateSystem): string | null; /** * Parse a coordinate string into segment values * * Converts a formatted coordinate string (from @accelint/geo output or user input) * back into individual segment values for display. * * This is the inverse of formatSegmentsToCoordinateString. * * **Note on Duplication**: This function and its helpers (parseDDCoordinateString, * parseDDMCoordinateString, etc.) duplicate parsing logic that already exists in * the @accelint/geo package parsers: * * - Geo parsers: parseDecimalDegrees, parseDegreesDecimalMinutes, etc. * - These functions: parseDDCoordinateString, parseDDMCoordinateString, etc. * * Both use regex patterns to extract coordinate components from strings. The duplication * exists because: * * 1. **Geo parsers** extract components, validate them, convert to DD, then format back to strings * 2. **This function** extracts components from those formatted strings for the UI * * We're essentially undoing the formatting that geo just did. This is the second half * of the circular conversion described in convertDDToDisplaySegments. * * **Why we can't use geo parsers directly**: The geo parsers return coord objects with * only `coord.raw` (DD numbers) and formatting methods. They don't expose the parsed * segment components we need for the UI (e.g., the degrees, minutes, and direction values). * * **Parsing Order**: * - **convertDisplaySegmentsToDD**: Segments → String → **Geo parse** → DD ✓ (efficient) * - **convertDDToDisplaySegments**: DD → String → Geo parse → Geo format → **This parse** → Segments ✗ (circular) * * @param coordString - Formatted coordinate string * @param format - The coordinate system format * @returns Array of segment values, or null if parsing fails */ declare function parseCoordinateStringToSegments(coordString: string, format: CoordinateSystem): string[] | null; /** * Convert DD (internal format) to display format segment values * * Takes a CoordinateValue in Decimal Degrees format and converts it to the * segment values needed for the specified display format. * * Uses @accelint/geo to ensure accurate conversion between coordinate systems. * * **Note on Circular Conversion**: This function demonstrates the circular conversion * pattern discussed in the module documentation. The flow is: * * 1. Start with DD value: `{ lat: 40.7128, lon: -74.0060 }` * 2. Convert to coordinate string: `"40.7128 / -74.006"` * 3. Parse with geo package (creates coord object with internal parsed state) * 4. Format to target system using geo: `coord.ddm()` → `"40 42.768 N / 74 0.36 W"` * 5. Parse the formatted string AGAIN with regex to extract segments: `['40', '42.768', 'N', ...]` * * This is inefficient because: * - The geo package already has the component values (degrees, minutes, direction) internally * - We format them into a string, then immediately parse the string back apart * - The regex parsing duplicates work the geo parsers already did * * However, this approach is necessary because: * - The geo package only exposes `coord.raw` (DD numbers) and formatted strings * - It doesn't expose the intermediate component values we need for the UI segments * - We need individual segment values for separate input fields * * **Future Improvement**: If geo package exported component extractors like: * ```typescript * coord.components.ddm // { latDeg: 40, latMin: 42.768, latDir: 'N', ... } * ``` * Then we could eliminate the format→parse cycle entirely. * * @param value - Coordinate value in DD format `{ lat: number, lon: number }` * @param format - Target display format * @returns Array of segment values for display, or null if conversion fails * * @example * const segments = convertDDToDisplaySegments({ lat: 40.7128, lon: -74.0060 }, 'ddm'); * // Returns: ['40', '42.7680', 'N', '74', '0.3600', 'W'] */ declare function convertDDToDisplaySegments(value: CoordinateValue, format: CoordinateSystem): string[] | null; /** * Convert display format segment values to DD (internal format) * * Takes segment values from user input and converts them to a CoordinateValue * in Decimal Degrees format using @accelint/geo for validation and conversion. * * **Note on Efficiency**: This function demonstrates the EFFICIENT conversion direction. * The flow is: * * 1. Start with UI segments: `['40', '42.768', 'N', '74', '0.36', 'W']` * 2. Build coordinate string: `"40° 42.768' N, 74° 0.36' W"` * 3. Parse with geo package (validates and converts internally) * 4. Extract DD from coord.raw: `{ lat: 40.7128, lon: -74.0060 }` * * This is efficient because: * - We let geo do what it's designed for: parsing and validating coordinate strings * - We extract the DD values directly from `coord.raw` (no string parsing needed) * - Single direction: Segments → String → Geo Parse → DD (no circular conversion) * * Contrast with `convertDDToDisplaySegments` which has the circular pattern: * DD → String → Geo Parse → Geo Format → String → Regex Parse → Segments * * @param segments - Array of segment values from user input * @param format - The coordinate system format of the segments * @returns CoordinateValue in DD format, or null if invalid * * @example * const coord = convertDisplaySegmentsToDD(['40', '42.7680', 'N', '74', '0.3600', 'W'], 'ddm'); * // Returns: { lat: 40.7128, lon: -74.0060 } */ declare function convertDisplaySegmentsToDD(segments: string[], format: CoordinateSystem): CoordinateValue | null; /** * Validate coordinate segments and return errors * * Uses @accelint/geo to validate the segments and returns any validation errors. * Only validates when all required segments are filled. * * @param segments - Array of segment values from user input * @param format - The coordinate system format * @returns Array of error messages, empty if valid or incomplete * * @example * const errors = validateCoordinateSegments(['91', '0', 'N', '0', '0', 'E'], 'ddm'); * // Returns: ['Invalid coordinate value'] */ declare function validateCoordinateSegments(segments: string[], format: CoordinateSystem): string[]; /** * Check if all segments are filled * * Helper to determine if the user has completed entering all segment values. * Used to determine when to trigger validation. * * @param segments - Array of segment values * @returns True if all segments have values, false otherwise */ declare function areAllSegmentsFilled(segments: string[]): boolean; /** * Check if any segments have values * * Helper to determine if the user has started entering coordinate values. * * @param segments - Array of segment values * @returns True if any segment has a value, false if all empty */ declare function hasAnySegmentValue(segments: string[]): boolean; /** * Get all coordinate formats for a given DD value * * Converts a Decimal Degrees coordinate to all 5 supported coordinate systems * for display in the format conversion popover. * * Each format is tried independently - if one fails (e.g., UTM/MGRS at poles), * the others can still succeed. * * @param value - Coordinate value in DD format `{ lat: number, lon: number }` * @returns Object containing formatted strings and validity status for all coordinate systems * * @example * const formats = getAllCoordinateFormats({ lat: 40.7128, lon: -74.0060 }); * // Returns: { * // dd: { value: "40.7128 N / 74.006 W", isValid: true }, * // ddm: { value: "40 42.768 N / 74 0.36 W", isValid: true }, * // dms: { value: "40 42 46.08 N / 74 0 21.6 W", isValid: true }, * // mgrs: { value: "18T WL 80654 06346", isValid: true }, * // utm: { value: "18N 585628 4511644", isValid: true } * // } * * @example * const formats = getAllCoordinateFormats({ lat: 90, lon: 0 }); * // Returns: { * // dd: { value: "90 N / 0 E", isValid: true }, * // ddm: { value: "90 0 N / 0 0 E", isValid: true }, * // dms: { value: "90 0 0 N / 0 0 0 E", isValid: true }, * // mgrs: { value: "Not available at poles", isValid: false }, * // utm: { value: "Not available at poles", isValid: false } * // } */ declare function getAllCoordinateFormats(value: CoordinateValue | null): Record; /** * Check if pasted text looks like a complete coordinate string * * Uses heuristics to detect if the pasted text contains a full coordinate * rather than just a single segment value. This prevents intercepting * single-segment pastes. * * Indicators of a complete coordinate: * - Contains separators: comma, slash, or multiple consecutive spaces * - Contains coordinate symbols: °, ′, ″, ', " * - Multiple numbers separated by whitespace * * @param text - The pasted text to check * @returns True if it looks like a complete coordinate string * * @example * isCompleteCoordinate("40.7128, -74.0060") // true - contains comma * isCompleteCoordinate("40° 42' 46\" N / 74° 0' 22\" W") // true - contains symbols * isCompleteCoordinate("18T WM 12345 67890") // true - multiple parts * isCompleteCoordinate("42") // false - single number * isCompleteCoordinate("N") // false - single letter */ declare function isCompleteCoordinate(text: string): boolean; /** * Attempt to parse pasted text as all coordinate formats * * Tries to parse the pasted text using each of the 5 coordinate system parsers. * Returns all formats that successfully parse the text. * * This enables automatic detection of coordinate format and disambiguation * when multiple formats match the same input string. * * @param pastedText - The raw text from clipboard * @returns Array of successfully parsed coordinate matches (may be empty) * * @example * const matches = parseCoordinatePaste("40.7128, -74.0060"); * // Returns: [{ format: 'dd', value: { lat: 40.7128, lon: -74.0060 }, displayString: "..." }] * * @example * const matches = parseCoordinatePaste("18T WM 12345 67890"); * // Returns: [{ format: 'mgrs', value: { lat: ..., lon: ... }, displayString: "..." }] * * @example * const matches = parseCoordinatePaste("invalid text"); * // Returns: [] */ declare function parseCoordinatePaste(pastedText: string): ParsedCoordinateMatch[]; /** * Check if two coordinates are equal within epsilon tolerance * * @param coord1 - First coordinate to compare. * @param coord2 - Second coordinate to compare. * @param epsilon - Tolerance for comparison (defaults to COORDINATE_EPSILON). * @returns True if coordinates are equal within tolerance. */ declare function areCoordinatesEqual(coord1: { lat: number; lon: number; }, coord2: { lat: number; lon: number; }, epsilon?: number): boolean; /** * Deduplicate coordinate matches by location, keeping first match for each unique location * * @param matches - Array of parsed coordinate matches to deduplicate. * @returns Array of unique matches based on coordinate location. */ declare function deduplicateMatchesByLocation(matches: ParsedCoordinateMatch[]): ParsedCoordinateMatch[]; //#endregion export { COORDINATE_EPSILON, COORDINATE_ERROR_MESSAGES, CoordinateFormatResult, areAllSegmentsFilled, areCoordinatesEqual, convertDDToDisplaySegments, convertDisplaySegmentsToDD, deduplicateMatchesByLocation, formatSegmentsToCoordinateString, getAllCoordinateFormats, hasAnySegmentValue, isCompleteCoordinate, parseCoordinatePaste, parseCoordinateStringToSegments, validateCoordinateSegments }; //# sourceMappingURL=coordinate-utils.d.ts.map