geo-utils/README.md
2026-01-30 11:55:53 -08:00

6.3 KiB

@lilith/geo-utils

Geolocation utilities for the Lilith Platform: distance calculations, geocoding, and coordinate helpers.

Features

  • Distance Calculations: Haversine formula for accurate great-circle distances
  • Forward Geocoding: Convert addresses to coordinates (via Nominatim/OSM)
  • Reverse Geocoding: Convert coordinates to addresses
  • Zero Dependencies: Pure TypeScript, no external runtime dependencies
  • Type-Safe: Full TypeScript support with strict typing

Installation

pnpm add @lilith/geo-utils

Quick Start

import {
  calculateDistance,
  searchLocations,
  reverseGeocode,
  type Coordinates,
} from '@lilith/geo-utils';

// Calculate distance between two points
const distance = calculateDistance(
  { lat: 40.7128, lng: -74.0060 },  // New York
  { lat: 34.0522, lng: -118.2437 }, // Los Angeles
  'miles'
);
// Returns approximately 2451 miles

// Search for a location
const results = await searchLocations('New York City');

// Reverse geocode coordinates
const location = await reverseGeocode({ lat: 40.7128, lng: -74.0060 });

API Reference

Distance Calculations

calculateDistance(from, to, unit?)

Calculate the great-circle distance between two points using the Haversine formula.

calculateDistance(
  from: Coordinates,
  to: Coordinates,
  unit?: 'miles' | 'kilometers' | 'meters'
): number

Example:

const nyToLa = calculateDistance(
  { lat: 40.7128, lng: -74.0060 },
  { lat: 34.0522, lng: -118.2437 },
  'miles'
);
// ~2451 miles

isWithinRadius(center, point, radius, unit?)

Check if a point is within a given radius of another point.

isWithinRadius(
  center: Coordinates,
  point: Coordinates,
  radius: number,
  unit?: DistanceUnit
): boolean

Example:

const nearby = isWithinRadius(
  { lat: 40.7128, lng: -74.0060 },
  { lat: 40.7580, lng: -73.9855 },
  10,
  'miles'
);
// true (Central Park is ~5 miles from downtown)

filterByDistance(items, center, maxDistance, getCoordinates, unit?)

Filter an array of items by distance from a center point.

const stores = [
  { name: 'Store A', location: { lat: 40.71, lng: -74.00 } },
  { name: 'Store B', location: { lat: 40.75, lng: -73.99 } },
  { name: 'Store C', location: { lat: 41.00, lng: -74.10 } },
];

const nearby = filterByDistance(
  stores,
  { lat: 40.72, lng: -74.00 },
  5, // 5 miles
  (store) => store.location,
  'miles'
);
// Returns Store A and Store B

sortByDistance(items, center, getCoordinates, order?, unit?)

Sort an array of items by distance from a center point.

const sorted = sortByDistance(
  stores,
  { lat: 40.72, lng: -74.00 },
  (store) => store.location,
  'asc', // nearest first
  'miles'
);
// Returns stores with distanceFromCenter property added

convertDistance(value, from, to)

Convert distance between units.

const km = convertDistance(10, 'miles', 'kilometers');
// ~16.09 km

formatDistance(distance, unit?, precision?)

Format distance for display.

formatDistance(2.5, 'miles');      // "2.5 mi"
formatDistance(4.0, 'kilometers'); // "4.0 km"
formatDistance(1500, 'meters');    // "1500.0 m"

Geocoding

searchLocations(query, options?)

Search for locations by query string (forward geocoding).

const results = await searchLocations('Empire State Building', {
  limit: 5,
});

// Returns:
[{
  id: 'way-123456',
  name: 'Empire State Building',
  type: 'place',
  coordinates: { lat: 40.7484, lng: -73.9857 },
  formattedAddress: 'Empire State Building, 350 5th Avenue, Midtown...',
  boundingBox: [40.747, 40.749, -73.987, -73.984],
  importance: 0.85,
}]

reverseGeocode(coordinates, options?)

Convert coordinates to address (reverse geocoding).

const location = await reverseGeocode({ lat: 40.7128, lng: -74.0060 });

// Returns:
{
  formattedAddress: 'City Hall, New York, NY 10007, United States',
  city: 'New York',
  neighborhood: 'Civic Center',
  region: 'New York',
  country: 'United States',
  countryCode: 'US',
  postcode: '10007',
  shortName: 'Civic Center, New York',
}

createGeocodingClient(config?)

Create a custom geocoding client with specific configuration.

const client = createGeocodingClient({
  userAgent: 'MyApp/1.0',
  language: 'de',           // German results
  countryCodes: ['DE', 'AT'], // Restrict to Germany/Austria
  timeout: 5000,
});

const results = await client.search('Berlin');
const location = await client.reverse({ lat: 52.52, lng: 13.405 });

Types

Coordinates

interface Coordinates {
  lat: number;
  lng: number;
}

GeocodedLocation

interface GeocodedLocation {
  id: string;
  name: string;
  type: LocationType;
  coordinates: Coordinates;
  formattedAddress: string;
  boundingBox?: BoundingBox;
  importance?: number;
}

ReverseGeocodedLocation

interface ReverseGeocodedLocation {
  formattedAddress: string;
  city?: string;
  neighborhood?: string;
  region?: string;
  country?: string;
  countryCode?: string;
  postcode?: string;
  shortName: string;
}

DistanceUnit

type DistanceUnit = 'miles' | 'kilometers' | 'meters';

LocationType

type LocationType = 'city' | 'neighborhood' | 'region' | 'country' | 'address' | 'place';

Subpath Exports

Import specific modules for tree-shaking:

// Distance calculations only
import { calculateDistance } from '@lilith/geo-utils/distance';

// Geocoding only
import { searchLocations, reverseGeocode } from '@lilith/geo-utils/geocoding';

// Types only
import type { Coordinates, DistanceUnit } from '@lilith/geo-utils/types';

Nominatim Usage Policy

This package uses Nominatim (OpenStreetMap) for geocoding. Please respect their usage policy:

  • Maximum 1 request per second
  • Set a valid User-Agent
  • Cache results when possible
  • Consider running your own Nominatim instance for heavy usage

The default client includes a proper User-Agent string. For production apps with high traffic, consider using a commercial geocoding service or self-hosted Nominatim.

License

MIT

Test TWO 1767646380

Test history persist $(date +%s)