src — location

Module: src-location Cohesion: 0.80 Members: 0

src — location

The src/location module provides a comprehensive service for managing and retrieving geographic location data within the application. It encapsulates location acquisition logic, caching, configuration, and offers a suite of utility functions for common geospatial calculations.

This module is designed to be the single source of truth for location information, abstracting away the complexities of different location sources (GPS, IP, manual, etc.), reverse geocoding, and timezone resolution.

Core Concepts and Data Models

The module defines several interfaces to represent location data and configuration:

Utility Functions

The module exports several pure functions for common geographical calculations, which can be used independently or are leveraged by the LocationService.

Calculates the distance in meters between two geographic points using the Haversine formula. Internal calls: toRadians

Calculates the initial bearing in degrees (0-360) from one point to another. Internal calls: toRadians, toDegrees

Converts a numeric bearing (0-360 degrees) into a cardinal direction string (e.g., 'N', 'NE', 'SW').

Checks if a given point is within a specified radiusMeters of a center point. Internal calls: calculateDistance

Formats geographic coordinates into a human-readable string, either in decimal degrees or Degrees-Minutes-Seconds (DMS) format.

LocationService

The LocationService class is the central component of this module. It extends EventEmitter to allow other parts of the application to subscribe to location updates and errors.

Instantiation and Singleton Pattern

The LocationService can be instantiated directly, but it also provides a singleton pattern to ensure a single, consistent instance across the application:

Internal calls: shutdown

Configuration

Internal calls: stopAutoUpdate, startAutoUpdate

Location Retrieval (getCurrentLocation)

The core method for obtaining location data is getCurrentLocation. It handles source preference, caching, and post-processing.

graph TD
    A[getCurrentLocation(options?)] --> B{Mock Location Set?}
    B -- Yes --> Z[Return mockLocation]
    B -- No --> C{Cache Valid & Enabled?}
    C -- Yes --> Z
    C -- No --> D{Determine Source}
    D -- ip --> E[getLocationByIP()]
    D -- manual --> F[Use config.defaultLocation]
    D -- cached --> G[Return cachedLocation]
    D -- gps/network (mock) --> E
    E --> H[createLocation()]
    F --> H
    G --> H
    H --> I{Reverse Geocode Enabled & Needed?}
    I -- Yes --> J[reverseGeocode(location)]
    J --> K{Timezone Needed?}
    I -- No --> K
    K -- Yes --> L[getTimezone(location)]
    L --> M[Update Cache]
    K -- No --> M
    M --> N[Emit 'location-update']
    M --> O[Return GeoLocation]
    A --> P[Emit 'location-error']

  1. Mock Location Check: If setMockLocation has been used, it immediately returns the mock location.
  2. Cache Check: If caching is enabled (config.cacheEnabled) and a valid cachedLocation exists within config.cacheTTLMs, the cached location is returned. options.forceRefresh bypasses the cache.
  3. Source Selection: The location source is determined by options.source or the currentSource (which defaults to config.preferredSource).

  1. Location Object Creation: createLocation() is used to standardize the GeoLocation object.
  2. Post-Processing:

  1. Cache Update: The newly acquired location is stored in cachedLocation if config.cacheEnabled is true.
  2. Event Emission: On success, a 'location-update' event is emitted. On failure, a 'location-error' event is emitted.

Internal Location Acquisition (Mock Implementations)

The current module provides mock implementations for external API calls:

Internal calls: createLocation

Auto-Update

Internal calls: getCurrentLocation

Source Management

Mock Support (for Testing)

Cache Management

Distance & Direction Utilities

The LocationService integrates the module's utility functions for convenience:

Internal calls: getCurrentLocation, calculateDistance

Internal calls: getCurrentLocation, calculateBearing

Internal calls: getCurrentLocation, isWithinRadius

Lifecycle and Stats

Internal calls: stopAutoUpdate, clearCache

How to Use

import { getLocationService, LocationService, GeoCoordinates } from './location'; class="hl-cmt">// Adjust path as needed

async function initializeLocation() {
  const locationService: LocationService = getLocationService({
    preferredSource: 'ip',
    autoUpdateIntervalMs: 5000, class="hl-cmt">// Update every 5 seconds
    reverseGeocode: true,
  });

  class="hl-cmt">// Listen for location updates
  locationService.on('location-update', (location) => {
    console.log('Location updated:', location.name || location.formatted, location.latitude, location.longitude);
    if (location.address) {
      console.log('Address:', location.address.formatted);
    }
    if (location.timezone) {
      console.log('Timezone:', location.timezone.id);
    }
  });

  class="hl-cmt">// Listen for errors
  locationService.on('location-error', (error) => {
    console.error('Location error:', error.message);
  });

  class="hl-cmt">// Start auto-updating
  locationService.startAutoUpdate();

  class="hl-cmt">// Get current location once
  try {
    const currentLocation = await locationService.getCurrentLocation({ forceRefresh: true });
    console.log('Initial current location:', currentLocation.name || currentLocation.formatted);

    class="hl-cmt">// Example: Calculate distance to a point
    const eiffelTower: GeoCoordinates = { latitude: 48.8584, longitude: 2.2945 };
    const distance = await locationService.getDistanceTo(eiffelTower);
    console.log(`Distance to Eiffel Tower: ${distance.toFixed(2)} meters`);

    class="hl-cmt">// Example: Check if within radius
    const isNearEiffel = await locationService.isWithinRadius(eiffelTower, 2000); class="hl-cmt">// 2km radius
    console.log(`Is within 2km of Eiffel Tower: ${isNearEiffel}`);

  } catch (error) {
    console.error('Failed to get initial location:', error);
  }

  class="hl-cmt">// You can also manually set the source
  class="hl-cmt">// locationService.setSource('manual');
  class="hl-cmt">// locationService.updateConfig({ defaultLocation: { latitude: 34.0522, longitude: -118.2437 } }); // Los Angeles

  class="hl-cmt">// To stop auto-updates and clean up
  class="hl-cmt">// setTimeout(() => {
  class="hl-cmt">//   locationService.stopAutoUpdate();
  class="hl-cmt">//   console.log('Auto-update stopped.');
  class="hl-cmt">//   locationService.shutdown();
  class="hl-cmt">//   console.log('Location service shut down.');
  class="hl-cmt">// }, 30000);
}

initializeLocation();

Extension Points and Future Work

The current LocationService includes mock implementations for getLocationByIP, reverseGeocode, and getTimezone. For a production environment, these methods would need to be replaced with actual API calls to external services (e.g., Google Maps Geocoding API, OpenStreetMap Nominatim, IP geolocation providers, timezone APIs).

Additionally, support for native GPS/network location (e.g., using browser Geolocation API or a mobile SDK) would be integrated into the getCurrentLocation method, likely as new case statements within the switch (source) block.