npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ciphercross/nestjs-location

v1.0.1

Published

NestJS module for location-based functionality including distance calculation, filtering, nearest search, clustering, and reverse geocoding

Readme

@ciphercross/nestjs-location

NestJS module for location-based functionality including distance calculation, filtering, nearest search, clustering, and reverse geocoding.

Provides geospatial utilities, distance calculations, location clustering, and reverse geocoding out of the box.

✨ Features

🗺️ Distance calculation using Haversine formula

🔍 Nearest services/partners search

📍 Location filtering by radius

Optimized algorithms - Efficient in-memory filtering and sorting

🎯 Framework-agnostic utilities

🔌 Simple setup - no adapters or database configuration needed

👥 User clustering based on geographic proximity

🌍 Reverse geocoding with OpenStreetMap Nominatim API

📦 Installation

npm install @ciphercross/nestjs-location

How It Works

This package provides in-memory location utilities - it works with arrays of data you provide:

Your Project                    Package
┌─────────────┐                ┌─────────────┐
│ Your Data   │───passes──────▶│ Location    │
│ (arrays)    │                │ Service     │
│             │                │ (logic)     │
└──────┬──────┘                └──────┬──────┘
       │                               │
       │ Gets data from DB             │ Calculates distances
       │                               │ Filters & sorts
       ▼                               ▼
┌─────────────┐                ┌─────────────┐
│ Your DB     │                │ Results     │
│ (Prisma/    │                │ (filtered)  │
│  TypeORM)   │                └─────────────┘
└─────────────┘

Key point: You fetch data from your database, then pass it to the service for location calculations.

🚀 Quick Start

1. Import the Module

import { Module } from '@nestjs/common';
import { LocationModule } from '@ciphercross/nestjs-location';

@Module({
  imports: [
    LocationModule.forRoot(), // Simple setup - no adapters needed
  ],
})
export class AppModule {}

2. Use the Service

import { Injectable } from '@nestjs/common';
import { LocationService, SortBy } from '@ciphercross/nestjs-location';

@Injectable()
export class MyService {
  constructor(private readonly locationService: LocationService) {}

  async findNearbyServices(items: any[], lat: number, lng: number, radius: number) {
    // Find nearest items within radius
    return this.locationService.findNearest(items, lat, lng, radius, {
      page: 1,
      limit: 20,
      sortBy: SortBy.DISTANCE, // or SortBy.NAME
    });
  }
}

Performance Tips

For large datasets (10,000+ records), consider pre-filtering data at the database level before passing to the service:

// ✅ Good: Pre-filter by city/region before location calculations
const cityServices = await prisma.service.findMany({
  where: {
    location: {
      city: 'Kyiv', // Filter at DB level first
    },
  },
  include: { location: true },
});

// Then use LocationService for precise radius filtering
const result = locationService.findNearest(
  cityServices, // Pre-filtered data
  userLat,
  userLng,
  10 // km radius
);

Performance:

  • Small datasets (< 1,000 records): Fast, no optimization needed
  • Large datasets (10,000+ records): Pre-filter at database level, then use LocationService for precise filtering

Using Utilities Directly

You can also use the utilities directly without the full module:

import { calculateDistance, DistanceFilterService } from '@ciphercross/nestjs-location';

// Calculate distance between two points
const distance = calculateDistance(40.7128, -74.006, 34.0522, -118.2437);
// Returns distance in kilometers

// Filter items by distance
const filterService = new DistanceFilterService();
const filteredItems = filterService.applyDistanceFilter(
  items,
  40.7128, // user lat
  -74.006, // user lng
  10, // max distance in km
);

Reverse Geocoding (City Lookup)

Get city information by coordinates using OpenStreetMap Nominatim API:

import { getCityByCoordinates, GeocodingResultFormat } from '@ciphercross/nestjs-location';

// Get city info (default format)
const cityInfo = await getCityByCoordinates(50.4501, 30.5234);
// Returns: { city: 'Kyiv', country: 'Ukraine', countryCode: 'UA', ... }

// Get full Nominatim response
const fullResponse = await getCityByCoordinates(50.4501, 30.5234, {
  format: GeocodingResultFormat.FULL,
});

// Get formatted address string
const address = await getCityByCoordinates(50.4501, 30.5234, {
  format: GeocodingResultFormat.ADDRESS,
});
// Returns: 'Kyiv, Ukraine'

// With custom options
const result = await getCityByCoordinates(50.4501, 30.5234, {
  format: GeocodingResultFormat.CITY,
  language: 'uk', // Ukrainian language
  cacheTtl: 3600000, // Cache for 1 hour
});

Features:

  • Automatic caching (24 hours by default)
  • Rate limiting (respects Nominatim API limits)
  • Multiple result formats (city info, full response, address string)
  • Language support
  • Custom API endpoint support

Note: The public Nominatim API has a rate limit of 1 request per second. For production use with high volume, consider setting up your own Nominatim server.

Clustering

Group users into clusters based on their geographic locations:

import { ClusteringService } from '@ciphercross/nestjs-location';

const clusteringService = new ClusteringService();

// With array of users
const users = [
  { id: '1', lat: 50.4501, lng: 30.5234 },
  { id: '2', lat: 50.4547, lng: 30.5238 },
  { id: '3', lat: 40.7128, lng: -74.006 },
];

// Create clusters with default radius (50km)
const clusters = clusteringService.createClusters(users);

// With custom radius
const clustersCustom = clusteringService.createClusters(users, { radiusKm: 25 });

