src — location
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:
GeoCoordinates: The fundamental building block for any geographic point, including latitude, longitude, altitude, speed, heading, and accuracy metrics.GeoLocation: ExtendsGeoCoordinatesto provide a complete location context. It adds atimestamp,source(e.g.,'gps','ip'), an optionalname,addresscomponents, andtimezoneinformation. This is the primary data structure returned by theLocationService.LocationSource: A union type ('gps' | 'network' | 'ip' | 'manual' | 'cached' | 'mock') indicating where the location data originated.AddressComponents: Detailed breakdown of a physical address (street, city, country, postal code, etc.).TimezoneInfo: Information about the timezone at a given location (ID, abbreviation, UTC offset, DST status).LocationConfig: An interface for configuring theLocationService, including settings for caching, auto-updates, preferred source, reverse geocoding, and fallback locations.DEFAULT_LOCATION_CONFIGprovides sensible defaults.LocationEvents: Defines the events emitted by theLocationService('location-update','location-error','source-change').
Utility Functions
The module exports several pure functions for common geographical calculations, which can be used independently or are leveraged by the LocationService.
calculateDistance(point1: GeoCoordinates, point2: GeoCoordinates): number
Calculates the distance in meters between two geographic points using the Haversine formula.
Internal calls: toRadians
calculateBearing(from: GeoCoordinates, to: GeoCoordinates): number
Calculates the initial bearing in degrees (0-360) from one point to another.
Internal calls: toRadians, toDegrees
bearingToCardinal(bearing: number): string
Converts a numeric bearing (0-360 degrees) into a cardinal direction string (e.g., 'N', 'NE', 'SW').
isWithinRadius(point: GeoCoordinates, center: GeoCoordinates, radiusMeters: number): boolean
Checks if a given point is within a specified radiusMeters of a center point.
Internal calls: calculateDistance
formatCoordinates(coords: GeoCoordinates, format: 'decimal' | 'dms' = 'decimal'): string
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:
getLocationService(config?: Partial: Returns the singleton instance of): LocationService LocationService, creating it if it doesn't already exist. Configuration provided here will be merged withDEFAULT_LOCATION_CONFIGon first creation.resetLocationService(): void: Shuts down the current singleton instance and clears it, allowing a new instance to be created on the next call togetLocationService. This is primarily useful for testing or application resets.
Internal calls: shutdown
Configuration
constructor(config: Partial: Initializes the service with default or provided configuration.= {}) getConfig(): LocationConfig: Returns the current configuration of the service.updateConfig(config: Partial: Merges the provided partial configuration with the existing one. If): void autoUpdateIntervalMsis changed, it will automatically stop and restart the auto-update mechanism.
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']
- Mock Location Check: If
setMockLocationhas been used, it immediately returns the mock location. - Cache Check: If caching is enabled (
config.cacheEnabled) and a validcachedLocationexists withinconfig.cacheTTLMs, the cached location is returned.options.forceRefreshbypasses the cache. - Source Selection: The location source is determined by
options.sourceor thecurrentSource(which defaults toconfig.preferredSource).
'ip': CallsgetLocationByIP().'manual': Usesconfig.defaultLocationif set.'cached': Returns thecachedLocation(if available).'gps','network': In the current mock implementation, these fall back togetLocationByIP().
- Location Object Creation:
createLocation()is used to standardize theGeoLocationobject. - Post-Processing:
- Reverse Geocoding: If
config.reverseGeocodeis true and the retrieved location doesn't already have address components,reverseGeocode()is called to enrich theGeoLocationobject. - Timezone Resolution: If the location doesn't have timezone information,
getTimezone()is called.
- Cache Update: The newly acquired location is stored in
cachedLocationifconfig.cacheEnabledis true. - 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:
getLocationByIP(): Promise: Simulates an IP geolocation API call, currently returning hardcoded coordinates for Paris.
Internal calls: createLocation
reverseGeocode(location: GeoLocation): Promise: Simulates a reverse geocoding API call. It returns a mock address for Paris if the coordinates are close, otherwise a generic formatted string.getTimezone(location: GeoLocation): TimezoneInfo: Provides a simple timezone estimation based on longitude, returning mock timezone info for Paris.
Auto-Update
startAutoUpdate(): void: Initiates asetIntervalto periodically callgetCurrentLocation({ forceRefresh: true })based onconfig.autoUpdateIntervalMs. Errors during auto-update are emitted via'location-error'.
Internal calls: getCurrentLocation
stopAutoUpdate(): void: Clears the auto-update interval, stopping periodic location fetches.
Source Management
getSource(): LocationSource: Returns the currently active location source.setSource(source: LocationSource): void: Sets the preferred location source for subsequentgetCurrentLocationcalls. Emits a'source-change'event.
Mock Support (for Testing)
setMockLocation(location: GeoLocation | null): void: Allows setting a specificGeoLocationobject thatgetCurrentLocationwill immediately return, bypassing all other logic. Essential for predictable testing.createMockLocation(latitude: number, longitude: number, options?: Partial: A helper to easily create): GeoLocation GeoLocationobjects with a'mock'source for testing purposes.
Cache Management
clearCache(): void: Resets the internalcachedLocationandcacheTimestamp.getCachedLocation(): GeoLocation | null: Returns the currently cachedGeoLocationobject, if any.
Distance & Direction Utilities
The LocationService integrates the module's utility functions for convenience:
getDistanceTo(point: GeoCoordinates): Promise: Calculates the distance from the current location (fetched viagetCurrentLocation) to a specified point.
Internal calls: getCurrentLocation, calculateDistance
getBearingTo(point: GeoCoordinates): Promise: Calculates the bearing from the current location to a specified point.
Internal calls: getCurrentLocation, calculateBearing
isWithinRadius(center: GeoCoordinates, radiusMeters: number): Promise: Checks if the current location is within a specified radius of a center point.
Internal calls: getCurrentLocation, isWithinRadius
Lifecycle and Stats
shutdown(): void: Cleans up the service by stopping auto-updates, clearing the cache, and removing any mock location.
Internal calls: stopAutoUpdate, clearCache
getStats(): object: Returns an object containing various statistics about the service's current state, such asenabled,source,hasCachedLocation,cacheAge, andisAutoUpdating.
How to Use
import { getLocationService, LocationService, GeoCoordinates } from 39;./location39;; class="hl-cmt">// Adjust path as needed
async function initializeLocation() {
const locationService: LocationService = getLocationService({
preferredSource: 39;ip39;,
autoUpdateIntervalMs: 5000, class="hl-cmt">// Update every 5 seconds
reverseGeocode: true,
});
class="hl-cmt">// Listen for location updates
locationService.on(39;location-update39;, (location) => {
console.log(39;Location updated:39;, location.name || location.formatted, location.latitude, location.longitude);
if (location.address) {
console.log(39;Address:39;, location.address.formatted);
}
if (location.timezone) {
console.log(39;Timezone:39;, location.timezone.id);
}
});
class="hl-cmt">// Listen for errors
locationService.on(39;location-error39;, (error) => {
console.error(39;Location error:39;, 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(39;Initial current location:39;, 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(39;Failed to get initial location:39;, error);
}
class="hl-cmt">// You can also manually set the source
class="hl-cmt">// locationService.setSource(39;manual39;);
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(39;Auto-update stopped.39;);
class="hl-cmt">// locationService.shutdown();
class="hl-cmt">// console.log(39;Location service shut down.39;);
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.