// With single user
const singleUser = { id: '1', lat: 50.4501, lng: 30.5234 };
const singleCluster = clusteringService.createClusters(singleUser);

Features:

  • Groups users into clusters based on proximity
  • Configurable cluster radius (default: 50km)
  • Accepts single user or array of users
  • Cluster center is fixed after creation (first user's coordinates)

API Reference

LocationService

  • findNearest<T extends LocationItem>(items: T[], userLat: number, userLng: number, radiusKm: number, options?: FindNearestOptions): FindNearestResult<T>

Note: LocationService extends LocationCoreService and provides location-based utilities. All calculations are performed in-memory on the data you provide.

LocationCoreService

  • findNearest<T extends LocationItem>(items: T[], userLat: number, userLng: number, radiusKm: number, options?: FindNearestOptions): FindNearestResult<T>
  • filterByRadius<T extends LocationItem>(items: T[], userLat: number, userLng: number, radiusKm: number): Array<T & { distance: number }>
  • sortByDistance<T extends LocationItem>(items: T[], userLat: number, userLng: number): Array<T & { distance: number }>
  • calculateDistanceForItem<T extends LocationItem>(item: T, userLat: number, userLng: number): number | null

DistanceFilterService

  • applyDistanceFilter<T>(items: T[], lat?: number, lng?: number, maxDistance?: number): T[]
  • calculateDistancesForItems<T>(items: T[], userLat: number, userLng: number): T[]

ClusteringService

  • createClusters(users: UserWithLocation | UserWithLocation[], options?: ClusteringOptions): Cluster[]
  • addUserToCluster(user: UserWithLocation, clusters: Cluster[], radiusKm?: number): Cluster[]
  • findNearestCluster(user: UserWithLocation, clusters: Cluster[], radiusKm: number): Cluster | null

Utilities

  • calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number - Calculate distance between two coordinates using Haversine formula
  • toRadians(degrees: number): number - Convert degrees to radians
  • isValidCoordinate(lat: number, lng: number): boolean - Validate if coordinates are within valid ranges
  • extractCoordinates(item: any): Coordinates | null - Extract coordinates from an object (supports multiple formats)
  • getCityByCoordinates(lat: number, lng: number, options?: GeocodingOptions): Promise<ReverseGeocodingResult> - Get city information by coordinates
  • clearGeocodingCache(): void - Clear the geocoding cache
  • getCacheStats(): { size: number; maxSize: number } - Get geocoding cache statistics

Constants and Enums

Enums

  • SortBy - Sort options enum
    • SortBy.DISTANCE - Sort by distance
    • SortBy.NAME - Sort by name
  • GeocodingResultFormat - Geocoding result format enum
    • GeocodingResultFormat.FULL - Full Nominatim response
    • GeocodingResultFormat.CITY - City info object
    • GeocodingResultFormat.ADDRESS - Formatted address string

Constants

Geographic Constants:

  • EARTH_RADIUS_KM - Earth's radius in kilometers (6371)
  • DEFAULT_RADIUS_KM - Default search radius (10 km)
  • MIN_LATITUDE, MAX_LATITUDE - Valid latitude range (-90 to 90)
  • MIN_LONGITUDE, MAX_LONGITUDE - Valid longitude range (-180 to 180)

Pagination Constants:

  • DEFAULT_PAGE - Default page number (1)
  • DEFAULT_LIMIT - Default items per page (20)
  • MAX_LIMIT - Maximum items per page (100)

Clustering Constants:

  • DEFAULT_CLUSTER_RADIUS_KM - Default cluster radius (50 km)

Sort Constants:

  • DEFAULT_SORT_BY - Default sort option (SortBy.DISTANCE)

Geocoding Constants:

  • DEFAULT_LANGUAGE - Default language for geocoding ('en')
  • DEFAULT_GEOCODING_FORMAT - Default geocoding format (GeocodingResultFormat.CITY)
  • DEFAULT_NOMINATIM_ENDPOINT - Default Nominatim API endpoint
  • DEFAULT_CACHE_TTL_MS - Default cache TTL (24 hours)
  • RATE_LIMIT_DELAY_MS - Rate limit delay (1000ms)
  • MAX_CACHE_SIZE - Maximum cache size (10000 entries)

Interfaces

FindNearestOptions

Options for finding nearest items:

interface FindNearestOptions {
  page?: number;
  limit?: number;
  sortBy?: SortBy; // SortBy.DISTANCE or SortBy.NAME
}

LocationItem

Base interface for items with location data. The service automatically detects coordinates in various formats:

interface LocationItem {
  // Supports multiple formats:
  lat?: number;
  lng?: number;
  location?: { lat: number; lng: number };
  coordinates?: { lat: number; lng: number } | [number, number];
  // ... and more
}

UserWithLocation

Interface for user clustering:

interface UserWithLocation {
  id: string;
  lat: number;
  lng: number;
}

Cluster

Result of clustering operation:

interface Cluster {
  center: { lat: number; lng: number };
  radius: number;
  users: UserWithLocation[];
}

GeocodingOptions

Options for reverse geocoding:

interface GeocodingOptions {
  format?: GeocodingResultFormat; // GeocodingResultFormat.FULL, CITY, or ADDRESS
  language?: string; // Language code (e.g., 'en', 'uk')
  cacheTtl?: number; // Cache TTL in milliseconds
  apiEndpoint?: string; // Custom Nominatim API endpoint
}

License

MIT